Skip to content

Commit

Permalink
Final clip is required in Ray tracing
Browse files Browse the repository at this point in the history
This is probably as far as we can take ray tracing.

It was though that the last correction and clip could be omitted, but
it is required to get the optimal deltas. This time confirmed with
a script. Max deltas: ∆L = 0.011682128741787201 ∆h = -4.212684767726387

Previously, there was still a region (in yellow) that could exceed this.
(before ∆L = 0.0011833203080477972 ∆h = -8.31632698148956).
  • Loading branch information
facelessuser committed Feb 27, 2024
1 parent 8ae3b44 commit 599341f
Showing 1 changed file with 28 additions and 21 deletions.
49 changes: 28 additions & 21 deletions apps/gamut-mapping/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const methods = {
},
"raytrace": {
label: "Raytrace",
description: "Uses ray tracing find a color with reduced chroma on the RGB surface.",
description: "Uses ray tracing to find a color with reduced chroma on the RGB surface.",
compute: (color) => {
if (color.inGamut("p3", { epsilon: 0 })) {
return color.to("p3");
Expand All @@ -72,43 +72,50 @@ const methods = {
trace: (mapColor) => {
let achroma = mapColor.clone().set("c", 0).to("p3-linear");
let gamutColor = mapColor.clone().to("p3-linear");
let size = [1, 1, 1];
let [face, intersection] = methods.raytrace.raytrace_box(size, gamutColor.coords, achroma.coords);

// Intersect with the RGB cube
if (face) {
let [r, g, b] = intersection;
gamutColor.set({r: r, g: g, b: b});
gamutColor.set({
"oklch.l": mapColor.coords[0],
"oklch.h": mapColor.coords[2],
});

// See if we are still in/on the cube after correction
[face, intersection] = methods.raytrace.raytrace_box(size, gamutColor.coords, achroma.coords);
// Create a line from our color to color with zero lightness.
// Trace the line to the RGB cube finding the face and the point where it intersects.
// Take two rounds to get us as close as we can get.
let size = [1, 1, 1];
let iter = 2;
while (iter--) {
let [face, intersection] = methods.raytrace.raytrace_box(size, gamutColor.coords, achroma.coords);
if (face) {
let [r, g, b] = intersection;
gamutColor.set({r: r, g: g, b: b});
gamutColor.set({
"oklch.l": mapColor.coords[0],
"oklch.h": mapColor.coords[2],
});
gamutColor.set({"oklch.l": mapColor.coords[0], "oklch.h": mapColor.coords[2]});
}
}

// We may be under saturated, so extend the vector outside the cube and make one final pass.
// This will place us on the cube surface with reduced chroma and very little to no lightness or hue shift.
// We might be under saturated now, so extend the vector out,
// ignoring the original point and find the surface one last
// Give us the most saturated color at that point on the RGB cube.
let [x1, y1, z1] = achroma.coords;
let [x2, y2, z2] = gamutColor.coords;
let x3 = x2 + (x2 - x1) * 100;
let y3 = y2 + (y2 - y1) * 100;
let z3 = z2 + (z2 - z1) * 100;
[face, intersection] = methods.raytrace.raytrace_box(size, [x3, y3, z3], achroma.coords);
let [face, intersection] = methods.raytrace.raytrace_box(size, [x3, y3, z3], achroma.coords);
if (face) {
let [r, g, b] = intersection;
gamutColor.set({r: r, g: g, b: b});
gamutColor.set({"oklch.l": mapColor.coords[0], "oklch.h": mapColor.coords[2]});
}

gamutColor.set(
{
r: c => {
return util.clamp(0, c, 1);
},
g: c => {
return util.clamp(0, c, 1);
},
b: c => {
return util.clamp(0, c, 1);
},
},
);

return gamutColor.to("p3");
},
raytrace_box: (size, start, end) => {
Expand Down

0 comments on commit 599341f

Please sign in to comment.