- From: Dan Burzo via GitHub <sysbot+gh@w3.org>
- Date: Sun, 26 Mar 2023 17:11:39 +0000
- To: public-css-archive@w3.org
This is my first pass at the algorithm: <details> <summary>Expand JavaScript code</summary> ```js const OKLAB_TO_LMS = [ [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339], [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402], [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399] ]; const LMS_TO_XYZ_D65 = [ [1.2268798733741557, -0.5578149965554813, 0.28139105017721583], [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418] ]; const XYZ_D65_TO_LRGB = [ [3.2409699419045226, -1.537383177570094, -0.4986107602930034], [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559], [0.05563007969699366, -0.20397695888897652, 1.0569715142428786] ]; const LMS_TO_LRGB = multiplyMatrices(XYZ_D65_TO_LRGB, LMS_TO_XYZ_D65); function multiplyMatrices(a, b) { let res = [[], [], []]; for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { res[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; } } return res; } function multiplyMatrixWithVector(m, v) { return [ m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2], m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2], m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2] ]; } function multiplyComponentWise(arr1, arr2) { return arr1.map((it, i) => it * arr2[i]); } function multiplyVectors(arr1, arr2) { return arr1.reduce((acc, curr, i) => { return acc + curr * arr2[i]; }, 0); } export function GamutMapNewton(original) { const zero_a_b = [0, original[1], original[2]]; let alpha = 1; let res_oklab = original.slice(); let res_rgb = multiplyMatrixWithVector( LMS_TO_LRGB, multiplyMatrixWithVector(OKLAB_TO_LMS, res_oklab).map(it => it ** 3) ); for (let comp = 0; comp < 3; comp++) { if (res_rgb[comp] >= 0 && res_rgb[comp] <= 1) continue; let target = res_rgb[comp] > 1 ? 1 : 0; for (let iter = 0; iter < 6; iter++) { let residual = multiplyVectors( LMS_TO_LRGB[comp], multiplyMatrixWithVector(OKLAB_TO_LMS, res_oklab).map(it => it ** 3) ) - target; if (Math.abs(residual) < 1e-15) break; let gradient = multiplyVectors( LMS_TO_LRGB[comp], multiplyComponentWise( multiplyMatrixWithVector(OKLAB_TO_LMS, res_oklab).map(it => 3 * it ** 2), multiplyMatrixWithVector(OKLAB_TO_LMS, zero_a_b) ) ); alpha -= residual / gradient; res_oklab[1] = alpha * original[1]; res_oklab[2] = alpha * original[2]; } res_rgb = multiplyMatrixWithVector( LMS_TO_LRGB, multiplyMatrixWithVector(OKLAB_TO_LMS, res_oklab).map(it => it ** 3) ); } return [res_oklab, res_rgb]; } ``` </details> I've updated [my gamut mapping demo page](https://danburzo.ro/demos/color/gamut-mapping.html) to include this new algorithm (see the _newton (@ccameron)_ method). There seem to be some visual glitches outside the sRGB gamut, so I can't exclude translation errors on my part either in the code above, or in the glue code for the demo page, but I hope it's a good starting point. <img width="659" alt="comparison between chroma-reduction algorithm based on binary search and newton method shows visual glitches in the latter for hue = 104" src="https://user-images.githubusercontent.com/205375/227792268-34954e3f-3b5a-4885-81a0-25fde73f0528.png"> Assuming the algorithm is sound and the glitches can be ironed out, what I'm observing is that it's very close to (indistinguishable from) basic binary search (the `chroma-reduce` method) when rendering lch() / oklch() / etc. canvases, but seems to converge faster to the solution. (I'll update the demo page to include lab() / oklab() canvases) -- GitHub Notification of comment by danburzo Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7653#issuecomment-1484161031 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Sunday, 26 March 2023 17:11:41 UTC