matisse
Version:
TypeScript library for mutable colour conversion and manipulation 🎨
257 lines • 12.8 kB
JavaScript
import Colour, { mix, tint, shade, tone, normal, multiply, screen, overlay, darken, lighten, colourDodge, colourBurn, hardLight, softLight, difference, exclusion } from "../index";
var colour1;
var colour2;
var colour3;
var black;
var white;
beforeEach(() => {
colour1 = Colour.RGB(250, 200, 0, 0.6);
colour2 = Colour.RGB(50, 150, 75, 0.4);
colour3 = new Colour("#1D5D85");
black = new Colour("#000000");
white = new Colour("#FFFFFF");
});
test('normal blend', () => {
let actual = normal(colour1, colour2);
let expected = Colour.RGB(145, 174, 39, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
describe("Alpha Composition Edge Cases", () => {
it("should handle blending when base and blend alphas are 0", () => {
const base = Colour.RGB(100, 100, 100, 0); // base alpha = 0
const blend = Colour.RGB(200, 200, 200, 0); // blend alpha = 0
// Using 'normal' blend mode, but any blend mode that uses _alphaComposition would work
const result = normal(base, blend);
// Expectations:
// compositeAlpha will be 0.
// _alphaComposition will internally calculate with 0/0 (NaN) for the division part.
// The final channel values might be NaN before _constrain,
// but _constrain will likely clamp them.
// The most important part is that compositeAlpha is 0.
// The RGB values might effectively be from the blend colour if base is fully transparent,
// or a mix depending on how NaN propagates and is clamped.
// Let's verify the resulting alpha and ensure RGB values are numbers (not NaN).
expect(result.alpha).toBe(0);
// Initial thought was they would not be NaN due to _constrain, but
// _constrain(NaN, min, max) results in NaN.
// And Colour.RGB calls roundInt, and roundInt(NaN) is NaN.
//
// Re-verified _alphaComposition logic:
// resultChannel = roundInt((1 - baseAlpha) * blendChannel + baseAlpha * compositeChannel);
// If baseAlpha = 0, resultChannel = roundInt(blendChannel);
// final = (1 - blendAlpha/compositeAlpha)*baseChannel + (blendAlpha/compositeAlpha)*resultChannel
// If baseAlpha = 0, blendAlpha = 0, compositeAlpha = 0:
// final = (1 - 0/0)*baseChannel + (0/0)*roundInt(blendChannel)
// final = NaN * baseChannel + NaN * roundInt(blendChannel) = NaN.
// So, red, green, blue should be NaN.
expect(isNaN(result.red)).toBe(true); // Expecting NaN
expect(isNaN(result.green)).toBe(true); // Expecting NaN
expect(isNaN(result.blue)).toBe(true); // Expecting NaN
});
it("should handle blending when blendAlpha is 0 and compositeAlpha is > 0 (base opaque, blend transparent)", () => {
// Case A: blendAlpha = 0, compositeAlpha > 0. Expect _alphaComposition to return baseChannel.
// Base is opaque, Blend is fully transparent.
const base = Colour.RGB(100, 110, 120, 1); // baseAlpha = 1
const blendCol = Colour.RGB(200, 210, 220, 0); // blendAlpha = 0
// compositeAlpha = blendCol.alpha + base.alpha - blendCol.alpha * base.alpha
// compositeAlpha = 0 + 1 - 0 * 1 = 1.
// resultChannel (for red) in _alphaComposition will be:
// C_blend_prime = normal_blend_result_for_red_channel (which is blendCol.red = 200 for normal blend)
// C_result_intermediate = roundInt((1 - base.alpha) * blendCol.red + base.alpha * C_blend_prime)
// C_result_intermediate = roundInt((1-1)*200 + 1*200) = roundInt(200) = 200.
// _alphaComposition for red:
// Co = (1 - blendCol.alpha/compositeAlpha)*base.red + (blendCol.alpha/compositeAlpha)*C_result_intermediate
// Co = (1 - 0/1)*100 + (0/1)*200 = 1 * 100 + 0 * 200 = 100. This is base.red.
const result = normal(base, blendCol);
expect(result.alpha).toBe(1); // compositeAlpha
expect(result.red).toBe(base.red); // Should be baseChannel
expect(result.green).toBe(base.green); // Should be baseChannel
expect(result.blue).toBe(base.blue); // Should be baseChannel
});
it("should handle blending when blendAlpha equals compositeAlpha and compositeAlpha is > 0 (base transparent, blend opaque)", () => {
// Case B: blendAlpha = compositeAlpha, compositeAlpha > 0. Expect _alphaComposition to return resultChannel.
// Base is fully transparent, Blend is opaque.
const base = Colour.RGB(100, 110, 120, 0); // baseAlpha = 0
const blendCol = Colour.RGB(200, 210, 220, 1); // blendAlpha = 1
// compositeAlpha = blendCol.alpha + base.alpha - blendCol.alpha * base.alpha
// compositeAlpha = 1 + 0 - 1 * 0 = 1.
// Here, blendCol.alpha (1) is equal to compositeAlpha (1).
// C_blend_prime (for red) = normal_blend_result_for_red_channel (which is blendCol.red = 200 for normal blend)
// C_result_intermediate = roundInt((1 - base.alpha) * blendCol.red + base.alpha * C_blend_prime)
// C_result_intermediate = roundInt((1-0)*200 + 0*200) = roundInt(200) = 200. (This is blendCol.red)
// _alphaComposition for red:
// Co = (1 - blendCol.alpha/compositeAlpha)*base.red + (blendCol.alpha/compositeAlpha)*C_result_intermediate
// Co = (1 - 1/1)*100 + (1/1)*200 = 0 * 100 + 1 * 200 = 200. This is C_result_intermediate (blendCol.red).
const result = normal(base, blendCol);
expect(result.alpha).toBe(1); // compositeAlpha
// The C_result_intermediate for normal blend when base is transparent is effectively the blend channel.
expect(result.red).toBe(blendCol.red);
expect(result.green).toBe(blendCol.green);
expect(result.blue).toBe(blendCol.blue);
});
it("should handle blending with fractional alpha values", () => {
// Case C: blendAlpha and compositeAlpha are non-zero, blendAlpha !== compositeAlpha.
// This should hit the fractional calculation in _alphaComposition.
const base = Colour.RGB(10, 20, 30, 0.5); // baseAlpha = 0.5
const blendCol = Colour.RGB(200, 210, 220, 0.5); // blendAlpha = 0.5
// compositeAlpha = blendCol.alpha + base.alpha - blendCol.alpha * base.alpha
// compositeAlpha = 0.5 + 0.5 - (0.5 * 0.5) = 1 - 0.25 = 0.75.
// For normal blend mode:
// C_blend_prime_red = blendCol.red = 200
// C_result_intermediate_red = roundInt((1 - base.alpha) * blendCol.red + base.alpha * C_blend_prime_red)
// C_result_intermediate_red = roundInt((1 - 0.5) * 200 + 0.5 * 200) = roundInt(0.5 * 200 + 0.5 * 200) = roundInt(200) = 200.
// _alphaComposition for red:
// Co_red = (1 - blendCol.alpha/compositeAlpha)*base.red + (blendCol.alpha/compositeAlpha)*C_result_intermediate_red
// Co_red = (1 - 0.5/0.75)*10 + (0.5/0.75)*200
// Co_red = (1 - 2/3)*10 + (2/3)*200
// Co_red = (1/3)*10 + (2/3)*200 = 3.333... + 133.333... = 136.666...
// After Colour.RGB calls roundInt, this should be 137.
// Let's trace for green (base: 20, blend: 210)
// C_result_intermediate_green = roundInt((1-0.5)*210 + 0.5*210) = 210
// Co_green = (1/3)*20 + (2/3)*210 = 6.666... + 140 = 146.666... -> roundInt -> 147
// Let's trace for blue (base: 30, blend: 220)
// C_result_intermediate_blue = roundInt((1-0.5)*220 + 0.5*220) = 220
// Co_blue = (1/3)*30 + (2/3)*220 = 10 + 146.666... = 156.666... -> roundInt -> 157
const result = normal(base, blendCol);
expect(result.alpha).toBe(0.75);
expect(result.red).toBe(137);
expect(result.green).toBe(147);
expect(result.blue).toBe(157);
});
});
test('multiply blend', () => {
let actual = multiply(colour1, colour2);
let expected = Colour.RGB(144, 164, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('screen blend', () => {
let actual = screen(colour1, colour2);
let expected = Colour.RGB(208, 199, 39, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('overlay blend', () => {
let actual = overlay(colour1, colour2);
let expected = Colour.RGB(207, 193, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('darken blend', () => {
let actual = darken(colour1, colour2);
let expected = Colour.RGB(145, 174, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('lighten blend', () => {
let actual = lighten(colour1, colour2);
let expected = Colour.RGB(208, 189, 39, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('colourDodge blend', () => {
let actual = colourDodge(colour1, colour2);
let expected = Colour.RGB(209, 207, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('colourBurn blend', () => {
let actual = colourBurn(colour1, colour2);
let expected = Colour.RGB(201, 177, 16, 0.76);
expect(actual.red).toBeCloseTo(expected.red, 2);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('hardLight blend', () => {
let actual = hardLight(colour1, colour2);
let expected = Colour.RGB(160, 193, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('softLight blend', () => {
let actual = softLight(colour1, colour2);
let expected = Colour.RGB(207, 191, 16, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('difference blend', () => {
let actual = difference(colour1, colour2);
let expected = Colour.RGB(192, 142, 39, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('exclusion blend', () => {
let actual = exclusion(colour1, colour2);
let expected = Colour.RGB(193, 163, 39, 0.76);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('mix', () => {
let actual = mix(new Colour("cyan"), new Colour("yellow"), 0.3);
let expected = Colour.RGB(77, 255, 179);
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('tint', () => {
let actual = tint(colour3, 1);
let expected = new Colour("#FFFFFF");
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('shade', () => {
let actual = shade(colour3, 1);
let expected = new Colour("#000000");
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('tone', () => {
let actual = tone(colour3, 1);
let expected = new Colour("#808080");
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('colourDodge white', () => {
let actual = colourDodge(colour2, white);
let expected = white;
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('colourBurn white', () => {
let actual = colourBurn(white, colour2);
let expected = white;
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('colourBurn black', () => {
let actual = colourBurn(colour2, black);
let expected = black;
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
test('softLight blargh', () => {
let actual = softLight(black, white);
let expected = black;
expect(actual.red).toBe(expected.red);
expect(actual.green).toBe(expected.green);
expect(actual.blue).toBe(expected.blue);
});
//# sourceMappingURL=bartender.test.js.map