feat: release Colour Tools 0.1.0
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import fc from "fast-check";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { compositeLayers, compositeSourceOver } from "../../src/colour";
|
||||
import type { BlendMode } from "../../src/colour";
|
||||
|
||||
describe("source-over compositing", () => {
|
||||
it("matches the canonical translucent-red-over-white result", () => {
|
||||
const result = compositeSourceOver("rgba(255, 0, 0, .5)", "white");
|
||||
expect(result.colour.coords).toEqual([1, 0.5, 0.5]);
|
||||
expect(result.colour.alpha).toBe(1);
|
||||
expect(result.hex).toBe("#ff8080");
|
||||
});
|
||||
|
||||
it("composites an arbitrary RGBA reference with the source-over equation", () => {
|
||||
const result = compositeSourceOver(
|
||||
"rgba(0, 129, 255, .42)",
|
||||
"rgb(255, 255, 255)",
|
||||
);
|
||||
expect(result.colour.coords[0]).toBeCloseTo(0.58, 12);
|
||||
expect(result.colour.coords[1]).toBeCloseTo(0.7924705882352941, 12);
|
||||
expect(result.colour.coords[2]).toBe(1);
|
||||
});
|
||||
|
||||
it("supports N layers and an explicit stack order", () => {
|
||||
const layers = [
|
||||
{ colour: "white" },
|
||||
{ colour: "rgba(255,0,0,.5)" },
|
||||
{ colour: "rgba(0,0,255,.5)" },
|
||||
];
|
||||
expect(compositeLayers(layers).hex).toBe("#8040bf");
|
||||
expect(
|
||||
compositeLayers([...layers].reverse(), { order: "top-to-bottom" }).hex,
|
||||
).toBe("#8040bf");
|
||||
});
|
||||
|
||||
it("offers visibly different sRGB and linear-light workflows", () => {
|
||||
const srgb = compositeSourceOver("rgba(255,0,0,.5)", "#00ff00", {
|
||||
space: "srgb",
|
||||
});
|
||||
const linear = compositeSourceOver("rgba(255,0,0,.5)", "#00ff00", {
|
||||
space: "linear-srgb",
|
||||
});
|
||||
expect(srgb.hex).toBe("#808000");
|
||||
expect(linear.hex).toBe("#bcbc00");
|
||||
});
|
||||
|
||||
it("implements opacity and common blend modes", () => {
|
||||
expect(
|
||||
compositeSourceOver("#ff0000", "#808080", { blendMode: "multiply" }).hex,
|
||||
).toBe("#800000");
|
||||
expect(
|
||||
compositeSourceOver("#ff0000", "#808080", { blendMode: "screen" }).hex,
|
||||
).toBe("#ff8080");
|
||||
expect(compositeSourceOver("#ff0000", "#ffffff", { opacity: 0 }).hex).toBe(
|
||||
"#ffffff",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps every exposed blend mode renderable", () => {
|
||||
const modes: BlendMode[] = [
|
||||
"normal",
|
||||
"multiply",
|
||||
"screen",
|
||||
"overlay",
|
||||
"darken",
|
||||
"lighten",
|
||||
"color-dodge",
|
||||
"color-burn",
|
||||
"hard-light",
|
||||
"soft-light",
|
||||
"difference",
|
||||
"exclusion",
|
||||
"hue",
|
||||
"saturation",
|
||||
"color",
|
||||
"luminosity",
|
||||
];
|
||||
for (const blendMode of modes) {
|
||||
const result = compositeSourceOver("rgba(230,30,80,.7)", "#2878c8", {
|
||||
blendMode,
|
||||
});
|
||||
expect(result.colour.coords.every(Number.isFinite), blendMode).toBe(true);
|
||||
expect(result.hex, blendMode).toMatch(/^#[0-9a-f]{6}$/);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps arbitrary composites bounded and alpha-correct", () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.tuple(
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
),
|
||||
fc.tuple(
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
fc.double({ min: 0, max: 1, noNaN: true }),
|
||||
),
|
||||
(foreground, background) => {
|
||||
const result = compositeSourceOver(
|
||||
{
|
||||
space: "srgb",
|
||||
coords: foreground.slice(0, 3) as [number, number, number],
|
||||
alpha: foreground[3],
|
||||
},
|
||||
{
|
||||
space: "srgb",
|
||||
coords: background.slice(0, 3) as [number, number, number],
|
||||
alpha: background[3],
|
||||
},
|
||||
).colour;
|
||||
expect(
|
||||
result.coords.every((channel) => channel >= 0 && channel <= 1),
|
||||
).toBe(true);
|
||||
expect(result.alpha).toBeGreaterThanOrEqual(0);
|
||||
expect(result.alpha).toBeLessThanOrEqual(1);
|
||||
expect(result.alpha).toBeCloseTo(
|
||||
foreground[3] + background[3] * (1 - foreground[3]),
|
||||
12,
|
||||
);
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user