Skip to content

Commit

Permalink
apply changes
Browse files Browse the repository at this point in the history
  • Loading branch information
straker committed Mar 12, 2024
1 parent 6ea3b48 commit a0b8822
Showing 1 changed file with 25 additions and 100 deletions.
125 changes: 25 additions & 100 deletions lib/commons/color/flatten-colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,23 @@ const blendFunctions = {
// an not individual color components (red, green, blue)
hue(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendinghue
return setLuminosity(setSaturation(Cs, saturation(Cb)), luminosity(Cb));
return Cs.setSaturation(Cb.getSaturation()).setLuminosity(
Cb.getLuminosity()
);
},
saturation(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingsaturation
return setLuminosity(setSaturation(Cb, saturation(Cs)), luminosity(Cb));
return Cb.setSaturation(Cs.getSaturation()).setLuminosity(
Cb.getLuminosity()
);
},
color(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingcolor
return setLuminosity(Cs, luminosity(Cb));
return Cs.setLuminosity(Cb.getLuminosity());
},
luminosity(Cb, Cs) {
// @see https://www.w3.org/TR/compositing-1/#blendingluminosity
return setLuminosity(Cb, luminosity(Cs));
return Cb.setLuminosity(Cs.getLuminosity());
}
};

Expand All @@ -95,40 +99,30 @@ export default function flattenColors(
backdrop,
blendMode = 'normal'
) {
let blendingResult;
if (nonSeparableBlendModes.includes(blendMode)) {
// Note: Cs and Cb values need to be between 0 and 1 inclusive for the blend function
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
const Cs = sourceColor.divide(255);
const Cb = backdrop.divide(255);

blendingResult = blendFunctions[blendMode](Cb, Cs).multiply(255);
}
const blendingResult = blend(backdrop, sourceColor, blendMode);

// foreground is the "source" color and background is the "backdrop" color
const r = simpleAlphaCompositing(
sourceColor.red,
sourceColor.alpha,
backdrop.red,
backdrop.alpha,
blendMode,
blendingResult?.red
// we don't want to round the blended value
blendingResult.r * 255
);
const g = simpleAlphaCompositing(
sourceColor.green,
sourceColor.alpha,
backdrop.green,
backdrop.alpha,
blendMode,
blendingResult?.green
blendingResult.g * 255
);
const b = simpleAlphaCompositing(
sourceColor.blue,
sourceColor.alpha,
backdrop.blue,
backdrop.alpha,
blendMode,
blendingResult?.blue
blendingResult.b * 255
);

// formula: αo = αs + αb x (1 - αs)
Expand All @@ -151,11 +145,11 @@ export default function flattenColors(
//
// RGB color space doesn't have decimal values so we will follow what browsers do and round
// e.g. rgb(255.2, 127.5, 127.8) === rgb(255, 128, 128)
const Cred = Math.round(r / αo);
const Cgreen = Math.round(g / αo);
const Cblue = Math.round(b / αo);
const Cr = Math.round(r / αo);
const Cg = Math.round(g / αo);
const Cb = Math.round(b / αo);

return new Color(Cred, Cgreen, Cblue, αo);
return new Color(Cr, Cg, Cb, αo);
}

// Simple Alpha Compositing written as non-premultiplied.
Expand All @@ -167,92 +161,23 @@ export default function flattenColors(
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
// @see https://www.w3.org/TR/compositing-1/#blending
// @see https://ciechanow.ski/alpha-compositing/
function simpleAlphaCompositing(Cs, αs, Cb, αb, blendMode, blendingResult) {
return (
αs * (1 - αb) * Cs +
αs *
αb *
(blendingResult ??
// Note: Cs and Cb values need to be between 0 and 1 inclusive for the blend function
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
blendFunctions[blendMode](Cb / 255, Cs / 255) * 255) +
(1 - αs) * αb * Cb
);
function simpleAlphaCompositing(Cs, αs, Cb, αb, blendingResult) {
return αs * (1 - αb) * Cs + αs * αb * blendingResult + (1 - αs) * αb * Cb;
}

// clamp a value between two numbers (inclusive)
function clamp(value, min, max) {
return Math.min(Math.max(min, value), max);
}

// following functions taken from the spec
// @see https://www.w3.org/TR/compositing-1/#blendingnonseparable
function luminosity(color) {
return 0.3 * color.red + 0.59 * color.green + 0.11 * color.blue;
}

function clipColor(color) {
const L = luminosity(color);
const n = Math.min(color.red, color.green, color.blue);
const x = Math.max(color.red, color.green, color.blue);

if (n < 0) {
return new Color(
L + ((color.red - L) * L) / (L - n),
L + ((color.green - L) * L) / (L - n),
L + ((color.blue - L) * L) / (L - n),
color.alpha
);
}

if (x > 1) {
return new Color(
L + ((color.red - L) * (1 - L)) / (x - L),
L + ((color.green - L) * (1 - L)) / (x - L),
L + ((color.blue - L) * (1 - L)) / (x - L),
color.alpha
);
function blend(Cb, Cs, blendMode) {
if (nonSeparableBlendModes.includes(blendMode)) {
return blendFunctions[blendMode](Cb, Cs);
}

return color;
}

function setLuminosity(color, L) {
const d = L - luminosity(color);
return clipColor(color.add(d));
}

function saturation(color) {
return (
Math.max(color.red, color.green, color.blue) -
Math.min(color.red, color.green, color.blue)
);
}

function setSaturation(color, s) {
const C = new Color(color.red, color.green, color.blue, color.alpha);
const colorEntires = Object.entries(C)
.filter(([prop]) => prop !== 'alpha')
.map(([name, value]) => {
return { name, value };
});

// find the min, mid, and max values of the color components
const [Cmin, Cmid, Cmax] = colorEntires.sort((a, b) => {
return a.value - b.value;
const C = new Color();
['r', 'g', 'b'].forEach(channel => {
C[channel] = blendFunctions[blendMode](Cb[channel], Cs[channel]);
});

if (Cmax.value > Cmin.value) {
Cmid.value = ((Cmid.value - Cmin.value) * s) / (Cmax.value - Cmin.value);
Cmax.value = s;
} else {
Cmid.value = Cmax.value = 0;
}

Cmin.value = 0;

C[Cmax.name] = Cmax.value;
C[Cmin.name] = Cmin.value;
C[Cmid.name] = Cmid.value;
return C;
}

0 comments on commit a0b8822

Please sign in to comment.