Commit 9c361598 authored by Christopher Cameron's avatar Christopher Cameron Committed by Chromium LUCI CQ

ImageData: Add constructors for canvas color extension

Add new constructors for ImageData. These come in two flavors: ones
that take an optional ImageDataSettings, and ones that take an array
that isn't Uint8ClampedArray.

Move all ImageData::Create instances to be in the header file, since
they are one line long now, and there are many more of them (all
optional arguments are expanded into several separate Create functions).

Because IDL files do not allow runtime feature checks in constructors,
add a new parameter for ImageData::ValidateAndCreate to add this check.

Add tests for all of these configurations.

R=yiyix
TBR=fserb

Bug: 1160105
Change-Id: Ib4392cd1ace7ff52789083c72b771eb50228f9c4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2605629Reviewed-by: default avatarccameron <ccameron@chromium.org>
Reviewed-by: default avatarYi Xu <yiyix@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#839893}
parent 671c144b
...@@ -42,10 +42,16 @@ ImageData* ImageData::ValidateAndCreate( ...@@ -42,10 +42,16 @@ ImageData* ImageData::ValidateAndCreate(
unsigned width, unsigned width,
base::Optional<unsigned> height, base::Optional<unsigned> height,
base::Optional<NotShared<DOMArrayBufferView>> data, base::Optional<NotShared<DOMArrayBufferView>> data,
const ImageDataSettings* settings, const ImageDataSettings* input_settings,
ExceptionState& exception_state, ExceptionState& exception_state,
uint32_t flags) { uint32_t flags) {
IntSize size; IntSize size;
if ((flags & RequireCanvasColorManagement &&
!RuntimeEnabledFeatures::CanvasColorManagementEnabled())) {
exception_state.ThrowTypeError("Overload resolution failed.");
return nullptr;
}
if (!width) { if (!width) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError, DOMExceptionCode::kIndexSizeError,
...@@ -63,6 +69,13 @@ ImageData* ImageData::ValidateAndCreate( ...@@ -63,6 +69,13 @@ ImageData* ImageData::ValidateAndCreate(
size.SetHeight(*height); size.SetHeight(*height);
} }
// Populate the ImageDataSettings to use based on |input_settings|.
ImageDataSettings* settings = ImageDataSettings::Create();
if (input_settings) {
settings->setColorSpace(input_settings->colorSpace());
settings->setStorageFormat(input_settings->storageFormat());
}
// Ensure the size does not overflow. // Ensure the size does not overflow.
unsigned size_in_elements = 0; unsigned size_in_elements = 0;
{ {
...@@ -88,18 +101,25 @@ ImageData* ImageData::ValidateAndCreate( ...@@ -88,18 +101,25 @@ ImageData* ImageData::ValidateAndCreate(
} }
// If |data| is provided, ensure it is a reasonable format, and that it can // If |data| is provided, ensure it is a reasonable format, and that it can
// work with |size|. // work with |size|. Update |settings| to reflect |data|'s format.
if (data) { if (data) {
DCHECK(data); DCHECK(data);
if ((*data)->GetType() != DOMArrayBufferView::ViewType::kTypeUint8Clamped && switch ((*data)->GetType()) {
(*data)->GetType() != DOMArrayBufferView::ViewType::kTypeUint16 && case DOMArrayBufferView::ViewType::kTypeUint8Clamped:
(*data)->GetType() != DOMArrayBufferView::ViewType::kTypeFloat32) { settings->setStorageFormat(kUint8ClampedArrayStorageFormatName);
exception_state.ThrowDOMException( break;
DOMExceptionCode::kNotSupportedError, case DOMArrayBufferView::ViewType::kTypeUint16:
"The input data type is not supported."); settings->setStorageFormat(kUint16ArrayStorageFormatName);
return nullptr; break;
case DOMArrayBufferView::ViewType::kTypeFloat32:
settings->setStorageFormat(kFloat32ArrayStorageFormatName);
break;
default:
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"The input data type is not supported.");
return nullptr;
} }
static_assert( static_assert(
std::numeric_limits<unsigned>::max() >= std::numeric_limits<unsigned>::max() >=
std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::max(),
...@@ -153,8 +173,7 @@ ImageData* ImageData::ValidateAndCreate( ...@@ -153,8 +173,7 @@ ImageData* ImageData::ValidateAndCreate(
NotShared<DOMArrayBufferView> allocated_data; NotShared<DOMArrayBufferView> allocated_data;
if (!data) { if (!data) {
ImageDataStorageFormat storage_format = ImageDataStorageFormat storage_format =
settings ? GetImageDataStorageFormat(settings->storageFormat()) GetImageDataStorageFormat(settings->storageFormat());
: kUint8ClampedArrayStorageFormat;
allocated_data = AllocateAndValidateDataArray( allocated_data = AllocateAndValidateDataArray(
size_in_elements, storage_format, &exception_state); size_in_elements, storage_format, &exception_state);
if (!allocated_data) if (!allocated_data)
...@@ -202,35 +221,6 @@ NotShared<DOMArrayBufferView> ImageData::AllocateAndValidateDataArray( ...@@ -202,35 +221,6 @@ NotShared<DOMArrayBufferView> ImageData::AllocateAndValidateDataArray(
return data_array; return data_array;
} }
ImageData* ImageData::Create(unsigned width,
unsigned height,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, base::nullopt, nullptr,
exception_state);
}
ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data,
unsigned width,
ExceptionState& exception_state) {
return ValidateAndCreate(width, base::nullopt, data, nullptr,
exception_state);
}
ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data,
unsigned width,
unsigned height,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, nullptr, exception_state);
}
ImageData* ImageData::Create(unsigned width,
unsigned height,
const ImageDataSettings* settings,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, base::nullopt, settings,
exception_state);
}
// This function accepts size (0, 0) and always returns the ImageData in // This function accepts size (0, 0) and always returns the ImageData in
// "srgb" color space and "uint8" storage format. // "srgb" color space and "uint8" storage format.
ImageData* ImageData::CreateForTest(const IntSize& size) { ImageData* ImageData::CreateForTest(const IntSize& size) {
...@@ -423,8 +413,8 @@ ImageData::ImageData(const IntSize& size, ...@@ -423,8 +413,8 @@ ImageData::ImageData(const IntSize& size,
GetImageDataStorageFormat(settings_->storageFormat()); GetImageDataStorageFormat(settings_->storageFormat());
switch (storage_format) { switch (storage_format) {
case kUint8ClampedArrayStorageFormat: case kUint8ClampedArrayStorageFormat:
DCHECK(data->GetType() == DCHECK_EQ(data->GetType(),
DOMArrayBufferView::ViewType::kTypeUint8Clamped); DOMArrayBufferView::ViewType::kTypeUint8Clamped);
data_u8_ = data; data_u8_ = data;
DCHECK(data_u8_); DCHECK(data_u8_);
data_.SetUint8ClampedArray(data_u8_); data_.SetUint8ClampedArray(data_u8_);
...@@ -434,7 +424,7 @@ ImageData::ImageData(const IntSize& size, ...@@ -434,7 +424,7 @@ ImageData::ImageData(const IntSize& size,
break; break;
case kUint16ArrayStorageFormat: case kUint16ArrayStorageFormat:
DCHECK(data->GetType() == DOMArrayBufferView::ViewType::kTypeUint16); DCHECK_EQ(data->GetType(), DOMArrayBufferView::ViewType::kTypeUint16);
data_u16_ = data; data_u16_ = data;
DCHECK(data_u16_); DCHECK(data_u16_);
data_.SetUint16Array(data_u16_); data_.SetUint16Array(data_u16_);
...@@ -444,7 +434,7 @@ ImageData::ImageData(const IntSize& size, ...@@ -444,7 +434,7 @@ ImageData::ImageData(const IntSize& size,
break; break;
case kFloat32ArrayStorageFormat: case kFloat32ArrayStorageFormat:
DCHECK(data->GetType() == DOMArrayBufferView::ViewType::kTypeFloat32); DCHECK_EQ(data->GetType(), DOMArrayBufferView::ViewType::kTypeFloat32);
data_f32_ = data; data_f32_ = data;
DCHECK(data_f32_); DCHECK(data_f32_);
data_.SetFloat32Array(data_f32_); data_.SetFloat32Array(data_f32_);
......
...@@ -62,20 +62,95 @@ class CORE_EXPORT ImageData final : public ScriptWrappable, ...@@ -62,20 +62,95 @@ class CORE_EXPORT ImageData final : public ScriptWrappable,
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
static ImageData* Create(unsigned width, unsigned height, ExceptionState&); // Constructor that takes width, height, and an optional ImageDataSettings.
static ImageData* Create(NotShared<DOMUint8ClampedArray>, static ImageData* Create(unsigned width,
unsigned height,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, base::nullopt, nullptr,
exception_state);
}
static ImageData* Create(unsigned width,
unsigned height,
const ImageDataSettings* settings,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, base::nullopt, settings,
exception_state, RequireCanvasColorManagement);
}
// Constructor that takes Uint8ClampedArray, width, optional height, and
// optional ImageDataSettings.
static ImageData* Create(NotShared<DOMUint8ClampedArray> data,
unsigned width, unsigned width,
ExceptionState&); ExceptionState& exception_state) {
static ImageData* Create(NotShared<DOMUint8ClampedArray>, return ValidateAndCreate(width, base::nullopt, data, nullptr,
exception_state);
}
static ImageData* Create(NotShared<DOMUint8ClampedArray> data,
unsigned width, unsigned width,
unsigned height, unsigned height,
ExceptionState&); ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, nullptr, exception_state);
static ImageData* Create(unsigned width, }
static ImageData* Create(NotShared<DOMUint8ClampedArray> data,
unsigned width,
unsigned height, unsigned height,
const ImageDataSettings*, const ImageDataSettings* settings,
ExceptionState&); ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, settings, exception_state,
RequireCanvasColorManagement);
}
// Constructor that takes DOMUint16Array, width, optional height, and optional
// ImageDataSettings.
static ImageData* Create(NotShared<DOMUint16Array> data,
unsigned width,
ExceptionState& exception_state) {
return ValidateAndCreate(width, base::nullopt, data, nullptr,
exception_state, RequireCanvasColorManagement);
}
static ImageData* Create(NotShared<DOMUint16Array> data,
unsigned width,
unsigned height,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, nullptr, exception_state,
RequireCanvasColorManagement);
}
static ImageData* Create(NotShared<DOMUint16Array> data,
unsigned width,
unsigned height,
const ImageDataSettings* settings,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, settings, exception_state,
RequireCanvasColorManagement);
}
// Constructor that takes DOMFloat32Array, width, optional height, and
// optional ImageDataSettings.
static ImageData* Create(NotShared<DOMFloat32Array> data,
unsigned width,
ExceptionState& exception_state) {
return ValidateAndCreate(width, base::nullopt, data, nullptr,
exception_state, RequireCanvasColorManagement);
}
static ImageData* Create(NotShared<DOMFloat32Array> data,
unsigned width,
unsigned height,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, nullptr, exception_state,
RequireCanvasColorManagement);
}
static ImageData* Create(NotShared<DOMFloat32Array> data,
unsigned width,
unsigned height,
const ImageDataSettings* settings,
ExceptionState& exception_state) {
return ValidateAndCreate(width, height, data, settings, exception_state,
RequireCanvasColorManagement);
}
// ValidateAndCreate is the common path that all ImageData creation code
// should call directly. The other Create functions are to be called only by
// generated code.
enum ValidateAndCreateFlags { enum ValidateAndCreateFlags {
None = 0x0, None = 0x0,
// When a too-large ImageData is created using a constructor, it has // When a too-large ImageData is created using a constructor, it has
...@@ -83,6 +158,10 @@ class CORE_EXPORT ImageData final : public ScriptWrappable, ...@@ -83,6 +158,10 @@ class CORE_EXPORT ImageData final : public ScriptWrappable,
// canvas, it has historically thrown a RangeError. This flag will // canvas, it has historically thrown a RangeError. This flag will
// trigger the RangeError path. // trigger the RangeError path.
Context2DErrorMode = 0x1, Context2DErrorMode = 0x1,
// Constructors in IDL files cannot specify RuntimeEnabled restrictions.
// This argument is passed by Create functions that should require that the
// CanvasColorManagement feature be enabled.
RequireCanvasColorManagement = 0x2,
}; };
static ImageData* ValidateAndCreate( static ImageData* ValidateAndCreate(
unsigned width, unsigned width,
......
...@@ -35,8 +35,10 @@ typedef (Uint8ClampedArray or Uint16Array or Float32Array) ImageDataArray; ...@@ -35,8 +35,10 @@ typedef (Uint8ClampedArray or Uint16Array or Float32Array) ImageDataArray;
Exposed=(Window,Worker), Exposed=(Window,Worker),
Serializable Serializable
] interface ImageData { ] interface ImageData {
[RaisesException] constructor(unsigned long sw, unsigned long sh); [RaisesException] constructor(unsigned long sw, unsigned long sh, optional ImageDataSettings settings);
[RaisesException] constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh); [RaisesException] constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh, optional ImageDataSettings settings);
[RaisesException] constructor(Uint16Array data, unsigned long sw, optional unsigned long sh, optional ImageDataSettings settings);
[RaisesException] constructor(Float32Array data, unsigned long sw, optional unsigned long sh, optional ImageDataSettings settings);
[RuntimeEnabled=CanvasColorManagement] ImageDataSettings getSettings(); [RuntimeEnabled=CanvasColorManagement] ImageDataSettings getSettings();
readonly attribute unsigned long width; readonly attribute unsigned long width;
......
...@@ -22,8 +22,8 @@ PASS [Worker] new ImageData(new Uint8ClampedArray(27), 2) threw exception Invali ...@@ -22,8 +22,8 @@ PASS [Worker] new ImageData(new Uint8ClampedArray(27), 2) threw exception Invali
PASS [Worker] new ImageData(new Uint8ClampedArray(28), 7, 0) threw exception IndexSizeError: Failed to construct 'ImageData': The source height is zero or not a number.. PASS [Worker] new ImageData(new Uint8ClampedArray(28), 7, 0) threw exception IndexSizeError: Failed to construct 'ImageData': The source height is zero or not a number..
PASS [Worker] new ImageData(new Uint8ClampedArray(104), 14) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of (4 * width).. PASS [Worker] new ImageData(new Uint8ClampedArray(104), 14) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of (4 * width)..
PASS [Worker] new ImageData(new Uint8ClampedArray([12, 34, 168, 65328]), 1, 151) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not equal to (4 * width * height).. PASS [Worker] new ImageData(new Uint8ClampedArray([12, 34, 168, 65328]), 1, 151) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not equal to (4 * width * height)..
PASS [Worker] new ImageData(self, 4, 4) threw exception TypeError: Failed to construct 'ImageData': parameter 1 is not of type 'Uint8ClampedArray'.. PASS [Worker] new ImageData(self, 4, 4) threw exception TypeError: Failed to construct 'ImageData': cannot convert to dictionary..
PASS [Worker] new ImageData(null, 4, 4) threw exception TypeError: Failed to construct 'ImageData': parameter 1 is not of type 'Uint8ClampedArray'.. PASS [Worker] new ImageData(null, 4, 4) threw exception TypeError: Failed to construct 'ImageData': cannot convert to dictionary..
PASS [Worker] new ImageData(imageData.data, 0) threw exception IndexSizeError: Failed to construct 'ImageData': The source width is zero or not a number.. PASS [Worker] new ImageData(imageData.data, 0) threw exception IndexSizeError: Failed to construct 'ImageData': The source width is zero or not a number..
PASS [Worker] new ImageData(imageData.data, 13) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of (4 * width).. PASS [Worker] new ImageData(imageData.data, 13) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of (4 * width)..
PASS [Worker] new ImageData(imageData.data, 1 << 31) threw exception IndexSizeError: Failed to construct 'ImageData': The requested image size exceeds the supported range.. PASS [Worker] new ImageData(imageData.data, 1 << 31) threw exception IndexSizeError: Failed to construct 'ImageData': The requested image size exceeds the supported range..
......
<!DOCTYPE html>
<body>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script>
test(drawThenGetImageData,
'tests creating ImageData using several methods, calling putImageData, and reading back the color.');
function populate_array(data, data_color_space, data_storage_format) {
for (var i = 0; i < data.length / 4; ++i) {
var color_srgb_f32 = [50/255., 100/255., 150/255., 1.0];
var color_p3_f32 = [0.24304, 0.38818, 0.57227, 1.0];
var color_rec2020_f32 = [0.34106, 0.41553, 0.59180, 1.0];
if (data_color_space == 'srgb')
data_color = color_srgb_f32;
else if (data_color_space == 'display-p3')
data_color = color_p3_f32;
else if (data_color_space == 'rec2020')
data_color = color_rec2020_f32;
var multiplier = 1;
if (data_storage_format == 'uint8')
multiplier = 255;
if (data_storage_format == 'uint16')
multiplier = 65535;
if (data_storage_format == 'float32')
multiplier = 1.0;
data[4*i + 0] = multiplier * data_color[0];
data[4*i + 1] = multiplier * data_color[1];
data[4*i + 2] = multiplier * data_color[2];
data[4*i + 3] = multiplier * data_color[3];
}
}
function run_test(canvas_color_space, canvas_pixel_format, data_color_space, data_storage_format) {
var canvas = document.createElement('canvas');
canvas.width = 20;
canvas.height = 16;
var ctx = canvas.getContext('2d', {colorSpace:canvas_color_space, pixelFormat:canvas_pixel_format});
const num_subtests = 5;
const data_width = Math.floor(canvas.width / num_subtests);
const data_height = canvas.height;
subtest_name = '';
// Create an ImageData using createImageData and populate its data array.
{
var data_to_put = ctx.createImageData(data_width, data_height, {colorSpace:data_color_space, storageFormat:data_storage_format});
populate_array(data_to_put.data, data_color_space, data_storage_format);
ctx.putImageData(data_to_put, 0 * data_width, 0);
subtest_name = 'createImageData';
}
// Create an ImageData using a constructor and populate its data array.
{
var data_to_put = new ImageData(data_width, data_height, {colorSpace:data_color_space, storageFormat:data_storage_format});
populate_array(data_to_put.data, data_color_space, data_storage_format);
ctx.putImageData(data_to_put, 1 * data_width, 0);
subtest_name = 'constructor with width,height';
}
// Create and populate an array, then use that array to create an ImageData.
{
var length = 4 * data_width * data_height;
var data = [];
if (data_storage_format == 'uint8')
data = new Uint8ClampedArray(length);
if (data_storage_format == 'uint16')
data = new Uint16Array(length);
if (data_storage_format == 'float32')
data = new Float32Array(length);
populate_array(data, data_color_space, data_storage_format);
var data_to_put = new ImageData(data, data_width, data_height, {colorSpace:data_color_space, storageFormat:data_storage_format});
ctx.putImageData(data_to_put, 2 * data_width, 0);
subtest_name = 'constructor with data,width,height,settings';
}
// Create and populate an array, then use that array to create an ImageData
// without specifying the height. Note that this will also not specify the
// ImageDataSettings, so the color space will implicitly be sRGB.
{
var length = 4 * data_width * data_height;
var data = [];
if (data_storage_format == 'uint8')
data = new Uint8ClampedArray(length);
if (data_storage_format == 'uint16')
data = new Uint16Array(length);
if (data_storage_format == 'float32')
data = new Float32Array(length);
populate_array(data, 'srgb', data_storage_format);
var data_to_put = new ImageData(data, data_width);
ctx.putImageData(data_to_put, 3 * data_width, 0);
subtest_name = 'constructor with data,width';
}
// Create and populate an array, then use that array to create an ImageData
// specifying the height but not ImageDataSettings. Note that this will also
// not specify the ImageDataSettings, so the color space will implicitly be
// sRGB.
{
var length = 4 * data_width * data_height;
var data = [];
if (data_storage_format == 'uint8')
data = new Uint8ClampedArray(length);
if (data_storage_format == 'uint16')
data = new Uint16Array(length);
if (data_storage_format == 'float32')
data = new Float32Array(length);
populate_array(data, 'srgb', data_storage_format);
var data_to_put = new ImageData(data, data_width, data_height);
ctx.putImageData(data_to_put, 4 * data_width, 0);
subtest_name = 'constructor with data,width';
}
// Read back as 8-bit sRGB.
for (var j = 0; j < num_subtests; ++j) {
var data_from_get = ctx.getImageData(data_width * (j + 0.5), data_height / 2, 1, 1);
var passed = true;
var pixel_expected = [50, 100, 150, 255];
var pixel_actual = data_from_get.data;
for (var i = 0; i < 4; ++i) {
var epsilon = 2;
if (Math.abs(pixel_actual[i] - pixel_expected[i]) > epsilon)
passed = false;
}
if (!passed) {
console.log('Failure! ' + subtest_name);
console.log(' Canvas: ' + canvas_color_space + ',' + canvas_pixel_format);
console.log(' ImageData: ' + data_color_space + ',' + data_storage_format);
console.log(' Expected: ' + pixel_expected);
console.log(' Actual: ' + [pixel_actual[0], pixel_actual[1], pixel_actual[2], pixel_actual[3]]);
console.log(' Epsilon: ' + epsilon);
}
}
return passed;
}
function drawThenGetImageData() {
color_spaces = ['srgb', 'display-p3', 'rec2020'];
canvas_pixel_formats = ['uint8', 'float16'];
image_data_pixel_formats = ['uint8', 'uint16', 'float32'];
var passed = true;
for (var i = 0; i < 3; ++i) {
for (var j = 0; j < 2; ++j) {
for (var k = 0; k < 3; ++k) {
for (var l = 0; l < 3; ++l) {
if (!run_test(color_spaces[i],
canvas_pixel_formats[j],
color_spaces[k],
image_data_pixel_formats[l])) {
passed = false;
}
}
}
}
}
assert_true(passed);
}
</script>
</body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment