Skip to content

Commit

Permalink
Use d50 referent instead of d65 for lab (and by extension hcl) color …
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLoer committed Apr 28, 2023
1 parent f1607d1 commit dfccd37
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 37 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

### 🐛 Bug fixes

* Fix color interpolation in HCL and LAB color spaces to use d50 white point instead of d65, in conformance with CSS spec [#146](https://github.com/maplibre/maplibre-style-spec/pull/146)

* Fix incorrect color interpolation in HCL and LAB color spaces when interpolation results are outside the sRGB gamut. [#94](https://github.com/maplibre/maplibre-style-spec/pull/94)

## 18.0.0
Expand Down
4 changes: 2 additions & 2 deletions src/function/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ describe('exponential function', () => {
}).evaluate;

expectToMatchColor(f({zoom: 0}, undefined), 'rgb(14.12% 38.43% 14.12% / .6)', 4);
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(0% 35.71% 46.73% / .8)', 4);
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(0.00% 35.13% 43.84% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined), 'rgb(8.64% 22.66% 55.36% / 1)', 4);
});

Expand All @@ -205,7 +205,7 @@ describe('exponential function', () => {
}).evaluate;

expectToMatchColor(f({zoom: 0}, undefined), 'rgb(0% 0% 0% / .6)');
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(14.29% 46.73% 46.63% / .8)', 4);
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(14.15% 46.74% 46.63% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined), 'rgb(0% 100% 100% / 1)');
});

Expand Down
40 changes: 20 additions & 20 deletions src/util/color_spaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ describe('color spaces', () => {
test('should convert colors from sRGB to LAB color space', () => {
expectCloseToArray(rgbToLab([0, 0, 0, 1]), [0, 0, 0, 1]);
expectCloseToArray(rgbToLab([1, 1, 1, 1]), [100, 0, 0, 1], 4);
expectCloseToArray(rgbToLab([0, 1, 0, 1]), [87.73, -86.18, 83.18, 1], 2);
expectCloseToArray(rgbToLab([0, 1, 1, 1]), [91.11, -48.09, -14.13, 1], 2);
expectCloseToArray(rgbToLab([0, 0, 1, 1]), [32.3, 79.19, -107.86, 1], 2);
expectCloseToArray(rgbToLab([1, 1, 0, 1]), [97.14, -21.55, 94.48, 1], 2);
expectCloseToArray(rgbToLab([1, 0, 0, 1]), [53.24, 80.09, 67.2, 1], 2);
expectCloseToArray(rgbToLab([0, 1, 0, 1]), [87.82, -79.29, 80.99, 1], 2);
expectCloseToArray(rgbToLab([0, 1, 1, 1]), [90.67, -50.67, -14.96, 1], 2);
expectCloseToArray(rgbToLab([0, 0, 1, 1]), [29.57, 68.3, -112.03, 1], 2);
expectCloseToArray(rgbToLab([1, 1, 0, 1]), [97.61, -15.75, 93.39, 1], 2);
expectCloseToArray(rgbToLab([1, 0, 0, 1]), [54.29, 80.81, 69.89, 1], 2);
});

test('should convert colors from LAB to sRGB color space', () => {
expectCloseToArray(labToRgb([0, 0, 0, 1]), [0, 0, 0, 1]);
expectCloseToArray(labToRgb([100, 0, 0, 1]), [1, 1, 1, 1]);
expectCloseToArray(labToRgb([50, 50, 0, 1]), [0.7605, 0.3096, 0.4734, 1], 4);
expectCloseToArray(labToRgb([70, -45, 0, 1]), [0.0469, 0.7537, 0.6656, 1], 4);
expectCloseToArray(labToRgb([70, 0, 70, 1]), [0.7955, 0.6590, 0.0818, 1], 4);
expectCloseToArray(labToRgb([55, 0, -60, 1]), [0, 0.5403, 0.9255, 1], 4);
expectCloseToArray(labToRgb([32.3, 79.19, -107.86, 1]), [0, 0, 1, 1], 3);
expectCloseToArray(labToRgb([50, 50, 0, 1]), [0.7562, 0.3045, 0.4756, 1], 4);
expectCloseToArray(labToRgb([70, -45, 0, 1]), [0.1079, 0.7556, 0.6640, 1], 4);
expectCloseToArray(labToRgb([70, 0, 70, 1]), [0.7663, 0.6636, 0.0558, 1], 4);
expectCloseToArray(labToRgb([55, 0, -60, 1]), [0.1281, 0.5310, 0.9276, 1], 4);
expectCloseToArray(labToRgb([29.57, 68.3, -112.03, 1]), [0, 0, 1, 1], 3);
});

});
Expand All @@ -32,21 +32,21 @@ describe('color spaces', () => {
test('should convert colors from sRGB to HCL color space', () => {
expectCloseToArray(rgbToHcl([0, 0, 0, 1]), [NaN, 0, 0, 1]);
expectCloseToArray(rgbToHcl([1, 1, 1, 1]), [NaN, 0, 100, 1], 4);
expectCloseToArray(rgbToHcl([0, 1, 0, 1]), [136.02, 119.78, 87.73, 1], 2);
expectCloseToArray(rgbToHcl([0, 1, 1, 1]), [196.38, 50.12, 91.11, 1], 2);
expectCloseToArray(rgbToHcl([0, 0, 1, 1]), [306.28, 133.81, 32.30, 1], 2);
expectCloseToArray(rgbToHcl([1, 1, 0, 1]), [102.85, 96.91, 97.14, 1], 2);
expectCloseToArray(rgbToHcl([1, 0, 0, 1]), [40.00, 104.55, 53.24, 1], 2);
expectCloseToArray(rgbToHcl([0, 1, 0, 1]), [134.39, 113.34, 87.82, 1], 2);
expectCloseToArray(rgbToHcl([0, 1, 1, 1]), [196.45, 52.83, 90.67, 1], 2);
expectCloseToArray(rgbToHcl([0, 0, 1, 1]), [301.37, 131.21, 29.57, 1], 2);
expectCloseToArray(rgbToHcl([1, 1, 0, 1]), [99.57, 94.71, 97.61, 1], 2);
expectCloseToArray(rgbToHcl([1, 0, 0, 1]), [40.85, 106.84, 54.29, 1], 2);
});

test('should convert colors from HCL to sRGB color space', () => {
expectCloseToArray(hclToRgb([0, 0, 0, 1]), [0, 0, 0, 1]);
expectCloseToArray(hclToRgb([0, 0, 100, 1]), [1, 1, 1, 1]);
expectCloseToArray(hclToRgb([0, 50, 50, 1]), [0.7605, 0.3096, 0.4734, 1], 4);
expectCloseToArray(hclToRgb([180, 45, 70, 1]), [0.0469, 0.7537, 0.6656, 1], 4);
expectCloseToArray(hclToRgb([90, 70, 70, 1]), [0.7955, 0.6590, 0.0818, 1], 4);
expectCloseToArray(hclToRgb([270, 60, 55, 1]), [0, 0.5403, 0.9255, 1], 4);
expectCloseToArray(hclToRgb([306.28, 133.81, 32.30, 1]), [0, 0, 1, 1], 3);
expectCloseToArray(hclToRgb([0, 50, 50, 1]), [0.7562, 0.3045, 0.4756, 1], 4);
expectCloseToArray(hclToRgb([180, 45, 70, 1]), [0.1079, 0.7556, 0.6640, 1], 4);
expectCloseToArray(hclToRgb([90, 70, 70, 1]), [0.7663, 0.6636, 0.0558, 1], 4);
expectCloseToArray(hclToRgb([270, 60, 55, 1]), [0.1281, 0.5310, 0.9276, 1], 4);
expectCloseToArray(hclToRgb([301.37, 131.21, 29.57, 1]), [0, 0, 1, 1], 3);
});

});
Expand Down
23 changes: 14 additions & 9 deletions src/util/color_spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export type HCLColor = [h: number, c: number, l: number, alpha: number];
*/
export type LABColor = [l: number, a: number, b: number, alpha: number];

// Constants
const Xn = 0.950470, // D65 standard referent
// See https://observablehq.com/@mbostock/lab-and-rgb
const Xn = 0.96422,
Yn = 1,
Zn = 1.088830,
Zn = 0.82521,
t0 = 4 / 29,
t1 = 6 / 29,
t2 = 3 * t1 * t1,
Expand All @@ -53,9 +53,14 @@ export function rgbToLab([r, g, b, alpha]: RGBColor): LABColor {
r = rgb2xyz(r);
g = rgb2xyz(g);
b = rgb2xyz(b);
const x = xyz2lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
const y = xyz2lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / Yn);
const z = xyz2lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / Zn);
let x, z;
const y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn);
if (r === g && g === b) {
x = z = y;
} else {
x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
}

const l = 116 * y - 16;
return [(l < 0) ? 0 : l, 500 * (x - y), 200 * (y - z), alpha];
Expand All @@ -79,9 +84,9 @@ export function labToRgb([l, a, b, alpha]: LABColor): RGBColor {
z = Zn * lab2xyz(z);

return [
xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z), // D65 -> sRGB
xyz2rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z),
xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z),
xyz2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), // D50 -> sRGB
xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),
xyz2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z),
alpha,
];
}
Expand Down
12 changes: 6 additions & 6 deletions src/util/interpolate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ describe('interpolate', () => {

const i11nFn = (t: number) => interpolate.color(color, targetColor, t, 'hcl');
expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 100% / 1)');
expectToMatchColor(i11nFn(0.25), 'rgb(0% 53.05% 100% / 0.9)', 4);
expectToMatchColor(i11nFn(0.50), 'rgb(0% 72.97% 100% / 0.8)', 4);
expectToMatchColor(i11nFn(0.75), 'rgb(0% 88.42% 67.80% / 0.7)', 4);
expectToMatchColor(i11nFn(0.25), 'rgb(0% 49.37% 100% / 0.9)', 4);
expectToMatchColor(i11nFn(0.50), 'rgb(0% 70.44% 100% / 0.8)', 4);
expectToMatchColor(i11nFn(0.75), 'rgb(0% 87.54% 63.18% / 0.7)', 4);
expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 0.6)');
});

Expand All @@ -69,9 +69,9 @@ describe('interpolate', () => {

const i11nFn = (t: number) => interpolate.color(color, targetColor, t, 'lab');
expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 100% / 1)');
expectToMatchColor(i11nFn(0.25), 'rgb(42.40% 35.65% 82.90% / 0.9)', 4);
expectToMatchColor(i11nFn(0.50), 'rgb(49.19% 57.81% 65.10% / 0.8)', 4);
expectToMatchColor(i11nFn(0.75), 'rgb(43.61% 78.93% 44.66% / 0.7)', 4);
expectToMatchColor(i11nFn(0.25), 'rgb(39.64% 34.55% 83.36% / 0.9)', 4);
expectToMatchColor(i11nFn(0.50), 'rgb(46.42% 56.82% 65.91% / 0.8)', 4);
expectToMatchColor(i11nFn(0.75), 'rgb(41.45% 78.34% 45.62% / 0.7)', 4);
expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 0.6)');
});

Expand Down

0 comments on commit dfccd37

Please sign in to comment.