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" |
10 | 11 |
11 namespace blink { | 12 namespace blink { |
12 namespace { | 13 namespace { |
13 | 14 |
14 class ImageDataTest : public ::testing::Test { | 15 class ImageDataTest : public ::testing::Test { |
15 protected: | 16 protected: |
16 ImageDataTest(){}; | 17 virtual void SetUp() { |
17 void TearDown(){}; | 18 // Save the state of experimental canvas features and color correct |
| 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; |
18 }; | 37 }; |
19 | 38 |
20 TEST_F(ImageDataTest, NegativeAndZeroIntSizeTest) { | 39 TEST_F(ImageDataTest, NegativeAndZeroIntSizeTest) { |
21 ImageData* imageData; | 40 ImageData* imageData; |
22 | 41 |
23 imageData = ImageData::create(IntSize(0, 10)); | 42 imageData = ImageData::create(IntSize(0, 10)); |
24 EXPECT_EQ(imageData, nullptr); | 43 EXPECT_EQ(imageData, nullptr); |
25 | 44 |
26 imageData = ImageData::create(IntSize(10, 0)); | 45 imageData = ImageData::create(IntSize(10, 0)); |
27 EXPECT_EQ(imageData, nullptr); | 46 EXPECT_EQ(imageData, nullptr); |
(...skipping 26 matching lines...) Expand all Loading... |
54 // allocated to the ImageData, then an exception must raise. | 73 // allocated to the ImageData, then an exception must raise. |
55 TEST_F(ImageDataTest, MAYBE_CreateImageDataTooBig) { | 74 TEST_F(ImageDataTest, MAYBE_CreateImageDataTooBig) { |
56 DummyExceptionStateForTesting exceptionState; | 75 DummyExceptionStateForTesting exceptionState; |
57 ImageData* tooBigImageData = ImageData::create(32767, 32767, exceptionState); | 76 ImageData* tooBigImageData = ImageData::create(32767, 32767, exceptionState); |
58 if (!tooBigImageData) { | 77 if (!tooBigImageData) { |
59 EXPECT_TRUE(exceptionState.hadException()); | 78 EXPECT_TRUE(exceptionState.hadException()); |
60 EXPECT_EQ(exceptionState.code(), V8RangeError); | 79 EXPECT_EQ(exceptionState.code(), V8RangeError); |
61 } | 80 } |
62 } | 81 } |
63 | 82 |
| 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 |
64 } // namspace | 397 } // namspace |
65 } // namespace blink | 398 } // namespace blink |
OLD | NEW |