haikuwebkit/LayoutTests/fast/canvas/canvas-color-space-display-...

291 lines
12 KiB
HTML
Raw Permalink Normal View History

Add support for creating/accessing/setting non-sRGB ImageData via canvas https://bugs.webkit.org/show_bug.cgi?id=225841 Reviewed by Darin Adler. LayoutTests/imported/w3c: * web-platform-tests/html/canvas/element/wide-gamut-canvas/2d.color.space.p3.to.p3-expected.txt: Update result to passing. Source/WebCore: Test: fast/canvas/canvas-color-space-display-p3-ImageData.html Add support for accessing non-sRGB (only DisplayP3 for now due to the specification, but the support is general) pixel data in HTML canvas. Updates ImageData constructors and CanvasImageData operations to take optional ImageDataSettings dictionaries, which contain an optional color space (otherwise defaulting back to sRGB). * CMakeLists.txt: * DerivedSources-input.xcfilelist: * DerivedSources-output.xcfilelist: * DerivedSources.make: * Sources.txt: * WebCore.xcodeproj/project.pbxproj: * html/ImageDataSettings.h: Added. * html/ImageDataSettings.idl: Added. Add new ImageDataSettings.idl and related files. * bindings/js/SerializedScriptValue.cpp: (WebCore::CloneDeserializer::readImageBitmap): Fixes FIXME and uses PixelBuffer directly rather than allocating an unnecessary ImageData. This was done now since the relevent ImageData constructor has gone away. * html/ImageData.cpp: (WebCore::computeDataSize): (WebCore::ImageData::computeColorSpace): (WebCore::ImageData::create): (WebCore::ImageData::createUninitialized): (WebCore::ImageData::ImageData): (WebCore::ImageData::pixelBuffer const): (WebCore::ImageData::dataSize): Deleted. (WebCore::ImageData::deepClone const): Deleted. * html/ImageData.h: (WebCore::ImageData::size const): (WebCore::ImageData::width const): (WebCore::ImageData::height const): (WebCore::ImageData::data const): (WebCore::ImageData::colorSpace const): (WebCore::ImageData::pixelBuffer const): Deleted. - Reworked ImageData to no longer store a PixelBuffer, which has extraneous information in it, but rather to store just what it needs IntSize, Ref<JSC::Uint8ClampedArray>, and now PredefinedColorSpace. - Updates create functions for new optional ImageDataSettings. - Adds createUninitialized which follows spec language for ImageData creation and is used by CanvasRenderingContext2D to create ImageData objects of with the right color spaces, allowing for fallback to the canvas' own color space when no ImageDataSettings color space is provided. It is uninitialized and therefore requires the client to initialize the data to allow for support for no alpha support in the future, which requires a non-zero initialization pattern. * html/ImageData.idl: Add optional ImageDataSettings parameters and the new colorSpace attribute. * html/canvas/CanvasImageData.idl: Add optional ImageDataSettings parameters. * html/canvas/CanvasRenderingContext2DBase.h: * html/canvas/CanvasRenderingContext2DBase.cpp: (WebCore::initializeEmptyImageData): Add helper to initialize the ImageData buffer. Right now it always calls zeroFill(), but in the future it will need to do more. (WebCore::CanvasRenderingContext2DBase::createImageData const): Update to account for this function being able to throw an exception (when out of memory) and use the new createUninitialized/initializeEmptyImageData to create a correctly color spaced ImageData. (WebCore::CanvasRenderingContext2DBase::createImageData const): Update for new optional ImageDataSettings and use the new createUninitialized initializeEmptyImageData to create a correctly color spaced ImageData. (WebCore::CanvasRenderingContext2DBase::getImageData const): Moves parameter checks to the begining to match the spec, and uses new createUninitialized/initializeEmptyImageData to create a correctly color spaced ImageData. Also, use the ImageData's color space when getting the pixel buffer to actually return the right data! * html/canvas/PredefinedColorSpace.cpp: (WebCore::toPredefinedColorSpace): * html/canvas/PredefinedColorSpace.h: Add conversion function from DestinationColorSpace to PredefinedColorSpace. Since DestinationColorSpace is a superset of PredefinedColorSpace, this can fail, so this conversion returns an Optional. * inspector/InspectorCanvas.cpp: * inspector/InspectorCanvasCallTracer.cpp: * inspector/InspectorCanvasCallTracer.h: Stub out inspector support for ImageDataSettings. * platform/graphics/ImageBufferBackend.cpp: (WebCore::ImageBufferBackend::getPixelBuffer const): Use the ImageBuffer's actual color space as the source color space rather than hard coding sRGB. This allows the color space conversion to take place. Also remove some unnecessary temporary variables. (WebCore::ImageBufferBackend::putPixelBuffer): Use the ImageBuffer's actual color space as the destination color space rather than hard coding sRGB. This allows the color space conversion to take place. Also remove some unnecessary temporary variables. * platform/graphics/PixelBuffer.cpp: (WebCore::PixelBuffer::tryCreate): (WebCore::PixelBuffer::PixelBuffer): * platform/graphics/PixelBuffer.h: (WebCore::PixelBuffer::takeData): Add a few helpers to allow creationg to/from PixelBuffer a bit easier. * platform/graphics/PixelBufferConversion.cpp: (WebCore::convertImagePixelsAccelerated): Fix incorrect assertion. We want to assert that there is no error, not that there is one. * platform/graphics/filters/FilterEffect.cpp: (WebCore::FilterEffect::convertImageBufferToColorSpace): (WebCore::FilterEffect::copyUnmultipliedResult): (WebCore::FilterEffect::copyPremultipliedResult): (WebCore::FilterEffect::createUnmultipliedImageResult): (WebCore::FilterEffect::createPremultipliedImageResult): Stop hard coding SRGB for PixelBuffer color spaces and use the appropriate color space for the task. We still do color space conversion through ImageBuffer so we should come back and simplify code here to not always require that. * testing/Internals.cpp: (WebCore::Internals::videoSampleAvailable): (WebCore::Internals::loadArtworkImage): Update to specify a color space to maintain existing behavior. Source/WebKitLegacy/win: Add support for tests enabling the CanvasColorSpaceEnabled preference. * WebPreferences.cpp: (WebPreferences::canvasColorSpaceEnabled): * WebPreferences.h: * WebView.cpp: (WebView::notifyPreferencesChanged): LayoutTests: * TestExpectations: Remove wide-gamut-canvas now that they should pass. * fast/canvas/canvas-color-space-display-p3-ImageData-expected.txt: Added. * fast/canvas/canvas-color-space-display-p3-ImageData.html: Added. Add new test exercising getImageData and putImageData with non-sRGB canvases and non-sRGB ImageData. * fast/canvas/canvas-imageData-expected.txt: Update results for updated error messages, which are a bit worse due additional ambiguity in signatures. * platform/glib/TestExpectations: Mark new test as failing on glib due to lack of display-p3 support. * platform/win/TestExpectations: Mark new test as failing on Windows due to lack of display-p3 support. Unskip CanvasRenderingContext2DSettings-colorSpace-enabled.html which should now pass due to adding manual preferences support in WebKitLegacy/win. * storage/indexeddb/modern/objectstore-autoincrement-types-expected.txt: Update results due to new attribute in ImageData that is auto printed. Canonical link: https://commits.webkit.org/237797@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@277569 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2021-05-16 15:21:34 +00:00
<!DOCTYPE html>
<html>
<head>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Test that pixel access functions work with non-SRGB color spaces.");
let canvasDisplayP3 = document.createElement("canvas");
let contextDisplayP3 = canvasDisplayP3.getContext("2d", { colorSpace: "display-p3" });
let canvasSRGB = document.createElement("canvas");
let contextSRGB = canvasSRGB.getContext("2d", { colorSpace: "srgb" });
const imageDataCreatedFromDisplayP3Canvas = contextDisplayP3.createImageData(50, 50);
shouldBe("imageDataCreatedFromDisplayP3Canvas.colorSpace", `"display-p3"`);
const imageDataCreatedFromDisplayP3CanvasOverridingColorSpaceSRGB = contextDisplayP3.createImageData(50, 50, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromDisplayP3CanvasOverridingColorSpaceSRGB.colorSpace", `"srgb"`);
const imageDataCreatedFromDisplayP3CanvasOverridingColorSpaceDisplayP3 = contextDisplayP3.createImageData(50, 50, { colorSpace: "display-p3" });
shouldBe("imageDataCreatedFromDisplayP3CanvasOverridingColorSpaceDisplayP3.colorSpace", `"display-p3"`);
const imageDataCreatedFromSRGBCanvas = contextSRGB.createImageData(50, 50);
shouldBe("imageDataCreatedFromSRGBCanvas.colorSpace", `"srgb"`);
const imageDataCreatedFromSRGBCanvasOverridingColorSpaceSRGB = contextSRGB.createImageData(50, 50, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromSRGBCanvasOverridingColorSpaceSRGB.colorSpace", `"srgb"`);
const imageDataCreatedFromSRGBCanvasOverridingColorSpaceDisplayP3 = contextSRGB.createImageData(50, 50, { colorSpace: "display-p3" });
shouldBe("imageDataCreatedFromSRGBCanvasOverridingColorSpaceDisplayP3.colorSpace", `"display-p3"`);
const imageDataCreatedFromImageDataConstructor = new ImageData(50, 50, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromImageDataConstructor.colorSpace", `"srgb"`);
const imageDataCreatedFromImageDataConstructorSettingColorSpaceToSRGB = new ImageData(50, 50, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromImageDataConstructorSettingColorSpaceToSRGB.colorSpace", `"srgb"`);
const imageDataCreatedFromImageDataConstructorSettingColorSpaceToDisplayP3 = new ImageData(50, 50, { colorSpace: "display-p3" });
shouldBe("imageDataCreatedFromImageDataConstructorSettingColorSpaceToDisplayP3.colorSpace", `"display-p3"`);
const imageDataCreatedFromImageDataConstructorPassingBuffer = new ImageData(new Uint8ClampedArray(400), 10, 10, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromImageDataConstructorPassingBuffer.colorSpace", `"srgb"`);
const imageDataCreatedFromImageDataConstructorPassingBufferSettingColorSpaceToSRGB = new ImageData(new Uint8ClampedArray(400), 10, 10, { colorSpace: "srgb" });
shouldBe("imageDataCreatedFromImageDataConstructorPassingBufferSettingColorSpaceToSRGB.colorSpace", `"srgb"`);
const imageDataCreatedFromImageDataConstructorPassingBufferSettingColorSpaceToDisplayP3 = new ImageData(new Uint8ClampedArray(400), 10, 10, { colorSpace: "display-p3" });
shouldBe("imageDataCreatedFromImageDataConstructorPassingBufferSettingColorSpaceToDisplayP3.colorSpace", `"display-p3"`);
var data = { };
// NOTE:
// color(srgb 0 1 0) converted to display-p3 is color(display-p3 0.4584 0.98526 0.29829) or [117, 251, 76, 255] in byte form.
debug("");
debug("Testing a display-p3 canvas with color(display-p3 0 1 0) drawn into it");
debug("");
contextDisplayP3.fillStyle = "color(display-p3 0 1 0)";
contextDisplayP3.fillRect(0, 0, 50, 50);
// No specified color space will default to the canvas' color space, in this case, display-p3
debug("Test getImageData with no specified color space, on a display-p3 canvas (canvas has color(display-p3 0 1 0) drawn in it)");
data = contextDisplayP3.getImageData(0, 0, 1, 1);
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with srgb specified, on a display-p3 canvas (canvas has color(display-p3 0 1 0) drawn in it)");
// NOTE: color(display-p3 0 1 0) is outside the range of sRGB, so it clipped to 255.
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with display-p3 specified, on a display-p3 canvas (canvas has color(display-p3 0 1 0) drawn in it)");
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a display-p3 canvas with color(srgb 0 1 0) drawn into it");
debug("");
contextDisplayP3.fillStyle = "color(srgb 0 1 0)";
contextDisplayP3.fillRect(0, 0, 50, 50);
// No specified color space will default to the canvas' color space, in this case, display-p3
debug("Test getImageData with no specified color space, on a display-p3 canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextDisplayP3.getImageData(0, 0, 1, 1);
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("Test getImageData with srgb specified, on a display-p3 canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
// NOTE: This 3 is odd, but due to lack of precision in 8-bit round-tripping.
shouldBe("data.data[0]", "3");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with display-p3 specified, on a display-p3 canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a srgb canvas with color(display-p3 0 1 0) drawn into it");
debug("");
contextSRGB.fillStyle = "color(display-p3 0 1 0)";
contextSRGB.fillRect(0, 0, 50, 50);
// No specified color space will default to the canvas' color space, in this case, srgb
debug("Test getImageData with no specified color space, on a srgb canvas (canvas has color(display-p3 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1);
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with srgb specified, on a srgb canvas (canvas has color(display-p3 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with display-p3 specified, on a srgb canvas (canvas has color(display-p3 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a srgb canvas with color(srgb-p3 0 1 0) drawn into it");
debug("");
contextSRGB.fillStyle = "color(srgb 0 1 0)";
contextSRGB.fillRect(0, 0, 50, 50);
// No specified color space will default to the canvas' color space, in this case, srgb
debug("Test getImageData with no specified color space, on a srgb canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1);
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with srgb specified, on a srgb canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("Test getImageData with display-p3 specified, on a srgb canvas (canvas has color(srgb 0 1 0) drawn in it)");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
const imageDataDisplayP3ToPut = new ImageData(1, 1, { colorSpace: "display-p3" });
imageDataDisplayP3ToPut.data[0] = 0;
imageDataDisplayP3ToPut.data[1] = 255;
imageDataDisplayP3ToPut.data[2] = 0;
imageDataDisplayP3ToPut.data[3] = 255;
const imageDataSRGBToPut = new ImageData(1, 1, { colorSpace: "srgb" });
imageDataSRGBToPut.data[0] = 0;
imageDataSRGBToPut.data[1] = 255;
imageDataSRGBToPut.data[2] = 0;
imageDataSRGBToPut.data[3] = 255;
debug("");
debug("Testing a display-p3 canvas with display-p3 ImageData drawn into it");
debug("");
contextDisplayP3.putImageData(imageDataDisplayP3ToPut, 0, 0);
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a display-p3 canvas with srgb ImageData drawn into it");
debug("");
contextDisplayP3.putImageData(imageDataSRGBToPut, 0, 0);
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
// NOTE: This 3 is odd, but due to lack of precision in 8-bit round-tripping.
shouldBe("data.data[0]", "3");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
data = contextDisplayP3.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a srgb canvas with display-p3 ImageData drawn into it");
debug("");
contextSRGB.putImageData(imageDataDisplayP3ToPut, 0, 0);
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
debug("Testing a srgb canvas with srgb ImageData drawn into it");
debug("");
contextSRGB.putImageData(imageDataSRGBToPut, 0, 0);
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
shouldBe("data.colorSpace", `"srgb"`)
shouldBe("data.data[0]", "0");
shouldBe("data.data[1]", "255");
shouldBe("data.data[2]", "0");
shouldBe("data.data[3]", "255");
data = contextSRGB.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
shouldBe("data.colorSpace", `"display-p3"`)
shouldBe("data.data[0]", "117");
shouldBe("data.data[1]", "251");
shouldBe("data.data[2]", "76");
shouldBe("data.data[3]", "255");
debug("");
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>