blob: 0e969fcca1060ba3cb73898dd697cd579cffef4b [file] [log] [blame]
<!DOCTYPE HTML>
<script src="../../../../../resources/testharness.js"></script>
<script src="../../../../../resources/testharnessreport.js"></script>
<script>
// This layout test examines the behavior of ImageData thoroughly to make
// sure it matches the behavior explained in the proposal and the respective
// bug (crbug.com/758669).
// The reference values are generated by calling SkImage::makeColorSpace()
// in a Skia fiddle as this is the API used in ImageBitmap for color conversion.
// SkColorSpaceXform() may generate slightly different results.
// Please see: https://fiddle.skia.org/c/94578944d9d52c2b4c2cadda88a4e204
var srgbPixels =
[[155,27,27,128, "srgbRed"], [27,155,27,128, "srgbGreen"],
[27,27,155,128, "srgbBlue"], [27,27,27,128, "srgbBlack"]];
var linearRgbPixels =
[[0.329346, 0.011765, 0.011765, 0.501953, "linearRgbRed"],
[0.011765, 0.329346, 0.011765, 0.501953, "linearRgbGreen"],
[0.011765, 0.011765, 0.329346, 0.501953, "linearRgbBlue"],
[0.011765, 0.011765, 0.011765, 0.501953, "linearRgbBlack"]];
var rec2020Pixels =
[[0.207764, 0.031372, 0.015686, 0.501953, "rec2020Red"],
[0.113708, 0.301758, 0.039215, 0.501953, "rec2020Green"],
[0.023529, 0.015686, 0.293945, 0.501953, "rec2020Blue"],
[0.011765, 0.011765, 0.011765, 0.501953, "rec2020Black"]];
var p3Pixels =
[[0.270508, 0.019608, 0.015686, 0.501953, "p3Red"],
[0.066650, 0.317627, 0.035278, 0.501953, "p3Green"],
[0.011765, 0.011765, 0.297852, 0.501953, "p3Blue"],
[0.011765, 0.011765, 0.011765, 0.501953, "p3Black"]];
var xWidth = xHeight = 2;
var colorCorrectionToleranceSRGB = 1;
// The error level in SRGB->WCG_U8->SRGB is higher than normal color correction
// operations as the pixels are clipped in WCG when U8 storage is used instead
// of F16 storage. See this fiddle:
// https://fiddle.skia.org/c/584fbc0b778a32a6cc2ea3ecbf2c6e8e
var colorCorrectionToleranceWCG_U8toSRGB_U8 = 4;
var colorCorrectionToleranceWCG = 0.01;
function testBlankPixels(actualData, width, height)
{
var message = "This pixel should be transparent black";
var blankData = new Array(4 * width * height).fill(0);
assert_array_equals(actualData, blankData, message);
}
function testPixels(actualData, expectedPixels, tolerance)
{
assert_equals(actualData.length, 4 * xWidth * xHeight);
for (var i = 0; i < xWidth * xHeight; i++) {
var message = "This pixel should be " + expectedPixels[i][4];
for (var j = 0; j < 4; j++)
assert_approx_equals(actualData[i*4+j], expectedPixels[i][j], tolerance,
message);
}
}
function checkImageDataColorSettings(canvasColorSettings, imageData) {
var imageDataColorSettings = imageData.getColorSettings();
assert_equals(canvasColorSettings.colorSpace,
imageDataColorSettings.colorSpace);
// According to the proposal for canvas color management
// (github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md),
// pixelFormat can be "8-8-8-8", "10-10-10-2", "12-12-12-12", or "float16".
// However, the current implementation in Chromium only supports "8-8-8-8" and
// "float16", which are mapped to "uint8" and "float32" storage formats for
// ImageData, respectively.
if (canvasColorSettings.pixelFormat == "8-8-8-8")
assert_equals("uint8", imageDataColorSettings.storageFormat);
else
assert_equals("float32", imageDataColorSettings.storageFormat);
}
function checkImageDataColorValues(canvasColorSettings, imageData, isBlank = '',
width = xWidth, height = xHeight, isWCG_U8toSRGB_U8 = '') {
var data = imageData.dataUnion;
if (isBlank === 'isBlank') {
testBlankPixels(data, width, height);
} else {
var expectedPixels = srgbPixels;
var tolerance = colorCorrectionToleranceSRGB;
if (isWCG_U8toSRGB_U8 === 'isWCG_U8toSRGB_U8')
tolerance = colorCorrectionToleranceWCG_U8toSRGB_U8;
switch (canvasColorSettings.colorSpace) {
case "srgb":
if (canvasColorSettings.pixelFormat != "8-8-8-8") {
expectedPixels = linearRgbPixels;
tolerance = colorCorrectionToleranceWCG;
}
break;
case "rec2020":
expectedPixels = rec2020Pixels;
tolerance = colorCorrectionToleranceWCG;
break;
case "p3":
expectedPixels = p3Pixels;
tolerance = colorCorrectionToleranceWCG;
break;
default:
assert_true(false,
"Expected srgb, rec2020 or p3 color space, but got invalid value: " +
canvasColorSettings.colorSpace);
}
testPixels(data, expectedPixels, tolerance);
}
}
function initializeColorManagedCanvas(canvasColorSettings)
{
var canvas = document.createElement('canvas');
canvas.width = xWidth;
canvas.height = xHeight;
var ctx = canvas.getContext('2d',
{colorSpace: canvasColorSettings.colorSpace,
pixelFormat: canvasColorSettings.pixelFormat});
ctx.fillStyle = "rgba(155, 27, 27, 0.5)";
ctx.fillRect(0, 0, xWidth/2, xHeight/2);
ctx.fillStyle = "rgba(27, 155, 27, 0.5)";
ctx.fillRect(xWidth/2, 0, xWidth/2, xHeight/2);
ctx.fillStyle = "rgba(27, 27, 155, 0.5)";
ctx.fillRect(0, xHeight/2, xWidth/2, xHeight/2);
ctx.fillStyle = "rgba(27, 27, 27, 0.5)";
ctx.fillRect(xWidth/2, xHeight/2, xWidth/2, xHeight/2);
return canvas;
}
var canvasColorSettingsSet = [
{name: "SRGB", colorSettings: {colorSpace: "srgb", pixelFormat: "8-8-8-8"}},
{name: "Linear RGB",
colorSettings: {colorSpace: "srgb", pixelFormat: "float16"}},
{name: "Rec2020",
colorSettings: {colorSpace: "rec2020", pixelFormat: "float16"}},
{name: "P3", colorSettings: {colorSpace: "p3", pixelFormat: "float16"}},
];
var srgbImageDataU8, linearRgbImageDataU16, linearRgbImageDataF32,
rec2020ImageDataU8, rec2020ImageDataU16, rec2020ImageDataF32,
p3ImageDataU8, p3ImageDataU16, p3ImageDataF32;
function PreparePredefinedImageDataObjects() {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
srgbImageDataU8 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "srgb", storageFormat: "uint8"});
linearRgbImageDataU16 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "srgb", storageFormat: "uint16"});
linearRgbImageDataF32 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "srgb", storageFormat: "float32"});
rec2020ImageDataU8 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "rec2020", storageFormat: "uint8"});
rec2020ImageDataU16 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "rec2020", storageFormat: "uint16"});
rec2020ImageDataF32 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "rec2020", storageFormat: "float32"});
p3ImageDataU8 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "p3", storageFormat: "uint8"});
p3ImageDataU16 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "p3", storageFormat: "uint16"});
p3ImageDataF32 = ctx.createImageData(xWidth, xHeight,
{colorSpace: "p3", storageFormat: "float32"});
for (var i = 0; i < xWidth * xHeight; i++)
for (var j = 0; j < 4; j++) {
srgbImageDataU8.dataUnion[i*4+j] = srgbPixels[i][j];
linearRgbImageDataU16.dataUnion[i*4+j] =
Math.round(linearRgbPixels[i][j] * 65535);
linearRgbImageDataF32.dataUnion[i*4+j] = linearRgbPixels[i][j];
rec2020ImageDataU8.dataUnion[i*4+j] =
Math.round(rec2020Pixels[i][j] * 255);
rec2020ImageDataU16.dataUnion[i*4+j] =
Math.round(rec2020Pixels[i][j] * 65535);
rec2020ImageDataF32.dataUnion[i*4+j] = rec2020Pixels[i][j];
p3ImageDataU8.dataUnion[i*4+j] = Math.round(p3Pixels[i][j] * 255);
p3ImageDataU16.dataUnion[i*4+j] = Math.round(p3Pixels[i][j] * 65535);
p3ImageDataF32.dataUnion[i*4+j] = p3Pixels[i][j];
}
}
PreparePredefinedImageDataObjects();
var imageDataColorSettingsSet = [
{name: "SRGB U8", colorSettings: {colorSpace: "srgb", storageFormat: "uint8"},
imageData: srgbImageDataU8},
{name: "LinearRGB U16", colorSettings: {colorSpace: "srgb",
storageFormat: "uint16"}, imageData: linearRgbImageDataU16},
{name: "LinearRGB F32", colorSettings: {colorSpace: "srgb",
storageFormat: "float32"}, imageData: linearRgbImageDataF32},
{name: "Rec2020 U8", colorSettings: {colorSpace: "rec2020",
storageFormat: "uint8"}, imageData: rec2020ImageDataU8},
{name: "Rec2020 U16", colorSettings: {colorSpace: "rec2020",
storageFormat: "uint16"}, imageData: rec2020ImageDataU16},
{name: "Rec2020 F32", colorSettings: {colorSpace: "rec2020",
storageFormat: "float32"}, imageData: rec2020ImageDataF32},
{name: "P3 U8", colorSettings: {colorSpace: "p3", storageFormat: "uint8"},
imageData: p3ImageDataU8},
{name: "P3 U16", colorSettings: {colorSpace: "p3", storageFormat: "uint16"},
imageData: p3ImageDataU16},
{name: "P3 F32", colorSettings: {colorSpace: "p3", storageFormat: "float32"},
imageData: p3ImageDataF32},
];
////////////////////////////////////////////////////////////////////////////////
// * ImageData imagedata = ctx.createImageData(width, height);
// No color conversion. imagedata follows the color settings of the canvas.
function runTestCreateImageDataWH(canvasColorSettings) {
var canvas = initializeColorManagedCanvas(canvasColorSettings);
var ctx = canvas.getContext('2d');
var imageData = ctx.createImageData(xWidth, xHeight);
checkImageDataColorSettings(canvasColorSettings, imageData);
}
var testScenariosCreateImageDataWH = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
var message = "Test cretateImageData(width, height) from " +
canvasColorSettingsSet[i].name + " canvas ";
testScenariosCreateImageDataWH.
push([message, canvasColorSettingsSet[i].colorSettings]);
}
generate_tests(runTestCreateImageDataWH, testScenariosCreateImageDataWH);
////////////////////////////////////////////////////////////////////////////////
// * ImageData imagedata = ctx.getImageData(sx, sy, sw, sh);
// No color conversion. imagedata follows the color settings of the canvas.
function runTestGetImageDataXYWH(canvasColorSettings) {
var canvas = initializeColorManagedCanvas(canvasColorSettings);
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, xWidth, xHeight);
checkImageDataColorSettings(canvasColorSettings, imageData);
checkImageDataColorValues(canvasColorSettings, imageData);
}
var testScenariosGetImageDataXYWH = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
var message = "Test getImageData(sx, sy, sw, sh) from " +
canvasColorSettingsSet[i].name + " canvas ";
testScenariosGetImageDataXYWH.
push([message, canvasColorSettingsSet[i].colorSettings]);
}
generate_tests(runTestGetImageDataXYWH, testScenariosGetImageDataXYWH);
////////////////////////////////////////////////////////////////////////////////
// * void ctx.putImageData(imagedata, dx, dy, ...);
// Color conversion, if needed, to the color settings of the canvas.
function runTestPutImageDataDxDy(canvasColorSettings, imageData) {
var canvas = document.createElement('canvas');
canvas.width = xWidth * 2;
canvas.height = xHeight * 2;
var ctx = canvas.getContext('2d',
{colorSpace: canvasColorSettings.colorSpace,
pixelFormat: canvasColorSettings.pixelFormat});
ctx.putImageData(imageData, xWidth/2, xHeight/2);
var ctxImageData = ctx.getImageData(xWidth/2, xHeight/2, xWidth, xHeight);
checkImageDataColorSettings(canvasColorSettings, ctxImageData);
checkImageDataColorValues(canvasColorSettings, ctxImageData, 'noBlank',
xWidth, xHeight, 'isWCG_U8toSRGB_U8');
}
var testScenariosPutImageDataDxDy = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
for (var j = 0; j < imageDataColorSettingsSet.length; j++) {
var message = "Test putImageData(imagedata, dx, dy): " +
canvasColorSettingsSet[i].name + " canvas, " +
imageDataColorSettingsSet[j].name + " ImageData";
testScenariosPutImageDataDxDy.
push([message, canvasColorSettingsSet[i].colorSettings,
imageDataColorSettingsSet[j].imageData]);
}
}
generate_tests(runTestPutImageDataDxDy, testScenariosPutImageDataDxDy);
////////////////////////////////////////////////////////////////////////////////
// * ImageData imageData = ctx.createImageData(imagedata);
// Color conversion, if needed, to the color settings of the canvas. The
// returned imageData should be transparent black.
function runTestCreateImageDataFromImageData(canvasColorSettings, imageData) {
var canvas = document.createElement('canvas');
canvas.width = xWidth * 2;
canvas.height = xHeight * 2;
var ctx = canvas.getContext('2d',
{colorSpace: canvasColorSettings.colorSpace,
pixelFormat: canvasColorSettings.pixelFormat});
var ctxImageData = ctx.createImageData(imageData);
checkImageDataColorSettings(canvasColorSettings, ctxImageData);
checkImageDataColorValues(canvasColorSettings, ctxImageData, 'isBlank');
}
var testScenariosCreateImageDataFromImageData = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
for (var j = 0; j < imageDataColorSettingsSet.length; j++) {
var message = "Test createImageData(imagedata): " +
canvasColorSettingsSet[i].name + " canvas, " +
imageDataColorSettingsSet[j].name + " ImageData";
testScenariosCreateImageDataFromImageData.
push([message, canvasColorSettingsSet[i].colorSettings,
imageDataColorSettingsSet[j].imageData]);
}
}
generate_tests(runTestCreateImageDataFromImageData,
testScenariosCreateImageDataFromImageData);
////////////////////////////////////////////////////////////////////////////////
// *ImageData ctx.createImageData(width, height, imageDataColorSettings);
// No color conversion to the color settings of the canvas. Blank ImageData
// should be created based on imageDataColorSettings.
function runTestCreateImageDataWHC(canvasColorSettings, imageDataColorSettings) {
var canvas = initializeColorManagedCanvas(canvasColorSettings);
var ctx = canvas.getContext('2d');
width = height = 3;
var imageData = ctx.createImageData(width, height, imageDataColorSettings);
var colorSettings = imageData.getColorSettings();
assert_equals(colorSettings.colorSpace,
imageDataColorSettings.colorSpace,
"colorSpace should match");
assert_equals(colorSettings.storageFormat,
imageDataColorSettings.storageFormat,
"storageFormat should match");
var blankData = new Array(4 * width * height).fill(0);
assert_array_equals(imageData.dataUnion, blankData,
"ImageData should be transparent black");
}
var testScenariosCreateImageDataWHC = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
for (var j = 0; j < imageDataColorSettingsSet.length; j++) {
message = "Test createImageData(width, height, imageDataColorSettings): " +
canvasColorSettingsSet[i].name + " canvas, " +
imageDataColorSettingsSet[j].name + " ImageData";
testScenariosCreateImageDataWHC.
push([message, canvasColorSettingsSet[i].colorSettings,
imageDataColorSettingsSet[j].colorSettings]);
}
}
generate_tests(runTestCreateImageDataWHC, testScenariosCreateImageDataWHC);
////////////////////////////////////////////////////////////////////////////////
// *ImageData ctx.createImageData(data, width, height, imageDataColorSettings);
// No color conversion to the color settings of the canvas. ImageData is created
// from param "data" with proper storage format and is tagged with the
// CanvasColorSpace which is given in imageDataColorSettings.
function runTestCreateImageDataDWHC(canvasColorSettings, imageData) {
var data = imageData.dataUnion;
width = xWidth;
height = xHeight;
var colorSettings = imageData.getColorSettings();
var canvas = initializeColorManagedCanvas(canvasColorSettings);
var ctx = canvas.getContext('2d');
var newImageData = ctx.createImageData(data, width, height, colorSettings);
var newColorSettings = newImageData.getColorSettings();
assert_equals(newColorSettings.colorSpace,
colorSettings.colorSpace,
"colorSpace should match");
assert_equals(newColorSettings.storageFormat,
colorSettings.storageFormat,
"storageFormat should match");
assert_array_equals(newImageData.dataUnion, imageData.dataUnion,
"ImageData should be transparent black");
}
var testScenariosCreateImageDataDWHC = [];
for (var i = 0; i < canvasColorSettingsSet.length; i++) {
for (var j = 0; j < imageDataColorSettingsSet.length; j++) {
message = "Test createImageData(data, width, height, imageDataColorSettings): " +
canvasColorSettingsSet[i].name + " canvas, " +
imageDataColorSettingsSet[j].name + " ImageData";
testScenariosCreateImageDataDWHC.
push([message, canvasColorSettingsSet[i].colorSettings,
imageDataColorSettingsSet[j].imageData]);
}
}
generate_tests(runTestCreateImageDataDWHC, testScenariosCreateImageDataDWHC);
////////////////////////////////////////////////////////////////////////////////
</script>