| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/html/ImageData.h" | 5 #include "core/html/ImageData.h" |
| 6 | 6 |
| 7 #include "core/dom/ExceptionCode.h" | 7 #include "core/dom/ExceptionCode.h" |
| 8 #include "platform/geometry/IntSize.h" | 8 #include "platform/geometry/IntSize.h" |
| 9 #include "testing/gtest/include/gtest/gtest.h" | 9 #include "testing/gtest/include/gtest/gtest.h" |
| 10 #include "third_party/skia/include/core/SkColorSpaceXform.h" | |
| 11 | 10 |
| 12 namespace blink { | 11 namespace blink { |
| 13 namespace { | 12 namespace { |
| 14 | 13 |
| 15 class ImageDataTest : public ::testing::Test { | 14 class ImageDataTest : public ::testing::Test { |
| 16 protected: | 15 protected: |
| 17 virtual void SetUp() { | 16 ImageDataTest(){}; |
| 18 // Save the state of experimental canvas features and color correct | 17 void TearDown(){}; |
| 19 // rendering flags to restore them on teardown. | |
| 20 experimentalCanvasFeatures = | |
| 21 RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); | |
| 22 colorCorrectRendering = | |
| 23 RuntimeEnabledFeatures::colorCorrectRenderingEnabled(); | |
| 24 RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled(true); | |
| 25 RuntimeEnabledFeatures::setColorCorrectRenderingEnabled(true); | |
| 26 } | |
| 27 | |
| 28 virtual void TearDown() { | |
| 29 RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled( | |
| 30 experimentalCanvasFeatures); | |
| 31 RuntimeEnabledFeatures::setColorCorrectRenderingEnabled( | |
| 32 colorCorrectRendering); | |
| 33 } | |
| 34 | |
| 35 bool experimentalCanvasFeatures; | |
| 36 bool colorCorrectRendering; | |
| 37 }; | 18 }; |
| 38 | 19 |
| 39 TEST_F(ImageDataTest, NegativeAndZeroIntSizeTest) { | 20 TEST_F(ImageDataTest, NegativeAndZeroIntSizeTest) { |
| 40 ImageData* imageData; | 21 ImageData* imageData; |
| 41 | 22 |
| 42 imageData = ImageData::create(IntSize(0, 10)); | 23 imageData = ImageData::create(IntSize(0, 10)); |
| 43 EXPECT_EQ(imageData, nullptr); | 24 EXPECT_EQ(imageData, nullptr); |
| 44 | 25 |
| 45 imageData = ImageData::create(IntSize(10, 0)); | 26 imageData = ImageData::create(IntSize(10, 0)); |
| 46 EXPECT_EQ(imageData, nullptr); | 27 EXPECT_EQ(imageData, nullptr); |
| (...skipping 26 matching lines...) Expand all Loading... |
| 73 // allocated to the ImageData, then an exception must raise. | 54 // allocated to the ImageData, then an exception must raise. |
| 74 TEST_F(ImageDataTest, MAYBE_CreateImageDataTooBig) { | 55 TEST_F(ImageDataTest, MAYBE_CreateImageDataTooBig) { |
| 75 DummyExceptionStateForTesting exceptionState; | 56 DummyExceptionStateForTesting exceptionState; |
| 76 ImageData* tooBigImageData = ImageData::create(32767, 32767, exceptionState); | 57 ImageData* tooBigImageData = ImageData::create(32767, 32767, exceptionState); |
| 77 if (!tooBigImageData) { | 58 if (!tooBigImageData) { |
| 78 EXPECT_TRUE(exceptionState.hadException()); | 59 EXPECT_TRUE(exceptionState.hadException()); |
| 79 EXPECT_EQ(exceptionState.code(), V8RangeError); | 60 EXPECT_EQ(exceptionState.code(), V8RangeError); |
| 80 } | 61 } |
| 81 } | 62 } |
| 82 | 63 |
| 83 // Skia conversion does not guarantee to be exact, se we need to do approximate | |
| 84 // comparisons. | |
| 85 static inline bool IsNearlyTheSame(float f, float g) { | |
| 86 static const float epsilonScale = 0.01f; | |
| 87 return std::abs(f - g) < | |
| 88 epsilonScale * | |
| 89 std::max(std::max(std::abs(f), std::abs(g)), epsilonScale); | |
| 90 } | |
| 91 | |
| 92 // This test verifies the correct behavior of ImageData member function used | |
| 93 // to convert pixels data from canvas pixel format to image data storage | |
| 94 // format. This function is used in BaseRenderingContext2D::getImageData. | |
| 95 TEST_F(ImageDataTest, | |
| 96 TestConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat) { | |
| 97 // Source pixels in RGBA32 | |
| 98 unsigned char rgba32Pixels[] = {255, 0, 0, 255, // Red | |
| 99 0, 0, 0, 0, // Transparent | |
| 100 255, 192, 128, 64, // Decreasing values | |
| 101 93, 117, 205, 11}; // Random values | |
| 102 const unsigned numColorComponents = 16; | |
| 103 // Source pixels in F16 | |
| 104 unsigned char f16Pixels[numColorComponents * 2]; | |
| 105 | |
| 106 // Filling F16 source pixels | |
| 107 std::unique_ptr<SkColorSpaceXform> xform = | |
| 108 SkColorSpaceXform::New(SkColorSpace::MakeSRGBLinear().get(), | |
| 109 SkColorSpace::MakeSRGBLinear().get()); | |
| 110 xform->apply(SkColorSpaceXform::ColorFormat::kRGBA_F16_ColorFormat, f16Pixels, | |
| 111 SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat, | |
| 112 rgba32Pixels, 4, SkAlphaType::kUnpremul_SkAlphaType); | |
| 113 | |
| 114 // Creating ArrayBufferContents objects. We need two buffers for RGBA32 data | |
| 115 // because kRGBA8CanvasPixelFormat->kUint8ClampedArrayStorageFormat consumes | |
| 116 // the input data parameter. | |
| 117 WTF::ArrayBufferContents contentsRGBA32( | |
| 118 numColorComponents, 1, WTF::ArrayBufferContents::NotShared, | |
| 119 WTF::ArrayBufferContents::DontInitialize); | |
| 120 std::memcpy(contentsRGBA32.data(), rgba32Pixels, numColorComponents); | |
| 121 | |
| 122 WTF::ArrayBufferContents contents2RGBA32( | |
| 123 numColorComponents, 1, WTF::ArrayBufferContents::NotShared, | |
| 124 WTF::ArrayBufferContents::DontInitialize); | |
| 125 std::memcpy(contents2RGBA32.data(), rgba32Pixels, numColorComponents); | |
| 126 | |
| 127 WTF::ArrayBufferContents contentsF16( | |
| 128 numColorComponents * 2, 1, WTF::ArrayBufferContents::NotShared, | |
| 129 WTF::ArrayBufferContents::DontInitialize); | |
| 130 std::memcpy(contentsF16.data(), f16Pixels, numColorComponents * 2); | |
| 131 | |
| 132 // Uint16 is not supported as the storage format for ImageData created from a | |
| 133 // canvas, so this conversion is neither implemented nor tested here. | |
| 134 bool testPassed = true; | |
| 135 DOMArrayBufferView* data = nullptr; | |
| 136 DOMUint8ClampedArray* dataU8 = nullptr; | |
| 137 DOMFloat32Array* dataF32 = nullptr; | |
| 138 | |
| 139 // Testing kRGBA8CanvasPixelFormat -> kUint8ClampedArrayStorageFormat | |
| 140 data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( | |
| 141 contentsRGBA32, kRGBA8CanvasPixelFormat, kUint8ClampedArrayStorageFormat); | |
| 142 DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeUint8Clamped); | |
| 143 dataU8 = const_cast<DOMUint8ClampedArray*>( | |
| 144 static_cast<const DOMUint8ClampedArray*>(data)); | |
| 145 DCHECK(dataU8); | |
| 146 for (unsigned i = 0; i < numColorComponents; i++) { | |
| 147 if (dataU8->item(i) != rgba32Pixels[i]) { | |
| 148 testPassed = false; | |
| 149 break; | |
| 150 } | |
| 151 } | |
| 152 EXPECT_TRUE(testPassed); | |
| 153 | |
| 154 // Testing kRGBA8CanvasPixelFormat -> kFloat32ArrayStorageFormat | |
| 155 data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( | |
| 156 contents2RGBA32, kRGBA8CanvasPixelFormat, kFloat32ArrayStorageFormat); | |
| 157 DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeFloat32); | |
| 158 dataF32 = | |
| 159 const_cast<DOMFloat32Array*>(static_cast<const DOMFloat32Array*>(data)); | |
| 160 DCHECK(dataF32); | |
| 161 for (unsigned i = 0; i < numColorComponents; i++) { | |
| 162 if (!IsNearlyTheSame(dataF32->item(i), rgba32Pixels[i] / 255.0)) { | |
| 163 testPassed = false; | |
| 164 break; | |
| 165 } | |
| 166 } | |
| 167 EXPECT_TRUE(testPassed); | |
| 168 | |
| 169 // Testing kF16CanvasPixelFormat -> kUint8ClampedArrayStorageFormat | |
| 170 data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( | |
| 171 contentsF16, kF16CanvasPixelFormat, kUint8ClampedArrayStorageFormat); | |
| 172 DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeUint8Clamped); | |
| 173 dataU8 = const_cast<DOMUint8ClampedArray*>( | |
| 174 static_cast<const DOMUint8ClampedArray*>(data)); | |
| 175 DCHECK(dataU8); | |
| 176 for (unsigned i = 0; i < numColorComponents; i++) { | |
| 177 if (!IsNearlyTheSame(dataU8->item(i), rgba32Pixels[i])) { | |
| 178 testPassed = false; | |
| 179 break; | |
| 180 } | |
| 181 } | |
| 182 EXPECT_TRUE(testPassed); | |
| 183 | |
| 184 // Testing kF16CanvasPixelFormat -> kFloat32ArrayStorageFormat | |
| 185 data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( | |
| 186 contentsF16, kF16CanvasPixelFormat, kFloat32ArrayStorageFormat); | |
| 187 DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeFloat32); | |
| 188 dataF32 = | |
| 189 const_cast<DOMFloat32Array*>(static_cast<const DOMFloat32Array*>(data)); | |
| 190 DCHECK(dataF32); | |
| 191 for (unsigned i = 0; i < numColorComponents; i++) { | |
| 192 if (!IsNearlyTheSame(dataF32->item(i), rgba32Pixels[i] / 255.0)) { | |
| 193 testPassed = false; | |
| 194 break; | |
| 195 } | |
| 196 } | |
| 197 EXPECT_TRUE(testPassed); | |
| 198 } | |
| 199 | |
| 200 bool convertPixelsToColorSpaceAndPixelFormatForTest( | |
| 201 DOMArrayBufferView* dataArray, | |
| 202 CanvasColorSpace srcColorSpace, | |
| 203 CanvasColorSpace dstColorSpace, | |
| 204 CanvasPixelFormat dstPixelFormat, | |
| 205 std::unique_ptr<uint8_t[]>& convertedPixels) { | |
| 206 // Setting SkColorSpaceXform::apply parameters | |
| 207 SkColorSpaceXform::ColorFormat srcColorFormat = | |
| 208 SkColorSpaceXform::kRGBA_8888_ColorFormat; | |
| 209 | |
| 210 unsigned dataLength = dataArray->byteLength() / dataArray->typeSize(); | |
| 211 unsigned numPixels = dataLength / 4; | |
| 212 DOMUint8ClampedArray* u8Array = nullptr; | |
| 213 DOMUint16Array* u16Array = nullptr; | |
| 214 DOMFloat32Array* f32Array = nullptr; | |
| 215 void* srcData = nullptr; | |
| 216 | |
| 217 switch (dataArray->type()) { | |
| 218 case ArrayBufferView::ViewType::TypeUint8Clamped: | |
| 219 u8Array = const_cast<DOMUint8ClampedArray*>( | |
| 220 static_cast<const DOMUint8ClampedArray*>(dataArray)); | |
| 221 srcData = static_cast<void*>(u8Array->data()); | |
| 222 break; | |
| 223 | |
| 224 case ArrayBufferView::ViewType::TypeUint16: | |
| 225 u16Array = const_cast<DOMUint16Array*>( | |
| 226 static_cast<const DOMUint16Array*>(dataArray)); | |
| 227 srcColorFormat = SkColorSpaceXform::ColorFormat::kRGBA_U16_BE_ColorFormat; | |
| 228 srcData = static_cast<void*>(u16Array->data()); | |
| 229 break; | |
| 230 | |
| 231 case ArrayBufferView::ViewType::TypeFloat32: | |
| 232 f32Array = const_cast<DOMFloat32Array*>( | |
| 233 static_cast<const DOMFloat32Array*>(dataArray)); | |
| 234 srcColorFormat = SkColorSpaceXform::kRGBA_F32_ColorFormat; | |
| 235 srcData = static_cast<void*>(f32Array->data()); | |
| 236 break; | |
| 237 default: | |
| 238 NOTREACHED(); | |
| 239 return false; | |
| 240 } | |
| 241 | |
| 242 SkColorSpaceXform::ColorFormat dstColorFormat = | |
| 243 SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat; | |
| 244 if (dstPixelFormat == kF16CanvasPixelFormat) | |
| 245 dstColorFormat = SkColorSpaceXform::ColorFormat::kRGBA_F16_ColorFormat; | |
| 246 | |
| 247 sk_sp<SkColorSpace> srcSkColorSpace = nullptr; | |
| 248 if (u8Array) { | |
| 249 srcSkColorSpace = ImageData::getSkColorSpaceForTest( | |
| 250 srcColorSpace, kRGBA8CanvasPixelFormat); | |
| 251 } else { | |
| 252 srcSkColorSpace = | |
| 253 ImageData::getSkColorSpaceForTest(srcColorSpace, kF16CanvasPixelFormat); | |
| 254 } | |
| 255 | |
| 256 sk_sp<SkColorSpace> dstSkColorSpace = | |
| 257 ImageData::getSkColorSpaceForTest(dstColorSpace, dstPixelFormat); | |
| 258 | |
| 259 // When the input dataArray is in Uint16, we normally should convert the | |
| 260 // values from Little Endian to Big Endian before passing the buffer to | |
| 261 // SkColorSpaceXform::apply. However, in this test scenario we are creating | |
| 262 // the Uin16 dataArray by multiplying a Uint8Clamped array members by 257, | |
| 263 // hence the Big Endian and Little Endian representations are the same. | |
| 264 | |
| 265 std::unique_ptr<SkColorSpaceXform> xform = | |
| 266 SkColorSpaceXform::New(srcSkColorSpace.get(), dstSkColorSpace.get()); | |
| 267 | |
| 268 if (!xform->apply(dstColorFormat, convertedPixels.get(), srcColorFormat, | |
| 269 srcData, numPixels, kUnpremul_SkAlphaType)) | |
| 270 return false; | |
| 271 return true; | |
| 272 } | |
| 273 | |
| 274 // This test verifies the correct behavior of ImageData member function used | |
| 275 // to convert image data from image data storage format to canvas pixel format. | |
| 276 // This function is used in BaseRenderingContext2D::putImageData. | |
| 277 TEST_F(ImageDataTest, TestGetImageDataInCanvasColorSettings) { | |
| 278 unsigned numImageDataColorSpaces = 3; | |
| 279 CanvasColorSpace imageDataColorSpaces[] = { | |
| 280 kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, kP3CanvasColorSpace, | |
| 281 }; | |
| 282 | |
| 283 unsigned numImageDataStorageFormats = 3; | |
| 284 ImageDataStorageFormat imageDataStorageFormats[] = { | |
| 285 kUint8ClampedArrayStorageFormat, kUint16ArrayStorageFormat, | |
| 286 kFloat32ArrayStorageFormat, | |
| 287 }; | |
| 288 | |
| 289 unsigned numCanvasColorSettings = 4; | |
| 290 CanvasColorSpace canvasColorSpaces[] = { | |
| 291 kSRGBCanvasColorSpace, kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, | |
| 292 kP3CanvasColorSpace, | |
| 293 }; | |
| 294 CanvasPixelFormat canvasPixelFormats[] = { | |
| 295 kRGBA8CanvasPixelFormat, kF16CanvasPixelFormat, kF16CanvasPixelFormat, | |
| 296 kF16CanvasPixelFormat, | |
| 297 }; | |
| 298 | |
| 299 // As cross checking the output of Skia color space covnersion API is not in | |
| 300 // the scope of this unit test, we do turn-around tests here. To do so, we | |
| 301 // create an ImageData with the selected color space and storage format, | |
| 302 // convert it to the target canvas color space and pixel format by calling | |
| 303 // ImageData::imageDataInCanvasColorSettings(), and then convert it back | |
| 304 // to the source image data color space and Float32 storage format by calling | |
| 305 // ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat(). | |
| 306 // We expect to get the same image data as we started with. | |
| 307 | |
| 308 // Source pixels in RGBA32 | |
| 309 uint8_t u8Pixels[] = {255, 0, 0, 255, // Red | |
| 310 0, 0, 0, 0, // Transparent | |
| 311 255, 192, 128, 64, // Decreasing values | |
| 312 93, 117, 205, 11}; // Random values | |
| 313 unsigned dataLength = 16; | |
| 314 | |
| 315 uint16_t* u16Pixels = new uint16_t[dataLength]; | |
| 316 for (unsigned i = 0; i < dataLength; i++) | |
| 317 u16Pixels[i] = u8Pixels[i] * 257; | |
| 318 | |
| 319 float* f32Pixels = new float[dataLength]; | |
| 320 for (unsigned i = 0; i < dataLength; i++) | |
| 321 f32Pixels[i] = u8Pixels[i] / 255.0; | |
| 322 | |
| 323 DOMArrayBufferView* dataArray = nullptr; | |
| 324 | |
| 325 DOMUint8ClampedArray* dataU8 = | |
| 326 DOMUint8ClampedArray::create(u8Pixels, dataLength); | |
| 327 DCHECK(dataU8); | |
| 328 EXPECT_EQ(dataLength, dataU8->length()); | |
| 329 DOMUint16Array* dataU16 = DOMUint16Array::create(u16Pixels, dataLength); | |
| 330 DCHECK(dataU16); | |
| 331 EXPECT_EQ(dataLength, dataU16->length()); | |
| 332 DOMFloat32Array* dataF32 = DOMFloat32Array::create(f32Pixels, dataLength); | |
| 333 DCHECK(dataF32); | |
| 334 EXPECT_EQ(dataLength, dataF32->length()); | |
| 335 | |
| 336 ImageData* imageData = nullptr; | |
| 337 ImageDataColorSettings colorSettings; | |
| 338 | |
| 339 // At most two bytes are needed for output per color component. | |
| 340 std::unique_ptr<uint8_t[]> pixelsConvertedManually( | |
| 341 new uint8_t[dataLength * 2]()); | |
| 342 std::unique_ptr<uint8_t[]> pixelsConvertedInImageData( | |
| 343 new uint8_t[dataLength * 2]()); | |
| 344 | |
| 345 // Loop through different possible combinations of image data color space and | |
| 346 // storage formats and create the respective test image data objects. | |
| 347 for (unsigned i = 0; i < numImageDataColorSpaces; i++) { | |
| 348 colorSettings.setColorSpace( | |
| 349 ImageData::canvasColorSpaceName(imageDataColorSpaces[i])); | |
| 350 | |
| 351 for (unsigned j = 0; j < numImageDataStorageFormats; j++) { | |
| 352 switch (imageDataStorageFormats[j]) { | |
| 353 case kUint8ClampedArrayStorageFormat: | |
| 354 dataArray = static_cast<DOMArrayBufferView*>(dataU8); | |
| 355 colorSettings.setStorageFormat(kUint8ClampedArrayStorageFormatName); | |
| 356 break; | |
| 357 case kUint16ArrayStorageFormat: | |
| 358 dataArray = static_cast<DOMArrayBufferView*>(dataU16); | |
| 359 colorSettings.setStorageFormat(kUint16ArrayStorageFormatName); | |
| 360 break; | |
| 361 case kFloat32ArrayStorageFormat: | |
| 362 dataArray = static_cast<DOMArrayBufferView*>(dataF32); | |
| 363 colorSettings.setStorageFormat(kFloat32ArrayStorageFormatName); | |
| 364 break; | |
| 365 default: | |
| 366 NOTREACHED(); | |
| 367 } | |
| 368 | |
| 369 imageData = | |
| 370 ImageData::createForTest(IntSize(2, 2), dataArray, &colorSettings); | |
| 371 | |
| 372 for (unsigned k = 0; k < numCanvasColorSettings; k++) { | |
| 373 unsigned outputLength = | |
| 374 (canvasPixelFormats[k] == kRGBA8CanvasPixelFormat) ? dataLength | |
| 375 : dataLength * 2; | |
| 376 // Convert the original data used to create ImageData to the | |
| 377 // canvas color space and canvas pixel format. | |
| 378 EXPECT_TRUE(convertPixelsToColorSpaceAndPixelFormatForTest( | |
| 379 dataArray, imageDataColorSpaces[i], canvasColorSpaces[k], | |
| 380 canvasPixelFormats[k], pixelsConvertedManually)); | |
| 381 | |
| 382 // Convert the image data to the color settings of the canvas. | |
| 383 EXPECT_TRUE(imageData->imageDataInCanvasColorSettings( | |
| 384 canvasColorSpaces[k], canvasPixelFormats[k], | |
| 385 pixelsConvertedInImageData)); | |
| 386 | |
| 387 // Compare the converted pixels | |
| 388 EXPECT_EQ(0, memcmp(pixelsConvertedManually.get(), | |
| 389 pixelsConvertedInImageData.get(), outputLength)); | |
| 390 } | |
| 391 } | |
| 392 } | |
| 393 delete[] u16Pixels; | |
| 394 delete[] f32Pixels; | |
| 395 } | |
| 396 | |
| 397 } // namspace | 64 } // namspace |
| 398 } // namespace blink | 65 } // namespace blink |
| OLD | NEW |