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 |