Skip to content

Commit

Permalink
CTS: Color space conversion operation test for CopyToTexture
Browse files Browse the repository at this point in the history
This PR add cts to test copyExternalImageToTexture() color space
conversion ability.
It creates canvas with color space attr and copy to dst texture in
user defined color space through CopyExternalImageToTexture().

Issue:gpuweb#913
  • Loading branch information
shaoboyan committed Mar 7, 2022
1 parent d1fb6c5 commit aadaee3
Show file tree
Hide file tree
Showing 6 changed files with 535 additions and 50 deletions.
3 changes: 3 additions & 0 deletions docs/helper_index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Whenever a new generally-useful helper is added, it should be indexed here.
- {@link webgpu/util/conversion}: Numeric encoding/decoding for float/unorm/snorm values, etc.
- {@link webgpu/util/copy_to_texture}:
Helper class for copyToTexture test suites for execution copy and check results.
- {@link webgpu/util/color_space_conversion}:
Helper functions to do color space conversion. The algorithm is the same as defined in
CSS Color Module Level 4.
- {@link webgpu/util/create_elements}:
Helpers for creating web elements like HTMLCanvasElement, OffscrrenCanvas, etc.
- {@link webgpu/util/navigator_gpu}: Finds and returns the `navigator.gpu` object or equivalent.
Expand Down
23 changes: 13 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

190 changes: 190 additions & 0 deletions src/webgpu/util/color_space_conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { assert } from '../../common/util/util.js';

/**
* The color space conversion function definations are fully aign with
* CSS Color Module Level 4 Sample Code: https://drafts.csswg.org/css-color/#color-conversion-code
*/

/**
* Simple matrix (and vector) multiplication
* Warning: No error handling for incompatible dimensions!
* @author Lea Verou 2020 MIT License
*/
// A is m x n. B is n x p. product is m x p.
function multiplyMatrices(A: Array<Array<number>>, B: Array<Array<number>>) {
const B_cols = B[0].map((_, i) => B.map(x => x[i])); // transpose B
const product = A.map(row =>
B_cols.map(col => {
if (!Array.isArray(row)) {
return col.reduce((a, c) => a + c * row, 0);
}

return row.reduce((a, c, i) => a + c * (col[i] || 0), 0);
})
);

return product;
}

// Sample code for color conversions
// Conversion can also be done using ICC profiles and a Color Management System
// For clarity, a library is used for matrix multiplication (multiply-matrices.js)

// sRGB-related functions

function lin_sRGB(RGB: Array<number>) {
// convert an array of sRGB values
// where in-gamut values are in the range [0 - 1]
// to linear light (un-companded) form.
// https://en.wikipedia.org/wiki/SRGB
// Extended transfer function:
// for negative values, linear portion is extended on reflection of axis,
// then reflected power function is used.
return RGB.map(val => {
const sign = val < 0 ? -1 : 1;
const abs = Math.abs(val);

if (abs < 0.04045) {
return val / 12.92;
}

return sign * Math.pow((abs + 0.055) / 1.055, 2.4);
});
}

function gam_sRGB(RGB: Array<number>) {
// convert an array of linear-light sRGB values in the range 0.0-1.0
// to gamma corrected form
// https://en.wikipedia.org/wiki/SRGB
// Extended transfer function:
// For negative values, linear portion extends on reflection
// of axis, then uses reflected pow below that
return RGB.map(val => {
const sign = val < 0 ? -1 : 1;
const abs = Math.abs(val);

if (abs > 0.0031308) {
return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055);
}

return 12.92 * val;
});
}

function lin_sRGB_to_XYZ(rgb: Array<Array<number>>) {
// convert an array of linear-light sRGB values to CIE XYZ
// using sRGB's own white, D65 (no chromatic adaptation)

const M = [
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
];
return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_sRGB(XYZ: Array<Array<number>>) {
// convert XYZ to linear-light sRGB

const M = [
[3.2409699419045226, -1.537383177570094, -0.4986107602930034],
[-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
[0.05563007969699366, -0.20397695888897652, 1.0569715142428786],
];

return multiplyMatrices(M, XYZ);
}

// display-p3-related functions

function lin_P3(RGB: Array<number>) {
// convert an array of display-p3 RGB values in the range 0.0 - 1.0
// to linear light (un-companded) form.

return lin_sRGB(RGB); // same as sRGB
}

function gam_P3(RGB: Array<number>) {
// convert an array of linear-light display-p3 RGB in the range 0.0-1.0
// to gamma corrected form

return gam_sRGB(RGB); // same as sRGB
}

function lin_P3_to_XYZ(rgb: Array<Array<number>>) {
// convert an array of linear-light display-p3 values to CIE XYZ
// using D65 (no chromatic adaptation)
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
const M = [
[0.4865709486482162, 0.26566769316909306, 0.1982172852343625],
[0.2289745640697488, 0.6917385218365064, 0.079286914093745],
[0.0, 0.04511338185890264, 1.043944368900976],
];
// 0 was computed as -3.972075516933488e-17

return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_P3(XYZ: Array<Array<number>>) {
// convert XYZ to linear-light P3
const M = [
[2.493496911941425, -0.9313836179191239, -0.40271078445071684],
[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
[0.03584583024378447, -0.07617238926804182, 0.9568845240076872],
];

return multiplyMatrices(M, XYZ);
}

// Follow conversion steps in CSS Color Module Level 4
// https://drafts.csswg.org/css-color/#predefined-to-predefined
// display-p3 and sRGB share the same white points.
export function displayP3ToSrgb(pixel: {
R: number;
G: number;
B: number;
A: number;
}): { R: number; G: number; B: number; A: number } {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
);

let rgbVec = [pixel.R, pixel.G, pixel.B];
rgbVec = lin_P3(rgbVec);
let rgbMatrix = [[rgbVec[0]], [rgbVec[1]], [rgbVec[2]]];
rgbMatrix = XYZ_to_lin_sRGB(lin_P3_to_XYZ(rgbMatrix));
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_sRGB(rgbVec);

pixel.R = rgbVec[0];
pixel.G = rgbVec[1];
pixel.B = rgbVec[2];

return pixel;
}

export function srgbToDisplayP3(pixel: {
R: number;
G: number;
B: number;
A: number;
}): { R: number; G: number; B: number; A: number } {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
);

let rgbVec = [pixel.R, pixel.G, pixel.B];
rgbVec = lin_sRGB(rgbVec);
let rgbMatrix = [[rgbVec[0]], [rgbVec[1]], [rgbVec[2]]];
rgbMatrix = XYZ_to_lin_P3(lin_sRGB_to_XYZ(rgbMatrix));
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_P3(rgbVec);

pixel.R = rgbVec[0];
pixel.G = rgbVec[1];
pixel.B = rgbVec[2];

return pixel;
}
Loading

0 comments on commit aadaee3

Please sign in to comment.