Index: third_party/WebKit/Source/core/html/ImageDataTest.cpp |
diff --git a/third_party/WebKit/Source/core/html/ImageDataTest.cpp b/third_party/WebKit/Source/core/html/ImageDataTest.cpp |
index 906f56299ab0e3b8a2064811dec5ecc284618241..82d9a8a4a17978e127508ccd7142e856518e887a 100644 |
--- a/third_party/WebKit/Source/core/html/ImageDataTest.cpp |
+++ b/third_party/WebKit/Source/core/html/ImageDataTest.cpp |
@@ -7,14 +7,33 @@ |
#include "core/dom/ExceptionCode.h" |
#include "platform/geometry/IntSize.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+#include "third_party/skia/include/core/SkColorSpaceXform.h" |
namespace blink { |
namespace { |
class ImageDataTest : public ::testing::Test { |
protected: |
- ImageDataTest(){}; |
- void TearDown(){}; |
+ virtual void SetUp() { |
+ // Save the state of experimental canvas features and color correct |
+ // rendering flags to restore them on teardown. |
+ experimentalCanvasFeatures = |
+ RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); |
+ colorCorrectRendering = |
+ RuntimeEnabledFeatures::colorCorrectRenderingEnabled(); |
+ RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled(true); |
+ RuntimeEnabledFeatures::setColorCorrectRenderingEnabled(true); |
+ } |
+ |
+ virtual void TearDown() { |
+ RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled( |
+ experimentalCanvasFeatures); |
+ RuntimeEnabledFeatures::setColorCorrectRenderingEnabled( |
+ colorCorrectRendering); |
+ } |
+ |
+ bool experimentalCanvasFeatures; |
+ bool colorCorrectRendering; |
}; |
TEST_F(ImageDataTest, NegativeAndZeroIntSizeTest) { |
@@ -61,5 +80,319 @@ TEST_F(ImageDataTest, MAYBE_CreateImageDataTooBig) { |
} |
} |
+// Skia conversion does not guarantee to be exact, se we need to do approximate |
+// comparisons. |
+static inline bool IsNearlyTheSame(float f, float g) { |
+ static const float epsilonScale = 0.01f; |
+ return std::abs(f - g) < |
+ epsilonScale * |
+ std::max(std::max(std::abs(f), std::abs(g)), epsilonScale); |
+} |
+ |
+// This test verifies the correct behavior of ImageData member function used |
+// to convert pixels data from canvas pixel format to image data storage |
+// format. This function is used in BaseRenderingContext2D::getImageData. |
+TEST_F(ImageDataTest, |
+ TestConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat) { |
+ // Source pixels in RGBA32 |
+ unsigned char rgba32Pixels[] = {255, 0, 0, 255, // Red |
+ 0, 0, 0, 0, // Transparent |
+ 255, 192, 128, 64, // Decreasing values |
+ 93, 117, 205, 11}; // Random values |
+ const unsigned numColorComponents = 16; |
+ // Source pixels in F16 |
+ unsigned char f16Pixels[numColorComponents * 2]; |
+ |
+ // Filling F16 source pixels |
+ std::unique_ptr<SkColorSpaceXform> xform = |
+ SkColorSpaceXform::New(SkColorSpace::MakeSRGBLinear().get(), |
+ SkColorSpace::MakeSRGBLinear().get()); |
+ xform->apply(SkColorSpaceXform::ColorFormat::kRGBA_F16_ColorFormat, f16Pixels, |
+ SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat, |
+ rgba32Pixels, 4, SkAlphaType::kUnpremul_SkAlphaType); |
+ |
+ // Creating ArrayBufferContents objects. We need two buffers for RGBA32 data |
+ // because kRGBA8CanvasPixelFormat->kUint8ClampedArrayStorageFormat consumes |
+ // the input data parameter. |
+ WTF::ArrayBufferContents contentsRGBA32( |
+ numColorComponents, 1, WTF::ArrayBufferContents::NotShared, |
+ WTF::ArrayBufferContents::DontInitialize); |
+ std::memcpy(contentsRGBA32.data(), rgba32Pixels, numColorComponents); |
+ |
+ WTF::ArrayBufferContents contents2RGBA32( |
+ numColorComponents, 1, WTF::ArrayBufferContents::NotShared, |
+ WTF::ArrayBufferContents::DontInitialize); |
+ std::memcpy(contents2RGBA32.data(), rgba32Pixels, numColorComponents); |
+ |
+ WTF::ArrayBufferContents contentsF16( |
+ numColorComponents * 2, 1, WTF::ArrayBufferContents::NotShared, |
+ WTF::ArrayBufferContents::DontInitialize); |
+ std::memcpy(contentsF16.data(), f16Pixels, numColorComponents * 2); |
+ |
+ // Uint16 is not supported as the storage format for ImageData created from a |
+ // canvas, so this conversion is neither implemented nor tested here. |
+ bool testPassed = true; |
+ DOMArrayBufferView* data = nullptr; |
+ DOMUint8ClampedArray* dataU8 = nullptr; |
+ DOMFloat32Array* dataF32 = nullptr; |
+ |
+ // Testing kRGBA8CanvasPixelFormat -> kUint8ClampedArrayStorageFormat |
+ data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
+ contentsRGBA32, kRGBA8CanvasPixelFormat, kUint8ClampedArrayStorageFormat); |
+ DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeUint8Clamped); |
+ dataU8 = const_cast<DOMUint8ClampedArray*>( |
+ static_cast<const DOMUint8ClampedArray*>(data)); |
+ DCHECK(dataU8); |
+ for (unsigned i = 0; i < numColorComponents; i++) { |
+ if (dataU8->item(i) != rgba32Pixels[i]) { |
+ testPassed = false; |
+ break; |
+ } |
+ } |
+ EXPECT_TRUE(testPassed); |
+ |
+ // Testing kRGBA8CanvasPixelFormat -> kFloat32ArrayStorageFormat |
+ data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
+ contents2RGBA32, kRGBA8CanvasPixelFormat, kFloat32ArrayStorageFormat); |
+ DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeFloat32); |
+ dataF32 = |
+ const_cast<DOMFloat32Array*>(static_cast<const DOMFloat32Array*>(data)); |
+ DCHECK(dataF32); |
+ for (unsigned i = 0; i < numColorComponents; i++) { |
+ if (!IsNearlyTheSame(dataF32->item(i), rgba32Pixels[i] / 255.0)) { |
+ testPassed = false; |
+ break; |
+ } |
+ } |
+ EXPECT_TRUE(testPassed); |
+ |
+ // Testing kF16CanvasPixelFormat -> kUint8ClampedArrayStorageFormat |
+ data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
+ contentsF16, kF16CanvasPixelFormat, kUint8ClampedArrayStorageFormat); |
+ DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeUint8Clamped); |
+ dataU8 = const_cast<DOMUint8ClampedArray*>( |
+ static_cast<const DOMUint8ClampedArray*>(data)); |
+ DCHECK(dataU8); |
+ for (unsigned i = 0; i < numColorComponents; i++) { |
+ if (!IsNearlyTheSame(dataU8->item(i), rgba32Pixels[i])) { |
+ testPassed = false; |
+ break; |
+ } |
+ } |
+ EXPECT_TRUE(testPassed); |
+ |
+ // Testing kF16CanvasPixelFormat -> kFloat32ArrayStorageFormat |
+ data = ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
+ contentsF16, kF16CanvasPixelFormat, kFloat32ArrayStorageFormat); |
+ DCHECK(data->type() == DOMArrayBufferView::ViewType::TypeFloat32); |
+ dataF32 = |
+ const_cast<DOMFloat32Array*>(static_cast<const DOMFloat32Array*>(data)); |
+ DCHECK(dataF32); |
+ for (unsigned i = 0; i < numColorComponents; i++) { |
+ if (!IsNearlyTheSame(dataF32->item(i), rgba32Pixels[i] / 255.0)) { |
+ testPassed = false; |
+ break; |
+ } |
+ } |
+ EXPECT_TRUE(testPassed); |
+} |
+ |
+bool convertPixelsToColorSpaceAndPixelFormatForTest( |
+ DOMArrayBufferView* dataArray, |
+ CanvasColorSpace srcColorSpace, |
+ CanvasColorSpace dstColorSpace, |
+ CanvasPixelFormat dstPixelFormat, |
+ std::unique_ptr<uint8_t[]>& convertedPixels) { |
+ // Setting SkColorSpaceXform::apply parameters |
+ SkColorSpaceXform::ColorFormat srcColorFormat = |
+ SkColorSpaceXform::kRGBA_8888_ColorFormat; |
+ |
+ unsigned dataLength = dataArray->byteLength() / dataArray->typeSize(); |
+ unsigned numPixels = dataLength / 4; |
+ DOMUint8ClampedArray* u8Array = nullptr; |
+ DOMUint16Array* u16Array = nullptr; |
+ DOMFloat32Array* f32Array = nullptr; |
+ void* srcData = nullptr; |
+ |
+ switch (dataArray->type()) { |
+ case ArrayBufferView::ViewType::TypeUint8Clamped: |
+ u8Array = const_cast<DOMUint8ClampedArray*>( |
+ static_cast<const DOMUint8ClampedArray*>(dataArray)); |
+ srcData = static_cast<void*>(u8Array->data()); |
+ break; |
+ |
+ case ArrayBufferView::ViewType::TypeUint16: |
+ u16Array = const_cast<DOMUint16Array*>( |
+ static_cast<const DOMUint16Array*>(dataArray)); |
+ srcColorFormat = SkColorSpaceXform::ColorFormat::kRGBA_U16_BE_ColorFormat; |
+ srcData = static_cast<void*>(u16Array->data()); |
+ break; |
+ |
+ case ArrayBufferView::ViewType::TypeFloat32: |
+ f32Array = const_cast<DOMFloat32Array*>( |
+ static_cast<const DOMFloat32Array*>(dataArray)); |
+ srcColorFormat = SkColorSpaceXform::kRGBA_F32_ColorFormat; |
+ srcData = static_cast<void*>(f32Array->data()); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ return false; |
+ } |
+ |
+ SkColorSpaceXform::ColorFormat dstColorFormat = |
+ SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat; |
+ if (dstPixelFormat == kF16CanvasPixelFormat) |
+ dstColorFormat = SkColorSpaceXform::ColorFormat::kRGBA_F16_ColorFormat; |
+ |
+ sk_sp<SkColorSpace> srcSkColorSpace = nullptr; |
+ if (u8Array) { |
+ srcSkColorSpace = ImageData::getSkColorSpaceForTest( |
+ srcColorSpace, kRGBA8CanvasPixelFormat); |
+ } else { |
+ srcSkColorSpace = |
+ ImageData::getSkColorSpaceForTest(srcColorSpace, kF16CanvasPixelFormat); |
+ } |
+ |
+ sk_sp<SkColorSpace> dstSkColorSpace = |
+ ImageData::getSkColorSpaceForTest(dstColorSpace, dstPixelFormat); |
+ |
+ // When the input dataArray is in Uint16, we normally should convert the |
+ // values from Little Endian to Big Endian before passing the buffer to |
+ // SkColorSpaceXform::apply. However, in this test scenario we are creating |
+ // the Uin16 dataArray by multiplying a Uint8Clamped array members by 257, |
+ // hence the Big Endian and Little Endian representations are the same. |
+ |
+ std::unique_ptr<SkColorSpaceXform> xform = |
+ SkColorSpaceXform::New(srcSkColorSpace.get(), dstSkColorSpace.get()); |
+ |
+ if (!xform->apply(dstColorFormat, convertedPixels.get(), srcColorFormat, |
+ srcData, numPixels, kUnpremul_SkAlphaType)) |
+ return false; |
+ return true; |
+} |
+ |
+// This test verifies the correct behavior of ImageData member function used |
+// to convert image data from image data storage format to canvas pixel format. |
+// This function is used in BaseRenderingContext2D::putImageData. |
+TEST_F(ImageDataTest, TestGetImageDataInCanvasColorSettings) { |
+ unsigned numImageDataColorSpaces = 3; |
+ CanvasColorSpace imageDataColorSpaces[] = { |
+ kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, kP3CanvasColorSpace, |
+ }; |
+ |
+ unsigned numImageDataStorageFormats = 3; |
+ ImageDataStorageFormat imageDataStorageFormats[] = { |
+ kUint8ClampedArrayStorageFormat, kUint16ArrayStorageFormat, |
+ kFloat32ArrayStorageFormat, |
+ }; |
+ |
+ unsigned numCanvasColorSettings = 4; |
+ CanvasColorSpace canvasColorSpaces[] = { |
+ kSRGBCanvasColorSpace, kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, |
+ kP3CanvasColorSpace, |
+ }; |
+ CanvasPixelFormat canvasPixelFormats[] = { |
+ kRGBA8CanvasPixelFormat, kF16CanvasPixelFormat, kF16CanvasPixelFormat, |
+ kF16CanvasPixelFormat, |
+ }; |
+ |
+ // As cross checking the output of Skia color space covnersion API is not in |
+ // the scope of this unit test, we do turn-around tests here. To do so, we |
+ // create an ImageData with the selected color space and storage format, |
+ // convert it to the target canvas color space and pixel format by calling |
+ // ImageData::imageDataInCanvasColorSettings(), and then convert it back |
+ // to the source image data color space and Float32 storage format by calling |
+ // ImageData::convertPixelsFromCanvasPixelFormatToImageDataStorageFormat(). |
+ // We expect to get the same image data as we started with. |
+ |
+ // Source pixels in RGBA32 |
+ uint8_t u8Pixels[] = {255, 0, 0, 255, // Red |
+ 0, 0, 0, 0, // Transparent |
+ 255, 192, 128, 64, // Decreasing values |
+ 93, 117, 205, 11}; // Random values |
+ unsigned dataLength = 16; |
+ |
+ uint16_t* u16Pixels = new uint16_t[dataLength]; |
+ for (unsigned i = 0; i < dataLength; i++) |
+ u16Pixels[i] = u8Pixels[i] * 257; |
+ |
+ float* f32Pixels = new float[dataLength]; |
+ for (unsigned i = 0; i < dataLength; i++) |
+ f32Pixels[i] = u8Pixels[i] / 255.0; |
+ |
+ DOMArrayBufferView* dataArray = nullptr; |
+ |
+ DOMUint8ClampedArray* dataU8 = |
+ DOMUint8ClampedArray::create(u8Pixels, dataLength); |
+ DCHECK(dataU8); |
+ EXPECT_EQ(dataLength, dataU8->length()); |
+ DOMUint16Array* dataU16 = DOMUint16Array::create(u16Pixels, dataLength); |
+ DCHECK(dataU16); |
+ EXPECT_EQ(dataLength, dataU16->length()); |
+ DOMFloat32Array* dataF32 = DOMFloat32Array::create(f32Pixels, dataLength); |
+ DCHECK(dataF32); |
+ EXPECT_EQ(dataLength, dataF32->length()); |
+ |
+ ImageData* imageData = nullptr; |
+ ImageDataColorSettings colorSettings; |
+ |
+ // At most two bytes are needed for output per color component. |
+ std::unique_ptr<uint8_t[]> pixelsConvertedManually( |
+ new uint8_t[dataLength * 2]()); |
+ std::unique_ptr<uint8_t[]> pixelsConvertedInImageData( |
+ new uint8_t[dataLength * 2]()); |
+ |
+ // Loop through different possible combinations of image data color space and |
+ // storage formats and create the respective test image data objects. |
+ for (unsigned i = 0; i < numImageDataColorSpaces; i++) { |
+ colorSettings.setColorSpace( |
+ ImageData::canvasColorSpaceName(imageDataColorSpaces[i])); |
+ |
+ for (unsigned j = 0; j < numImageDataStorageFormats; j++) { |
+ switch (imageDataStorageFormats[j]) { |
+ case kUint8ClampedArrayStorageFormat: |
+ dataArray = static_cast<DOMArrayBufferView*>(dataU8); |
+ colorSettings.setStorageFormat(kUint8ClampedArrayStorageFormatName); |
+ break; |
+ case kUint16ArrayStorageFormat: |
+ dataArray = static_cast<DOMArrayBufferView*>(dataU16); |
+ colorSettings.setStorageFormat(kUint16ArrayStorageFormatName); |
+ break; |
+ case kFloat32ArrayStorageFormat: |
+ dataArray = static_cast<DOMArrayBufferView*>(dataF32); |
+ colorSettings.setStorageFormat(kFloat32ArrayStorageFormatName); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ |
+ imageData = |
+ ImageData::createForTest(IntSize(2, 2), dataArray, &colorSettings); |
+ |
+ for (unsigned k = 0; k < numCanvasColorSettings; k++) { |
+ unsigned outputLength = |
+ (canvasPixelFormats[k] == kRGBA8CanvasPixelFormat) ? dataLength |
+ : dataLength * 2; |
+ // Convert the original data used to create ImageData to the |
+ // canvas color space and canvas pixel format. |
+ EXPECT_TRUE(convertPixelsToColorSpaceAndPixelFormatForTest( |
+ dataArray, imageDataColorSpaces[i], canvasColorSpaces[k], |
+ canvasPixelFormats[k], pixelsConvertedManually)); |
+ |
+ // Convert the image data to the color settings of the canvas. |
+ EXPECT_TRUE(imageData->imageDataInCanvasColorSettings( |
+ canvasColorSpaces[k], canvasPixelFormats[k], |
+ pixelsConvertedInImageData)); |
+ |
+ // Compare the converted pixels |
+ EXPECT_EQ(0, memcmp(pixelsConvertedManually.get(), |
+ pixelsConvertedInImageData.get(), outputLength)); |
+ } |
+ } |
+ } |
+ delete[] u16Pixels; |
+ delete[] f32Pixels; |
+} |
+ |
} // namspace |
} // namespace blink |