| <!DOCTYPE HTML> |
| <script src="../../../resources/testharness.js"></script> |
| <script src="../../../resources/testharnessreport.js"></script> |
| <script> |
| |
| // Reference values generated by: |
| // https://fiddle.skia.org/c/8e7238b69744678e75b3e34715b29e0b |
| |
| var srgbPixels = |
| [[155,27,27,128, "srgbRed"], |
| [27,155,27,128, "srgbGreen"], |
| [27,27,155,128, "srgbBlue"], |
| [27,27,27,128, "srgbBlack"]]; |
| |
| var e_srgbPixels = |
| [[0.60742, 0.10583, 0.10583, 0.50195, "e_sRgbRed"], |
| [0.10583, 0.60742, 0.10583, 0.50195, "e_sRgbGreen"], |
| [0.10583, 0.10583, 0.60742, 0.50195, "e_sRgbBlue"], |
| [0.10583, 0.10583, 0.10583, 0.50195, "e_sRgbBlack"]]; |
| |
| var rec2020Pixels = |
| [[0.52148, 0.23914, 0.18030, 0.50195, "rec2020Red"], |
| [0.40771, 0.60742, 0.25879, 0.50195, "rec2020Green"], |
| [0.21558, 0.17249, 0.59961, 0.50195, "rec2020Blue"], |
| [0.15283, 0.15283, 0.15283, 0.50195, "rec2020Black"]]; |
| |
| var p3Pixels = |
| [[0.55664, 0.15686, 0.13330, 0.50195, "p3Red"], |
| [0.28613, 0.59961, 0.20386, 0.50195, "p3Green"], |
| [0.10583, 0.10583, 0.58398, 0.50195, "p3Blue"], |
| [0.10583, 0.10583, 0.10583, 0.50195, "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 = 8; |
| var colorCorrectionToleranceWCG = 0.02; |
| |
| 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 getImageSetting(canvasColorSettings) { |
| var imageSetting = {}; |
| imageSetting.colorSpace = canvasColorSettings.colorSpace; |
| if (canvasColorSettings.pixelFormat == "uint8") { |
| imageSetting.storageFormat = "uint8"; |
| } else { |
| imageSetting.storageFormat = "float32"; |
| } |
| return imageSetting; |
| } |
| |
| function checkImageDataSettings(expectedColorSetting, imageData) { |
| var imageDataSettings = imageData.getSettings(); |
| assert_equals(expectedColorSetting.colorSpace, |
| imageDataSettings.colorSpace); |
| // According to the proposal for canvas color management |
| // (github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md), |
| // pixelFormat can be "uint8", "10-10-10-2", "12-12-12-12", or "float16". |
| // However, the current implementation in Chromium only supports "uint8" and |
| // "float16", which are mapped to "uint8" and "float32" storage formats for |
| // ImageData, respectively. |
| assert_equals(expectedColorSetting.storageFormat, |
| imageDataSettings.storageFormat); |
| } |
| |
| function checkImageDataColorValues(canvasColorSettings, imageData, isBlank = '', |
| width = xWidth, height = xHeight, isWCG_U8toSRGB_U8 = '') { |
| var data = imageData.data; |
| 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 != "uint8") { |
| expectedPixels = e_srgbPixels; |
| tolerance = colorCorrectionToleranceWCG; |
| } |
| break; |
| case "rec2020": |
| expectedPixels = rec2020Pixels; |
| tolerance = colorCorrectionToleranceWCG; |
| break; |
| case "display-p3": |
| expectedPixels = p3Pixels; |
| tolerance = colorCorrectionToleranceWCG; |
| break; |
| default: |
| assert_true(false, |
| "Expected srgb, rec2020 or display-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: "uint8"}}, |
| {name: "e-SRGB", colorSettings: {colorSpace: "srgb", pixelFormat: "float16"}}, |
| {name: "Rec2020", |
| colorSettings: {colorSpace: "rec2020", pixelFormat: "float16"}}, |
| {name: "P3", colorSettings: {colorSpace: "display-p3", pixelFormat: "float16"}}, |
| ]; |
| |
| var srgbImageDataU8, e_srgbImageDataU16, e_srgbImageDataF32, |
| 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"}); |
| e_srgbImageDataU16 = ctx.createImageData(xWidth, xHeight, |
| {colorSpace: "srgb", storageFormat: "uint16"}); |
| e_srgbImageDataF32 = 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: "display-p3", storageFormat: "uint8"}); |
| p3ImageDataU16 = ctx.createImageData(xWidth, xHeight, |
| {colorSpace: "display-p3", storageFormat: "uint16"}); |
| p3ImageDataF32 = ctx.createImageData(xWidth, xHeight, |
| {colorSpace: "display-p3", storageFormat: "float32"}); |
| |
| for (var i = 0; i < xWidth * xHeight; i++) |
| for (var j = 0; j < 4; j++) { |
| srgbImageDataU8.data[i*4+j] = srgbPixels[i][j]; |
| e_srgbImageDataU16.data[i*4+j] = |
| Math.round(e_srgbPixels[i][j] * 65535); |
| e_srgbImageDataF32.data[i*4+j] = e_srgbPixels[i][j]; |
| |
| rec2020ImageDataU8.data[i*4+j] = |
| Math.round(rec2020Pixels[i][j] * 255); |
| rec2020ImageDataU16.data[i*4+j] = |
| Math.round(rec2020Pixels[i][j] * 65535); |
| rec2020ImageDataF32.data[i*4+j] = rec2020Pixels[i][j]; |
| |
| p3ImageDataU8.data[i*4+j] = Math.round(p3Pixels[i][j] * 255); |
| p3ImageDataU16.data[i*4+j] = Math.round(p3Pixels[i][j] * 65535); |
| p3ImageDataF32.data[i*4+j] = p3Pixels[i][j]; |
| } |
| } |
| |
| PreparePredefinedImageDataObjects(); |
| var imageDataSettingsSet = [ |
| {name: "SRGB U8", colorSettings: {colorSpace: "srgb", storageFormat: "uint8"}, |
| imageData: srgbImageDataU8}, |
| {name: "e-SRGB U16", colorSettings: {colorSpace: "srgb", |
| storageFormat: "uint16"}, imageData: e_srgbImageDataU16}, |
| {name: "e-SRGB F32", colorSettings: {colorSpace: "srgb", |
| storageFormat: "float32"}, imageData: e_srgbImageDataF32}, |
| |
| {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: "display-p3", storageFormat: "uint8"}, |
| imageData: p3ImageDataU8}, |
| {name: "P3 U16", colorSettings: {colorSpace: "display-p3", storageFormat: "uint16"}, |
| imageData: p3ImageDataU16}, |
| {name: "P3 F32", colorSettings: {colorSpace: "display-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 imageSetting = getImageSetting(canvasColorSettings) |
| var imageData = ctx.createImageData(xWidth, xHeight, imageSetting); |
| checkImageDataSettings(imageSetting, imageData); |
| } |
| |
| var testScenariosCreateImageDataWH = []; |
| for (var i = 0; i < canvasColorSettingsSet.length; i++) { |
| var message = "Test createImageData(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 imageSetting = getImageSetting(canvasColorSettings) |
| var imageData = ctx.getImageData(0, 0, xWidth, xHeight, imageSetting); |
| checkImageDataSettings(imageSetting, 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}); |
| var imageSetting = getImageSetting(canvasColorSettings); |
| ctx.putImageData(imageData, xWidth/2, xHeight/2); |
| var ctxImageData = ctx.getImageData(xWidth/2, xHeight/2, xWidth, xHeight, imageSetting); |
| checkImageDataSettings(imageSetting, 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 < imageDataSettingsSet.length; j++) { |
| var message = "Test putImageData(imagedata, dx, dy): " + |
| canvasColorSettingsSet[i].name + " canvas, " + |
| imageDataSettingsSet[j].name + " ImageData"; |
| testScenariosPutImageDataDxDy. |
| push([message, canvasColorSettingsSet[i].colorSettings, |
| imageDataSettingsSet[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); |
| checkImageDataSettings(imageData.getSettings(), ctxImageData); |
| checkImageDataColorValues(canvasColorSettings, ctxImageData, 'isBlank'); |
| } |
| |
| var testScenariosCreateImageDataFromImageData = []; |
| for (var i = 0; i < canvasColorSettingsSet.length; i++) { |
| for (var j = 0; j < imageDataSettingsSet.length; j++) { |
| var message = "Test createImageData(imagedata): " + |
| canvasColorSettingsSet[i].name + " canvas, " + |
| imageDataSettingsSet[j].name + " ImageData"; |
| testScenariosCreateImageDataFromImageData. |
| push([message, canvasColorSettingsSet[i].colorSettings, |
| imageDataSettingsSet[j].imageData]); |
| } |
| } |
| |
| generate_tests(runTestCreateImageDataFromImageData, |
| testScenariosCreateImageDataFromImageData); |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // *ImageData ctx.createImageData(width, height, imageDataSettings); |
| // No color conversion to the color settings of the canvas. Blank ImageData |
| // should be created based on imageDataSettings. |
| |
| function runTestCreateImageDataWHC(canvasColorSettings, imageDataSettings) { |
| var canvas = initializeColorManagedCanvas(canvasColorSettings); |
| var ctx = canvas.getContext('2d'); |
| width = height = 3; |
| var imageData = ctx.createImageData(width, height, imageDataSettings); |
| var colorSettings = imageData.getSettings(); |
| assert_equals(colorSettings.colorSpace, |
| imageDataSettings.colorSpace, |
| "colorSpace should match"); |
| assert_equals(colorSettings.storageFormat, |
| imageDataSettings.storageFormat, |
| "storageFormat should match"); |
| var blankData = new Array(4 * width * height).fill(0); |
| assert_array_equals(imageData.data, blankData, |
| "ImageData should be transparent black"); |
| } |
| |
| var testScenariosCreateImageDataWHC = []; |
| for (var i = 0; i < canvasColorSettingsSet.length; i++) { |
| for (var j = 0; j < imageDataSettingsSet.length; j++) { |
| message = "Test createImageData(width, height, imageDataSettings): " + |
| canvasColorSettingsSet[i].name + " canvas, " + |
| imageDataSettingsSet[j].name + " ImageData"; |
| testScenariosCreateImageDataWHC. |
| push([message, canvasColorSettingsSet[i].colorSettings, |
| imageDataSettingsSet[j].colorSettings]); |
| } |
| } |
| |
| generate_tests(runTestCreateImageDataWHC, testScenariosCreateImageDataWHC); |
| |
| </script> |