Index: third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp |
diff --git a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp |
index cfc096c739c577f714387f413a39d2aa2eb1acdd..3489a30ddffadac68c908151690635341696ddfe 100644 |
--- a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp |
+++ b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp |
@@ -37,6 +37,7 @@ |
#include "third_party/skia/include/core/SkColorSpaceXform.h" |
#include "third_party/skia/include/core/SkImage.h" |
#include "third_party/skia/include/core/SkSurface.h" |
+#include "third_party/skia/include/core/SkSwizzle.h" |
using ::testing::Mock; |
@@ -1342,6 +1343,291 @@ TEST_F(CanvasRenderingContext2DTest, ImageBitmapColorSpaceConversion) { |
color_correct_rendering_default_mode_runtime_flag); |
} |
+bool ConvertPixelsToColorSpaceAndPixelFormatForTest( |
+ DOMArrayBufferView* data_array, |
+ CanvasColorSpace src_color_space, |
+ CanvasColorSpace dst_color_space, |
+ CanvasPixelFormat dst_pixel_format, |
+ std::unique_ptr<uint8_t[]>& converted_pixels) { |
+ // Setting SkColorSpaceXform::apply parameters |
+ SkColorSpaceXform::ColorFormat src_color_format = |
+ SkColorSpaceXform::kRGBA_8888_ColorFormat; |
+ |
+ unsigned data_length = data_array->byteLength() / data_array->TypeSize(); |
+ unsigned num_pixels = data_length / 4; |
+ DOMUint8ClampedArray* u8_array = nullptr; |
+ DOMUint16Array* u16_array = nullptr; |
+ DOMFloat32Array* f32_array = nullptr; |
+ void* src_data = nullptr; |
+ |
+ switch (data_array->GetType()) { |
+ case ArrayBufferView::ViewType::kTypeUint8Clamped: |
+ u8_array = const_cast<DOMUint8ClampedArray*>( |
+ static_cast<const DOMUint8ClampedArray*>(data_array)); |
+ src_data = static_cast<void*>(u8_array->Data()); |
+ break; |
+ |
+ case ArrayBufferView::ViewType::kTypeUint16: |
+ u16_array = const_cast<DOMUint16Array*>( |
+ static_cast<const DOMUint16Array*>(data_array)); |
+ src_color_format = |
+ SkColorSpaceXform::ColorFormat::kRGBA_U16_BE_ColorFormat; |
+ src_data = static_cast<void*>(u16_array->Data()); |
+ break; |
+ |
+ case ArrayBufferView::ViewType::kTypeFloat32: |
+ f32_array = const_cast<DOMFloat32Array*>( |
+ static_cast<const DOMFloat32Array*>(data_array)); |
+ src_color_format = SkColorSpaceXform::kRGBA_F32_ColorFormat; |
+ src_data = static_cast<void*>(f32_array->Data()); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ return false; |
+ } |
+ |
+ SkColorSpaceXform::ColorFormat dst_color_format = |
+ SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat; |
+ if (dst_pixel_format == kF16CanvasPixelFormat) |
+ dst_color_format = SkColorSpaceXform::ColorFormat::kRGBA_F32_ColorFormat; |
+ |
+ sk_sp<SkColorSpace> src_sk_color_space = nullptr; |
+ if (u8_array) { |
+ src_sk_color_space = ImageData::GetSkColorSpaceForTest( |
+ src_color_space, kRGBA8CanvasPixelFormat); |
+ } else { |
+ src_sk_color_space = ImageData::GetSkColorSpaceForTest( |
+ src_color_space, kF16CanvasPixelFormat); |
+ } |
+ |
+ sk_sp<SkColorSpace> dst_sk_color_space = |
+ ImageData::GetSkColorSpaceForTest(dst_color_space, dst_pixel_format); |
+ |
+ // 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( |
+ src_sk_color_space.get(), dst_sk_color_space.get()); |
+ |
+ if (!xform->apply(dst_color_format, converted_pixels.get(), src_color_format, |
+ src_data, num_pixels, kUnpremul_SkAlphaType)) |
+ return false; |
+ return true; |
+} |
+ |
+// The color settings of the surface of the canvas always remaines loyal to the |
+// first created context 2D. Therefore, we have to test different canvas color |
+// space settings for CanvasRenderingContext2D::putImageData() in different |
+// tests. |
+enum class CanvasColorSpaceSettings : uint8_t { |
+ CANVAS_SRGB = 0, |
+ CANVAS_LINEARSRGB = 1, |
+ CANVAS_REC2020 = 2, |
+ CANVAS_P3 = 3, |
+ |
+ LAST = CANVAS_P3 |
+}; |
+ |
+// This test verifies the correct behavior of putImageData member function in |
+// color managed mode. |
+void TestPutImageDataOnCanvasWithColorSpaceSettings( |
+ HTMLCanvasElement& canvas_element, |
+ CanvasColorSpaceSettings canvas_colorspace_setting, |
+ float color_tolerance) { |
+ bool experimental_canvas_features_runtime_flag = |
+ RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); |
+ bool color_correct_rendering_runtime_flag = |
+ RuntimeEnabledFeatures::colorCorrectRenderingEnabled(); |
+ RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled(true); |
+ RuntimeEnabledFeatures::setColorCorrectRenderingEnabled(true); |
+ |
+ bool test_passed = true; |
+ unsigned num_image_data_color_spaces = 3; |
+ CanvasColorSpace image_data_color_spaces[] = { |
+ kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, kP3CanvasColorSpace, |
+ }; |
+ |
+ unsigned num_image_data_storage_formats = 3; |
+ ImageDataStorageFormat image_data_storage_formats[] = { |
+ kUint8ClampedArrayStorageFormat, kUint16ArrayStorageFormat, |
+ kFloat32ArrayStorageFormat, |
+ }; |
+ |
+ CanvasColorSpace canvas_color_spaces[] = { |
+ kSRGBCanvasColorSpace, kSRGBCanvasColorSpace, kRec2020CanvasColorSpace, |
+ kP3CanvasColorSpace, |
+ }; |
+ |
+ String canvas_color_space_names[] = { |
+ kSRGBCanvasColorSpaceName, kSRGBCanvasColorSpaceName, |
+ kRec2020CanvasColorSpaceName, kP3CanvasColorSpaceName}; |
+ |
+ CanvasPixelFormat canvas_pixel_formats[] = { |
+ kRGBA8CanvasPixelFormat, kF16CanvasPixelFormat, kF16CanvasPixelFormat, |
+ kF16CanvasPixelFormat, |
+ }; |
+ |
+ String canvas_pixel_format_names[] = { |
+ kRGBA8CanvasPixelFormatName, kF16CanvasPixelFormatName, |
+ kF16CanvasPixelFormatName, kF16CanvasPixelFormatName}; |
+ |
+ // Source pixels in RGBA32 |
+ uint8_t u8_pixels[] = {255, 0, 0, 255, // Red |
+ 0, 0, 0, 0, // Transparent |
+ 255, 192, 128, 64, // Decreasing values |
+ 93, 117, 205, 11}; // Random values |
+ unsigned data_length = 16; |
+ |
+ uint16_t* u16_pixels = new uint16_t[data_length]; |
+ for (unsigned i = 0; i < data_length; i++) |
+ u16_pixels[i] = u8_pixels[i] * 257; |
+ |
+ float* f32_pixels = new float[data_length]; |
+ for (unsigned i = 0; i < data_length; i++) |
+ f32_pixels[i] = u8_pixels[i] / 255.0; |
+ |
+ DOMArrayBufferView* data_array = nullptr; |
+ |
+ DOMUint8ClampedArray* data_u8 = |
+ DOMUint8ClampedArray::Create(u8_pixels, data_length); |
+ DCHECK(data_u8); |
+ EXPECT_EQ(data_length, data_u8->length()); |
+ DOMUint16Array* data_u16 = DOMUint16Array::Create(u16_pixels, data_length); |
+ DCHECK(data_u16); |
+ EXPECT_EQ(data_length, data_u16->length()); |
+ DOMFloat32Array* data_f32 = DOMFloat32Array::Create(f32_pixels, data_length); |
+ DCHECK(data_f32); |
+ EXPECT_EQ(data_length, data_f32->length()); |
+ |
+ ImageData* image_data = nullptr; |
+ ImageDataColorSettings color_settings; |
+ |
+ // At most four bytes are needed for Float32 output per color component. |
+ std::unique_ptr<uint8_t[]> pixels_converted_manually( |
+ new uint8_t[data_length * 4]()); |
+ |
+ // 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 < num_image_data_color_spaces; i++) { |
+ color_settings.setColorSpace( |
+ ImageData::CanvasColorSpaceName(image_data_color_spaces[i])); |
+ |
+ for (unsigned j = 0; j < num_image_data_storage_formats; j++) { |
+ switch (image_data_storage_formats[j]) { |
+ case kUint8ClampedArrayStorageFormat: |
+ data_array = static_cast<DOMArrayBufferView*>(data_u8); |
+ color_settings.setStorageFormat(kUint8ClampedArrayStorageFormatName); |
+ break; |
+ case kUint16ArrayStorageFormat: |
+ data_array = static_cast<DOMArrayBufferView*>(data_u16); |
+ color_settings.setStorageFormat(kUint16ArrayStorageFormatName); |
+ break; |
+ case kFloat32ArrayStorageFormat: |
+ data_array = static_cast<DOMArrayBufferView*>(data_f32); |
+ color_settings.setStorageFormat(kFloat32ArrayStorageFormatName); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ |
+ image_data = |
+ ImageData::CreateForTest(IntSize(2, 2), data_array, &color_settings); |
+ |
+ unsigned k = (unsigned)(canvas_colorspace_setting); |
+ // Convert the original data used to create ImageData to the |
+ // canvas color space and canvas pixel format. |
+ EXPECT_TRUE(ConvertPixelsToColorSpaceAndPixelFormatForTest( |
+ data_array, image_data_color_spaces[i], canvas_color_spaces[k], |
+ canvas_pixel_formats[k], pixels_converted_manually)); |
+ |
+ // Create a canvas and call putImageData and getImageData to make sure |
+ // the conversion is done correctly. |
+ CanvasContextCreationAttributes attributes; |
+ attributes.setAlpha(true); |
+ attributes.setColorSpace(canvas_color_space_names[k]); |
+ attributes.setPixelFormat(canvas_pixel_format_names[k]); |
+ CanvasRenderingContext2D* context = |
+ static_cast<CanvasRenderingContext2D*>( |
+ canvas_element.GetCanvasRenderingContext("2d", attributes)); |
+ NonThrowableExceptionState exception_state; |
+ context->putImageData(image_data, 0, 0, exception_state); |
+ |
+ void* pixels_from_get_image_data = nullptr; |
+ if (canvas_pixel_formats[k] == kRGBA8CanvasPixelFormat) { |
+ pixels_from_get_image_data = |
+ context->getImageData(0, 0, 2, 2, exception_state)->data()->Data(); |
+ // Swizzle if needed |
+ if (kN32_SkColorType == kBGRA_8888_SkColorType) { |
+ SkSwapRB(static_cast<uint32_t*>(pixels_from_get_image_data), |
+ static_cast<uint32_t*>(pixels_from_get_image_data), |
+ data_length / 4); |
+ } |
+ |
+ unsigned char* cpixels1 = |
+ static_cast<unsigned char*>(pixels_converted_manually.get()); |
+ unsigned char* cpixels2 = |
+ static_cast<unsigned char*>(pixels_from_get_image_data); |
+ for (unsigned m = 0; m < data_length; m++) { |
+ if (abs(cpixels1[m] - cpixels2[m]) > color_tolerance) |
+ test_passed = false; |
+ } |
+ } else { |
+ pixels_from_get_image_data = |
+ context->getImageData(0, 0, 2, 2, exception_state) |
+ ->dataUnion() |
+ .getAsFloat32Array() |
+ .View() |
+ ->Data(); |
+ float* fpixels1 = nullptr; |
+ float* fpixels2 = nullptr; |
+ void* vpointer = pixels_converted_manually.get(); |
+ fpixels1 = static_cast<float*>(vpointer); |
+ fpixels2 = static_cast<float*>(pixels_from_get_image_data); |
+ for (unsigned m = 0; m < data_length; m++) { |
+ if (fpixels1[m] < 0) |
+ fpixels1[m] = 0; |
+ if (fabs(fpixels1[m] - fpixels2[m]) > color_tolerance) { |
+ test_passed = false; |
+ } |
+ } |
+ |
+ ASSERT_TRUE(test_passed); |
+ } |
+ } |
+ } |
+ delete[] u16_pixels; |
+ delete[] f32_pixels; |
+ |
+ RuntimeEnabledFeatures::setExperimentalCanvasFeaturesEnabled( |
+ experimental_canvas_features_runtime_flag); |
+ RuntimeEnabledFeatures::setColorCorrectRenderingEnabled( |
+ color_correct_rendering_runtime_flag); |
+} |
+ |
+TEST_F(CanvasRenderingContext2DTest, ColorManagedPutImageDataOnSRGBCanvas) { |
+ TestPutImageDataOnCanvasWithColorSpaceSettings( |
+ CanvasElement(), CanvasColorSpaceSettings::CANVAS_SRGB, 0); |
+} |
+ |
+TEST_F(CanvasRenderingContext2DTest, |
+ ColorManagedPutImageDataOnLinearSRGBCanvas) { |
+ TestPutImageDataOnCanvasWithColorSpaceSettings( |
+ CanvasElement(), CanvasColorSpaceSettings::CANVAS_LINEARSRGB, 0.15); |
+} |
+ |
+TEST_F(CanvasRenderingContext2DTest, ColorManagedPutImageDataOnRec2020Canvas) { |
+ TestPutImageDataOnCanvasWithColorSpaceSettings( |
+ CanvasElement(), CanvasColorSpaceSettings::CANVAS_REC2020, 0.1); |
+} |
+ |
+TEST_F(CanvasRenderingContext2DTest, ColorManagedPutImageDataOnP3Canvas) { |
+ TestPutImageDataOnCanvasWithColorSpaceSettings( |
+ CanvasElement(), CanvasColorSpaceSettings::CANVAS_P3, 0.1); |
+} |
+ |
void OverrideScriptEnabled(Settings& settings) { |
// Simulate that we allow scripts, so that HTMLCanvasElement uses |
// LayoutHTMLCanvas. |