diff --git a/CHANGELOG.md b/CHANGELOG.md index 458815b..1e52b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## V0.4.0 - [x] color-mix(srgb) -- [x] color(srgb, srgb-linear, display-p3, prophoto-rgb, a98-rgb, rec2020) +- [x] color(srgb, srgb-linear, display-p3, prophoto-rgb, a98-rgb, rec2020, xyz, xyz-d50) ## V0.3.0 diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index a290e62..7dcbef8 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -1375,9 +1375,44 @@ return expr; } + // from https://www.w3.org/TR/css-color-4/multiply-matrices.js + /** + * 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, B) { + let m = A.length; + if (!Array.isArray(A[0])) { + // A is vector, convert to [[a, b, c, ...]] + A = [A]; + } + if (!Array.isArray(B[0])) { + // B is vector, convert to [[a], [b], [c], ...]] + B = B.map((x) => [x]); + } + let p = B[0].length; + let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B + let 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); + })); + if (m === 1) { + product = product[0]; // Avoid [[a, b, c, ...]] + } + if (p === 1) { + return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] + } + return product; + } + function roundWithPrecision(value, original) { return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); } + // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 @@ -1450,6 +1485,35 @@ }); } + function XYZ_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); + } + function XYZ_to_lin_sRGB(x, y, z) { + // convert XYZ to linear-light sRGB + const M = [ + [12831 / 3959, -329 / 214, -1974 / 3959], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [705 / 12673, -2585 / 12673, 705 / 667], + ]; + const XYZ = [x, y, z]; // convert to XYZ + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); + } + function XYZ_D50_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); + } + function D50_to_D65(x, y, z) { + // Bradford chromatic adaptation from D50 to D65 + const M = [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] + ]; + const XYZ = [x, y, z]; + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); + } + const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color']; function reduceNumber(val) { val = String(+val); @@ -1706,7 +1770,7 @@ case exports.EnumToken.ColorTokenType: if (options.convertColor) { if (token.val == 'color') { - const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020']; + const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; if (token.chi[0].typ == exports.EnumToken.IdenTokenType && supportedColorSpaces.includes(token.chi[0].val.toLowerCase())) { let values = token.chi.slice(1, 4).map((t) => { if (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') { @@ -1732,6 +1796,15 @@ // @ts-ignore values = gam_sRGB(...lin_2020(...values)); break; + case 'xyz': + case 'xyz-d65': + // @ts-ignore + values = XYZ_to_sRGB(...values); + break; + case 'xyz-d50': + // @ts-ignore + values = XYZ_D50_to_sRGB(...values); + break; } clampValues(values, colorSpace); let value = `#${values.reduce((acc, curr) => { diff --git a/dist/index.cjs b/dist/index.cjs index a823941..f35ead6 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -1373,9 +1373,44 @@ function computeComponentValue(expr, values) { return expr; } +// from https://www.w3.org/TR/css-color-4/multiply-matrices.js +/** + * 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, B) { + let m = A.length; + if (!Array.isArray(A[0])) { + // A is vector, convert to [[a, b, c, ...]] + A = [A]; + } + if (!Array.isArray(B[0])) { + // B is vector, convert to [[a], [b], [c], ...]] + B = B.map((x) => [x]); + } + let p = B[0].length; + let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B + let 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); + })); + if (m === 1) { + product = product[0]; // Avoid [[a, b, c, ...]] + } + if (p === 1) { + return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] + } + return product; +} + function roundWithPrecision(value, original) { return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); } + // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 @@ -1448,6 +1483,35 @@ function lin_2020(r, g, b) { }); } +function XYZ_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); +} +function XYZ_to_lin_sRGB(x, y, z) { + // convert XYZ to linear-light sRGB + const M = [ + [12831 / 3959, -329 / 214, -1974 / 3959], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [705 / 12673, -2585 / 12673, 705 / 667], + ]; + const XYZ = [x, y, z]; // convert to XYZ + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); +} +function XYZ_D50_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); +} +function D50_to_D65(x, y, z) { + // Bradford chromatic adaptation from D50 to D65 + const M = [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] + ]; + const XYZ = [x, y, z]; + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); +} + const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color']; function reduceNumber(val) { val = String(+val); @@ -1704,7 +1768,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, case exports.EnumToken.ColorTokenType: if (options.convertColor) { if (token.val == 'color') { - const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020']; + const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; if (token.chi[0].typ == exports.EnumToken.IdenTokenType && supportedColorSpaces.includes(token.chi[0].val.toLowerCase())) { let values = token.chi.slice(1, 4).map((t) => { if (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') { @@ -1730,6 +1794,15 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, // @ts-ignore values = gam_sRGB(...lin_2020(...values)); break; + case 'xyz': + case 'xyz-d65': + // @ts-ignore + values = XYZ_to_sRGB(...values); + break; + case 'xyz-d50': + // @ts-ignore + values = XYZ_D50_to_sRGB(...values); + break; } clampValues(values, colorSpace); let value = `#${values.reduce((acc, curr) => { diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index ece3dd4..c9572f9 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -9,6 +9,7 @@ import '../parser/parse.js'; import { isColor, isNewLine } from '../parser/utils/syntax.js'; import { parseRelativeColor } from './utils/relativecolor.js'; import { gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto } from './utils/colorspace/rgb.js'; +import { XYZ_D50_to_sRGB, XYZ_to_sRGB } from './utils/colorspace/xyz.js'; const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color']; function reduceNumber(val) { @@ -266,7 +267,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, case EnumToken.ColorTokenType: if (options.convertColor) { if (token.val == 'color') { - const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020']; + const supportedColorSpaces = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; if (token.chi[0].typ == EnumToken.IdenTokenType && supportedColorSpaces.includes(token.chi[0].val.toLowerCase())) { let values = token.chi.slice(1, 4).map((t) => { if (t.typ == EnumToken.IdenTokenType && t.val == 'none') { @@ -292,6 +293,15 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, // @ts-ignore values = gam_sRGB(...lin_2020(...values)); break; + case 'xyz': + case 'xyz-d65': + // @ts-ignore + values = XYZ_to_sRGB(...values); + break; + case 'xyz-d50': + // @ts-ignore + values = XYZ_D50_to_sRGB(...values); + break; } clampValues(values, colorSpace); let value = `#${values.reduce((acc, curr) => { diff --git a/dist/lib/renderer/utils/colorspace.js b/dist/lib/renderer/utils/colorspace.js deleted file mode 100644 index 9436f52..0000000 --- a/dist/lib/renderer/utils/colorspace.js +++ /dev/null @@ -1,39 +0,0 @@ -function roundWithPrecision(value, original) { - return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); -} -// from https://www.w3.org/TR/css-color-4/#color-conversion-code -// srgb-linear -> srgb -// 0 <= r, g, b <= 1 -function gam_sRGB(r, g, b) { - // 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 [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs > 0.0031308) { - return roundWithPrecision(sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055), val); - } - return roundWithPrecision(12.92 * val, val); - }); -} -function gam_ProPhoto(r, g, b) { - // convert an array of linear-light prophoto-rgb in the range 0.0-1.0 - // to gamma corrected form - // Transfer curve is gamma 1.8 with a small linear portion - // TODO for negative values, extend linear portion on reflection of axis, then add pow below that - const Et = 1 / 512; - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs >= Et) { - return roundWithPrecision(sign * Math.pow(abs, 1 / 1.8), val); - } - return roundWithPrecision(16 * val, val); - }); -} - -export { gam_ProPhoto, gam_sRGB }; diff --git a/dist/lib/renderer/utils/colorspace/rgb.js b/dist/lib/renderer/utils/colorspace/rgb.js index 5c7914b..90b1c53 100644 --- a/dist/lib/renderer/utils/colorspace/rgb.js +++ b/dist/lib/renderer/utils/colorspace/rgb.js @@ -1,6 +1,5 @@ -function roundWithPrecision(value, original) { - return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); -} +import { roundWithPrecision } from './utils/round.js'; + // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 diff --git a/dist/lib/renderer/utils/colorspace/utils/matrix.js b/dist/lib/renderer/utils/colorspace/utils/matrix.js new file mode 100644 index 0000000..13d8beb --- /dev/null +++ b/dist/lib/renderer/utils/colorspace/utils/matrix.js @@ -0,0 +1,35 @@ +// from https://www.w3.org/TR/css-color-4/multiply-matrices.js +/** + * 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, B) { + let m = A.length; + if (!Array.isArray(A[0])) { + // A is vector, convert to [[a, b, c, ...]] + A = [A]; + } + if (!Array.isArray(B[0])) { + // B is vector, convert to [[a], [b], [c], ...]] + B = B.map((x) => [x]); + } + let p = B[0].length; + let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B + let 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); + })); + if (m === 1) { + product = product[0]; // Avoid [[a, b, c, ...]] + } + if (p === 1) { + return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] + } + return product; +} + +export { multiplyMatrices }; diff --git a/dist/lib/renderer/utils/colorspace/utils/round.js b/dist/lib/renderer/utils/colorspace/utils/round.js new file mode 100644 index 0000000..afedf2d --- /dev/null +++ b/dist/lib/renderer/utils/colorspace/utils/round.js @@ -0,0 +1,5 @@ +function roundWithPrecision(value, original) { + return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); +} + +export { roundWithPrecision }; diff --git a/dist/lib/renderer/utils/colorspace/xyz.js b/dist/lib/renderer/utils/colorspace/xyz.js new file mode 100644 index 0000000..bb9a47e --- /dev/null +++ b/dist/lib/renderer/utils/colorspace/xyz.js @@ -0,0 +1,34 @@ +import { multiplyMatrices } from './utils/matrix.js'; +import { roundWithPrecision } from './utils/round.js'; +import { gam_sRGB } from './rgb.js'; + +function XYZ_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); +} +function XYZ_to_lin_sRGB(x, y, z) { + // convert XYZ to linear-light sRGB + const M = [ + [12831 / 3959, -329 / 214, -1974 / 3959], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [705 / 12673, -2585 / 12673, 705 / 667], + ]; + const XYZ = [x, y, z]; // convert to XYZ + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); +} +function XYZ_D50_to_sRGB(x, y, z) { + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); +} +function D50_to_D65(x, y, z) { + // Bradford chromatic adaptation from D50 to D65 + const M = [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753] + ]; + const XYZ = [x, y, z]; + return multiplyMatrices(M, XYZ).map((v, index) => roundWithPrecision(v, XYZ[index])); +} + +export { D50_to_D65, XYZ_D50_to_sRGB, XYZ_to_lin_sRGB, XYZ_to_sRGB }; diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 57e108d..2a06e7f 100644 --- a/src/@types/token.d.ts +++ b/src/@types/token.d.ts @@ -342,6 +342,8 @@ export declare interface ImportantToken extends BaseToken { } export declare type ColorKind = 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk'; +// xyz-d65 is an alias for xyz +// display-p3 is an alias for srgb export declare type ColorSpace = 'srgb' | "prophoto-rgb" | "a98-rgb" | 'rec2020' | 'display-p3' diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index e24e968..bb7f3d1 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -34,6 +34,7 @@ import {SourceMap} from "./sourcemap"; import {isColor, isNewLine} from "../parser"; import {parseRelativeColor, RelativeColorTypes} from "./utils/relativecolor"; import {gam_ProPhoto, gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto} from "./utils/colorspace"; +import {XYZ_D50_to_sRGB, XYZ_to_sRGB} from "./utils/colorspace/xyz"; export const colorsFunc: string[] = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color']; @@ -433,7 +434,7 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { if (token.val == 'color') { - const supportedColorSpaces: ColorSpace[] = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020']; + const supportedColorSpaces: ColorSpace[] = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; if (((token.chi)[0]).typ == EnumToken.IdenTokenType && supportedColorSpaces.includes(((token.chi)[0]).val.toLowerCase())) { @@ -468,6 +469,15 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { // @ts-ignore values = gam_sRGB(...lin_2020(...values)); break; + case 'xyz': + case 'xyz-d65': + // @ts-ignore + values = XYZ_to_sRGB(...values); + break; + case 'xyz-d50': + // @ts-ignore + values = XYZ_D50_to_sRGB(...values); + break; } clampValues(values, colorSpace); diff --git a/src/lib/renderer/utils/colorspace/rgb.ts b/src/lib/renderer/utils/colorspace/rgb.ts index 9acfffa..d887ef3 100644 --- a/src/lib/renderer/utils/colorspace/rgb.ts +++ b/src/lib/renderer/utils/colorspace/rgb.ts @@ -1,11 +1,10 @@ -function roundWithPrecision(value: number, original: number): number { - return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); -} // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 +import {roundWithPrecision} from "./utils"; + export function gam_sRGB(r: number, g: number, b: number): number[] { // convert an array of linear-light sRGB values in the range 0.0-1.0 // to gamma corrected form diff --git a/src/lib/renderer/utils/colorspace/utils/index.ts b/src/lib/renderer/utils/colorspace/utils/index.ts new file mode 100644 index 0000000..3de02b0 --- /dev/null +++ b/src/lib/renderer/utils/colorspace/utils/index.ts @@ -0,0 +1,3 @@ + +export * from './matrix'; +export * from './round'; \ No newline at end of file diff --git a/src/lib/renderer/utils/colorspace/utils/matrix.ts b/src/lib/renderer/utils/colorspace/utils/matrix.ts new file mode 100644 index 0000000..3301e44 --- /dev/null +++ b/src/lib/renderer/utils/colorspace/utils/matrix.ts @@ -0,0 +1,45 @@ + +// from https://www.w3.org/TR/css-color-4/multiply-matrices.js +/** + * 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. +export function multiplyMatrices(A: number[] | number[][], B: number[] | number[][]): number[] { + let m: number = A.length; + + if (!Array.isArray(A[0])) { + // A is vector, convert to [[a, b, c, ...]] + A = [A]; + } + + if (!Array.isArray(B[0])) { + // B is vector, convert to [[a], [b], [c], ...]] + B = (B).map((x: number) => [x]); + } + + let p: number = (B)[0].length; + let B_cols: number[][] = (B)[0].map((_: number, i: number) => (B).map((x: number[]) => x[i])); // transpose B + let product: number[][] | number[] = (A).map((row: number[]) => B_cols.map((col: number[]): number => { + + if (!Array.isArray(row)) { + + return col.reduce((a: number, c: number) => a + c * row, 0); + } + + return row.reduce((a: number, c: number, i: number) => a + c * (col[i] || 0), 0); + })); + + if (m === 1) { + + product = product[0]; // Avoid [[a, b, c, ...]] + } + + if (p === 1) { + + return (product).map((x: number[]) => x[0]); // Avoid [[a], [b], [c], ...]] + } + + return product; +} diff --git a/src/lib/renderer/utils/colorspace/utils/round.ts b/src/lib/renderer/utils/colorspace/utils/round.ts new file mode 100644 index 0000000..f620f35 --- /dev/null +++ b/src/lib/renderer/utils/colorspace/utils/round.ts @@ -0,0 +1,6 @@ + + +export function roundWithPrecision(value: number, original: number): number { + + return +value.toFixed(original.toString().split('.')[1]?.length ?? 0); +} \ No newline at end of file diff --git a/src/lib/renderer/utils/colorspace/xyz.ts b/src/lib/renderer/utils/colorspace/xyz.ts new file mode 100644 index 0000000..a632e60 --- /dev/null +++ b/src/lib/renderer/utils/colorspace/xyz.ts @@ -0,0 +1,40 @@ +import {multiplyMatrices, roundWithPrecision} from "./utils"; +import {gam_sRGB} from "./rgb"; + +export function XYZ_to_sRGB(x: number, y: number, z: number): number[] { + + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(x, y, z)); +} + +export function XYZ_to_lin_sRGB(x: number, y: number, z: number): number[] { + // convert XYZ to linear-light sRGB + + const M: number[][] = [ + [ 12831 / 3959, -329 / 214, -1974 / 3959 ], + [ -851781 / 878810, 1648619 / 878810, 36519 / 878810 ], + [ 705 / 12673, -2585 / 12673, 705 / 667 ], + ]; + + const XYZ: number[] = [x, y, z]; // convert to XYZ + + return multiplyMatrices(M, XYZ).map((v: number, index: number) => roundWithPrecision(v, XYZ[index])); +} + +export function XYZ_D50_to_sRGB(x: number, y: number, z: number): number[] { + + // @ts-ignore + return gam_sRGB(...XYZ_to_lin_sRGB(...D50_to_D65(x, y, z))); +} + +export function D50_to_D65(x: number, y: number, z: number): number[] { + // Bradford chromatic adaptation from D50 to D65 + const M: number[][] = [ + [ 0.9554734527042182, -0.023098536874261423, 0.0632593086610217 ], + [ -0.028369706963208136, 1.0099954580058226, 0.021041398966943008 ], + [ 0.012314001688319899, -0.020507696433477912, 1.3303659366080753 ] + ]; + const XYZ: number[] = [x, y, z]; + + return multiplyMatrices(M, XYZ).map((v: number, index: number) => roundWithPrecision(v, XYZ[index])); +} \ No newline at end of file diff --git a/test/specs/code/color.js b/test/specs/code/color.js index b69ab65..19c3563 100644 --- a/test/specs/code/color.js +++ b/test/specs/code/color.js @@ -332,8 +332,6 @@ color: color(a98-rgb 0.4961 0.4961 0.4961); }`)); }); - - it('color(rec2020 0.45004 0.45004 0.45004) #35', function () { return parse(` .selector { @@ -343,5 +341,55 @@ color: color(rec2020 0.45004 0.45004 0.45004); }`)); }); + it('color(xyz-d50 0.43607, 0.22249, 0.01392) #36', function () { + return parse(` +.selector { +color: color(xyz-d50 0.43607 0.22249 0.01392); +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: red +}`)); + }); + + it('color(xyz-d50 0.58098 0.49223 0.05045) #37', function () { + return parse(` +.selector { +color: color(xyz-d50 0.58098 0.49223 0.05045); +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: orange +}`)); + }); + + it('color(xyz 0.20344 0.21404 0.2331) #38', function () { + return parse(` +.selector { +color: color(xyz 0.20344 0.21404 0.2331); +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: grey +}`)); + }); + + it('color(xyz 0.41239 0.21264 0.01933) #39', function () { + return parse(` +.selector { +color: color(xyz 0.41239 0.21264 0.01933); +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: red +}`)); + }); + + it('color(xyz 0.54694 0.48173 0.06418) #40', function () { + return parse(` +.selector { +color: color(xyz 0.54694 0.48173 0.06418); +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: orange +}`)); + }); + + + + + + // } \ No newline at end of file