diff --git a/package.json b/package.json
index b1a23e4..85712c2 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"packageManager": "yarn@4.2.2",
"dependencies": {
"@astrojs/starlight": "^0.23.0",
- "d3-color": "^3.1.0",
+ "colorjs.io": "^0.5.0",
"konva": "^9.3.8",
"lit": "^3.1.3",
"manatea": "^0.6.1",
@@ -26,7 +26,6 @@
},
"devDependencies": {
"@astrojs/react": "^3.3.4",
- "@types/d3-color": "^3.1.3",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"astro": "^4.8.6",
diff --git a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
index 92f3f17..76164fa 100644
--- a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
+++ b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
@@ -7,11 +7,7 @@ import "./ColorSlider";
import { colorController, type Type, type Kind } from "../color-controller";
import { ManateaController } from "../manatea-controller";
-function toHex(color: number) {
- return Math.floor(color).toString(16).padStart(2, "0");
-}
-
-function toFixed(number: number, decimal: number = 2) {
+function toFixed(number: number, decimal: number = 0) {
const dec = 10 ** decimal;
return Math.round(number * dec) / dec;
}
@@ -63,8 +59,8 @@ export class ColorPicker extends LitElement {
const clamp = (n: number) => Math.min(max, Math.max(min, n));
const stored = colorController();
- // @ts-expect-error
- const value = stored[category].values[kind];
+ const colorInSpace = stored.to(category);
+ const value = colorInSpace[kind];
const id = `${category}--${kind}`;
@@ -76,7 +72,7 @@ export class ColorPicker extends LitElement {
.max=${max}
.step=${step}
.value=${value}
- .referenceColor=${stored[category].raw}
+ .referenceColor=${colorInSpace}
.valueToModify=${kind}
@input=${(event: Event) => {
const element = event.target as ColorSlider;
@@ -114,9 +110,7 @@ export class ColorPicker extends LitElement {
override render() {
const stored = colorController();
- const hexRGB = `#${toHex(stored.rgb.values.r)}${toHex(
- stored.rgb.values.g
- )}${toHex(stored.rgb.values.b)}`;
+
return html`
@@ -145,11 +139,9 @@ export class ColorPicker extends LitElement {
max: 360,
})}
- LCH(${toFixed(
- stored.lch.values.l
- )}% ${toFixed(stored.lch.values.c)} ${toFixed(
- stored.lch.values.h
- )})
+ ${stored
+ .to("lch")
+ .toString({ precision: 3 })}
@@ -181,45 +173,47 @@ export class ColorPicker extends LitElement {
max: 127,
})}
- Lab(${toFixed(
- stored.lab.values.l
- )}% ${toFixed(stored.lab.values.a)} ${toFixed(
- stored.lab.values.b
- )})
+ ${stored
+ .to("lab")
+ .toString({ precision: 3 })}
-
+
RGB
${this.renderInput({
- category: "rgb",
+ category: "srgb",
kind: "r",
label: "red",
min: 0,
- max: 255,
+ max: 1,
+ step: 0.01,
+ mod: (v) => toFixed(v * 255),
})}
${this.renderInput({
- category: "rgb",
+ category: "srgb",
kind: "g",
label: "green",
min: 0,
- max: 255,
+ max: 1,
+ step: 0.01,
+ mod: (v) => toFixed(v * 255),
})}
${this.renderInput({
- category: "rgb",
+ category: "srgb",
kind: "b",
label: "blue",
min: 0,
- max: 255,
+ max: 1,
+ step: 0.01,
+ mod: (v) => toFixed(v * 255),
})}
-
rgb(${toFixed(
- stored.rgb.values.r
- )} ${toFixed(stored.rgb.values.g)} ${toFixed(
- stored.rgb.values.b
- )})
-${hexRGB}
+
${stored
+ .to("srgb")
+ .toString({ precision: 3 })}
+${stored.to("srgb").toString({ format: "hex" })}
@@ -238,9 +232,7 @@ export class ColorPicker extends LitElement {
kind: "s",
label: "saturation",
min: 0,
- step: 0.01,
- max: 1,
- mod: (v) => v * 100,
+ max: 100,
unit: "%",
})}
${this.renderInput({
@@ -248,17 +240,13 @@ export class ColorPicker extends LitElement {
kind: "l",
label: "lightness",
min: 0,
- step: 0.01,
- max: 1,
- mod: (v) => v * 100,
+ max: 100,
unit: "%",
})}
- hsl(${toFixed(
- stored.hsl.values.h
- )} ${toFixed(stored.hsl.values.s) * 100}% ${toFixed(
- stored.hsl.values.l * 100
- )}%)
+ ${stored
+ .to("hsl")
+ .toString({ precision: 3 })}
diff --git a/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts b/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
index 0d0a3ec..2b767bf 100644
--- a/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
+++ b/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
@@ -1,7 +1,7 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
-import { type ColorSpaceObject, rgb } from "d3-color";
+import Color from "colorjs.io";
import type { Kind, Type } from "../color-controller";
const height = 20;
@@ -47,7 +47,7 @@ export class ColorSlider extends LitElement {
value: number | undefined;
@state()
- referenceColor: ColorSpaceObject = rgb(0, 0, 0);
+ referenceColor: Color = new Color("srgb", [0, 0, 0]);
@state()
valueToModify: Kind[Type] = "h";
@@ -58,8 +58,11 @@ export class ColorSlider extends LitElement {
getBackgroundRange = () => {
clearTimeout(this.throttleId);
- const color = this.referenceColor.copy();
- const colorToCompare: Record = { ...color };
+ const color = new Color(this.referenceColor);
+ const keys = Object.keys(color.space.coords);
+ const colorToCompare: Record = Object.fromEntries(
+ keys.map((key) => [key as Kind[Type], color[key as Kind[Type]]])
+ );
delete colorToCompare[this.valueToModify];
const stringColorToCompare = JSON.stringify(colorToCompare);
if (this.prevBackgroundRange) {
@@ -80,13 +83,12 @@ export class ColorSlider extends LitElement {
const colorArray = new Uint8ClampedArray(nbOfPoints * 4);
for (let i = 0; i <= nbOfPoints; i++) {
- // @ts-ignore
color[this.valueToModify] = i * this.step + this.min;
- const rgb = color.rgb();
+ const rgb = color.to("srgb");
const position = 4 * i;
- colorArray[position + 0] = rgb.r; // R value
- colorArray[position + 1] = rgb.g; // G value
- colorArray[position + 2] = rgb.b; // B value
+ colorArray[position + 0] = rgb.r * 255; // R value
+ colorArray[position + 1] = rgb.g * 255; // G value
+ colorArray[position + 2] = rgb.b * 255; // B value
colorArray[position + 3] = 255; // A value
}
diff --git a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
index 7e70179..42903d1 100644
--- a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
+++ b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
@@ -4,7 +4,7 @@ import { query } from "lit/decorators/query.js";
import { colorController } from "../color-controller";
-import { createGenerateColors } from "./generate-colors";
+import { createGenerateColors } from "./generate-lch-colors";
@customElement("lch-paint")
export class LCHPaint extends LitElement {
@@ -58,7 +58,7 @@ export class LCHPaint extends LitElement {
return;
}
const rect = canvas.getBoundingClientRect();
- const lch = colorController().lch.values;
+ const lch = colorController().to("lch");
const x = Math.floor((lch.c / 132) * rect.width);
const y = Math.floor((1 - lch.l / 100) * rect.height);
@@ -75,7 +75,7 @@ export class LCHPaint extends LitElement {
return;
}
- const lch = colorController().lch.values;
+ const lch = colorController().to("lch");
this.generateColors(lch.h, this.width, this.height).then((colorArray) => {
const imageData = new ImageData(colorArray, this.width, this.height);
diff --git a/src/components/page-color-visualizer/LCHPaint/generate-colors/index.ts b/src/components/page-color-visualizer/LCHPaint/generate-colors/index.ts
deleted file mode 100644
index ed97aa0..0000000
--- a/src/components/page-color-visualizer/LCHPaint/generate-colors/index.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import GenerateColorsWorker from "./generate-colors.worker.ts?worker";
-
-const worker = new GenerateColorsWorker();
-
-export const createGenerateColors = () => {
- return (
- hue: number,
- width: number,
- height: number
- ): Promise => {
- return new Promise((res) => {
- worker.onmessage = (event: MessageEvent) => {
- res(event.data.slice());
- };
- worker.postMessage([hue, width, height]);
- });
- };
-};
diff --git a/src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts
similarity index 65%
rename from src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.ts
rename to src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts
index da8b8a3..9042509 100644
--- a/src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.ts
+++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts
@@ -1,4 +1,4 @@
-import { hcl } from "d3-color";
+import Color from "colorjs.io";
interface Step {
coordinates: { x: number; y: number };
@@ -14,14 +14,19 @@ export function* generateColors(
width: number,
height: number
): Generator {
- const color = hcl(hue, 0, 0);
+ const color = new Color("lch", [50, 50, hue]);
for (let x = 0; x < width; x += 1) {
for (let y = 0; y < height; y += 1) {
color.c = Math.floor((x / width) * 132); // chroma goes to 132
color.l = Math.floor((1 - y / height) * 100);
+ const rgb = color.to("srgb");
yield {
coordinates: { x, y },
- colors: color.rgb(),
+ colors: {
+ r: Math.floor(rgb.r * 255),
+ g: Math.floor(rgb.g * 255),
+ b: Math.floor(rgb.b * 255),
+ },
};
}
}
diff --git a/src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.worker.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts
similarity index 90%
rename from src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.worker.ts
rename to src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts
index c25dc0b..d5a13d5 100644
--- a/src/components/page-color-visualizer/LCHPaint/generate-colors/generate-colors.worker.ts
+++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts
@@ -1,7 +1,6 @@
-import { generateColors } from "./generate-colors";
+import { generateColors } from "./generate-lch-colors";
function send(message: Uint8ClampedArray) {
- // @ts-ignore
postMessage(message);
}
diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/index.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/index.ts
new file mode 100644
index 0000000..61f4556
--- /dev/null
+++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/index.ts
@@ -0,0 +1,29 @@
+import GenerateColorsWorker from "./generate-lch-colors.worker.ts?worker";
+
+let prevWorker: Worker | null = null;
+const isRunningMap = new WeakMap();
+
+export const createGenerateColors = () => {
+ return (
+ hue: number,
+ width: number,
+ height: number
+ ): Promise => {
+ if (prevWorker && isRunningMap.get(prevWorker)) {
+ prevWorker.terminate();
+ }
+
+ const worker = new GenerateColorsWorker();
+ prevWorker = worker;
+
+ return new Promise((res) => {
+ worker.onmessage = (event: MessageEvent) => {
+ isRunningMap.set(worker, false);
+ res(event.data.slice());
+ };
+
+ isRunningMap.set(worker, true);
+ worker.postMessage([hue, width, height]);
+ });
+ };
+};
diff --git a/src/components/page-color-visualizer/color-controller.ts b/src/components/page-color-visualizer/color-controller.ts
index f6a26e5..ececcb8 100644
--- a/src/components/page-color-visualizer/color-controller.ts
+++ b/src/components/page-color-visualizer/color-controller.ts
@@ -1,21 +1,11 @@
import { orderCup } from "manatea";
-import {
- lch,
- lab,
- rgb,
- hsl,
- type LabColor,
- type HCLColor,
- type RGBColor,
- type HSLColor,
- type ColorSpaceObject,
-} from "d3-color";
+import Color from "colorjs.io";
-export type Type = "lch" | "lab" | "rgb" | "hsl";
+export type Type = "lch" | "lab" | "srgb" | "hsl";
export interface Kind {
lch: "l" | "c" | "h";
lab: "l" | "a" | "b";
- rgb: "r" | "g" | "b";
+ srgb: "r" | "g" | "b";
hsl: "h" | "s" | "l";
}
interface TriggerUpdate {
@@ -24,82 +14,20 @@ interface TriggerUpdate {
value: number;
}
-interface Stored {
- lch: {
- values: Record;
- raw: HCLColor;
- };
- lab: {
- values: Record;
- raw: LabColor;
- };
- rgb: {
- values: Record;
- raw: RGBColor;
- };
- hsl: {
- values: Record;
- raw: HSLColor;
- };
-}
-
-const bounds: { [type in Type]: Record } = {
- lch: { l: [0, 100], c: [0, 132], h: [0, 360] },
- lab: { l: [0, 100], a: [-128, 127], b: [-128, 127] },
- rgb: { r: [0, 255], g: [0, 255], b: [0, 255] },
- hsl: { h: [0, 360], s: [0, 1], l: [0, 1] },
-};
-
-const initialLCHColor = lch(57, 110, 283.8);
-const initialStored = createStored(initialLCHColor);
+const initialLCHColor = new Color("srgb", [0.2, 0.45, 0.98]);
const computeUpdate = (
update: TriggerUpdate,
- previouslyStored: Stored
-): Stored => {
- switch (update.type) {
- case "lch": {
- const newColor = lch(previouslyStored.lch.raw);
- const kind = update.kind as Kind["lch"];
- newColor[kind] = clamp(update.value, ...bounds["lch"][kind]);
- if (areEqual(newColor, previouslyStored.lch.raw)) {
- return previouslyStored;
- }
- return createStored(newColor);
- }
- case "lab": {
- const newColor = lab(previouslyStored.lab.raw);
- const kind = update.kind as Kind["lab"];
- newColor[kind] = clamp(update.value, ...bounds["lab"][kind]);
- if (areEqual(newColor, previouslyStored.lab.raw)) {
- return previouslyStored;
- }
- return createStored(newColor);
- }
- case "rgb": {
- const newColor = rgb(previouslyStored.rgb.raw);
- const kind = update.kind as Kind["rgb"];
- newColor[kind] = clamp(update.value, ...bounds["rgb"][kind]);
- if (areEqual(newColor, previouslyStored.rgb.raw)) {
- return previouslyStored;
- }
- return createStored(newColor);
- }
- case "hsl": {
- const newColor = hsl(previouslyStored.hsl.raw);
- const kind = update.kind as Kind["hsl"];
- newColor[kind] = clamp(update.value, ...bounds["hsl"][kind]);
- if (areEqual(newColor, previouslyStored.hsl.raw)) {
- return previouslyStored;
- }
- return createStored(newColor);
- }
- }
+ previouslyStored: Color
+): Color => {
+ const newColor = new Color(previouslyStored).to(update.type);
+ newColor[update.kind] = update.value;
+ return newColor;
};
-export const colorController = orderCup(
+export const colorController = orderCup(
[],
- (updates, previouslyStored = initialStored) => {
+ (updates, previouslyStored = initialLCHColor) => {
let valueToStore = previouslyStored;
for (const update of updates) {
valueToStore = computeUpdate(update, valueToStore);
@@ -107,57 +35,3 @@ export const colorController = orderCup(
return valueToStore;
}
);
-
-function clamp(number: number, min: number, max: number) {
- return Math.min(max, Math.max(min, number || 0));
-}
-
-function norm(color: ColorSpaceObject) {
- return JSON.stringify(
- Object.entries(color).sort(([a, b]) => a[0]!.localeCompare(b[0]))
- );
-}
-function areEqual(colorA: ColorSpaceObject, colorB: ColorSpaceObject) {
- return norm(colorA) === norm(colorB);
-}
-
-function createStored(color: ColorSpaceObject): Stored {
- const lchColor = lch(color);
- const labColor = lab(color);
- const rgbColor = rgb(color.formatHex());
- const hslColor = hsl(color.formatHex());
- return {
- lch: {
- values: {
- l: lchColor.l,
- c: lchColor.c,
- h: lchColor.h,
- },
- raw: lchColor,
- },
- lab: {
- values: {
- l: labColor.l,
- a: labColor.a,
- b: labColor.b,
- },
- raw: labColor,
- },
- rgb: {
- values: {
- r: rgbColor.r,
- g: rgbColor.g,
- b: rgbColor.b,
- },
- raw: rgbColor,
- },
- hsl: {
- values: {
- h: hslColor.h,
- s: hslColor.s,
- l: hslColor.l,
- },
- raw: hslColor,
- },
- };
-}
diff --git a/src/components/page-color-visualizer/index.ts b/src/components/page-color-visualizer/index.ts
index ad69319..ee77c54 100644
--- a/src/components/page-color-visualizer/index.ts
+++ b/src/components/page-color-visualizer/index.ts
@@ -2,9 +2,10 @@ import "./LCHPaint/LCHPaint.ts";
import "./ColorPicker/ColorPicker.ts";
import { colorController } from "./color-controller.ts";
-document.documentElement.style.backgroundColor =
- colorController().rgb.raw.formatHex();
+document.documentElement.style.backgroundColor = colorController()
+ .to("srgb")
+ .toString();
-colorController.on(({ rgb }) => {
- document.documentElement.style.backgroundColor = rgb.raw.formatHex();
+colorController.on((raw) => {
+ document.documentElement.style.backgroundColor = raw.to("srgb").toString();
});
diff --git a/yarn.lock b/yarn.lock
index cf5fe9c..9268f73 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1385,13 +1385,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-color@npm:^3.1.3":
- version: 3.1.3
- resolution: "@types/d3-color@npm:3.1.3"
- checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae
- languageName: node
- linkType: hard
-
"@types/debug@npm:^4.0.0":
version: 4.1.12
resolution: "@types/debug@npm:4.1.12"
@@ -2134,6 +2127,13 @@ __metadata:
languageName: node
linkType: hard
+"colorjs.io@npm:^0.5.0":
+ version: 0.5.0
+ resolution: "colorjs.io@npm:0.5.0"
+ checksum: 10c0/3fa5ed32f2bc62ea133d8df9b267a2bd2e33d3acb8f86bf057b125967f4b7ac607179df15fe4371e4f883760ea939a93a234d6b655fac605166d68e98a55a24d
+ languageName: node
+ linkType: hard
+
"comma-separated-tokens@npm:^2.0.0":
version: 2.0.3
resolution: "comma-separated-tokens@npm:2.0.3"
@@ -2196,13 +2196,6 @@ __metadata:
languageName: node
linkType: hard
-"d3-color@npm:^3.1.0":
- version: 3.1.0
- resolution: "d3-color@npm:3.1.0"
- checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c
- languageName: node
- linkType: hard
-
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.4":
version: 4.3.4
resolution: "debug@npm:4.3.4"
@@ -5032,11 +5025,10 @@ __metadata:
dependencies:
"@astrojs/react": "npm:^3.3.4"
"@astrojs/starlight": "npm:^0.23.0"
- "@types/d3-color": "npm:^3.1.3"
"@types/react": "npm:^18.3.2"
"@types/react-dom": "npm:^18.3.0"
astro: "npm:^4.8.6"
- d3-color: "npm:^3.1.0"
+ colorjs.io: "npm:^0.5.0"
konva: "npm:^9.3.8"
lit: "npm:^3.1.3"
manatea: "npm:^0.6.1"
--
2.51.2
From abfd0dca1134429d959ee003537bb1074fef729a Mon Sep 17 00:00:00 2001
From: Ayc0
Date: Fri, 17 May 2024 23:59:59 +0200
Subject: [PATCH 2/6] add oklch & oklab
---
.../ColorPicker/ColorPicker.ts | 73 +++++++++++++++++++
.../page-color-visualizer/color-controller.ts | 4 +-
2 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
index 76164fa..dfa1998 100644
--- a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
+++ b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
@@ -113,6 +113,79 @@ export class ColorPicker extends LitElement {
return html`
+
+ OkLCH
+
+ ${this.renderInput({
+ category: "oklch",
+ kind: "l",
+ label: "luminance",
+ min: 0,
+ step: 0.01,
+ max: 1,
+ unit: "%",
+ mod: (v) => toFixed(v * 100, 1),
+ })}
+ ${this.renderInput({
+ category: "oklch",
+ kind: "c",
+ label: "chroma",
+ min: 0,
+ step: 0.01,
+ max: 0.4,
+ })}
+ ${this.renderInput({
+ category: "oklch",
+ kind: "h",
+ label: "hue",
+ min: 0,
+ max: 360,
+ })}
+
+
${stored
+ .to("oklch")
+ .toString({ precision: 3 })}
+
+
+
+
+ OkLab
+
+ ${this.renderInput({
+ category: "oklab",
+ kind: "l",
+ label: "luminance",
+ min: 0,
+ step: 0.01,
+ max: 1,
+ unit: "%",
+ mod: (v) => toFixed(v * 100, 1),
+ })}
+ ${this.renderInput({
+ category: "oklab",
+ kind: "a",
+ label: "a",
+ shortName: "a",
+ min: -0.4,
+ step: 0.01,
+ max: 0.4,
+ })}
+ ${this.renderInput({
+ category: "oklab",
+ kind: "b",
+ label: "b",
+ shortName: "b",
+ min: -0.4,
+ step: 0.01,
+ max: 0.4,
+ })}
+
+
${stored
+ .to("oklab")
+ .toString({ precision: 3 })}
+
+
+
LCH
diff --git a/src/components/page-color-visualizer/color-controller.ts b/src/components/page-color-visualizer/color-controller.ts
index ececcb8..4911c46 100644
--- a/src/components/page-color-visualizer/color-controller.ts
+++ b/src/components/page-color-visualizer/color-controller.ts
@@ -1,12 +1,14 @@
import { orderCup } from "manatea";
import Color from "colorjs.io";
-export type Type = "lch" | "lab" | "srgb" | "hsl";
+export type Type = "lch" | "lab" | "srgb" | "hsl" | "oklch" | "oklab";
export interface Kind {
lch: "l" | "c" | "h";
lab: "l" | "a" | "b";
srgb: "r" | "g" | "b";
hsl: "h" | "s" | "l";
+ oklch: "l" | "c" | "h";
+ oklab: "l" | "a" | "b";
}
interface TriggerUpdate
{
type: T;
--
2.51.2
From 006b8812f8b864e360c8b96a4c4a74cd2934344a Mon Sep 17 00:00:00 2001
From: Ayc0
Date: Sat, 18 May 2024 00:33:47 +0200
Subject: [PATCH 3/6] use colorsjs gradient
---
.../ColorPicker/ColorSlider.ts | 65 ++++++-------------
1 file changed, 19 insertions(+), 46 deletions(-)
diff --git a/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts b/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
index 2b767bf..3ca3dd9 100644
--- a/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
+++ b/src/components/page-color-visualizer/ColorPicker/ColorSlider.ts
@@ -21,7 +21,7 @@ const thumbStyles = css`
`;
// Use both the previous and the current to avoid flickers on FF / Safari
-const trackBackground = css`var(--track-background, #9e9e9e), var(--previous-track-background, #9e9e9e)`;
+const trackBackground = css`var(--track-background, #9e9e9e)`;
// const trackBackgroundHover = css`var(--track-background-hover, #bbbbbb)`;
// const trackBackgroundFocus = css`var(--track-background-focus, #cbcbcb)`;
const trackStyles = css`
@@ -57,54 +57,28 @@ export class ColorSlider extends LitElement {
throttleId?: number;
getBackgroundRange = () => {
- clearTimeout(this.throttleId);
- const color = new Color(this.referenceColor);
- const keys = Object.keys(color.space.coords);
- const colorToCompare: Record = Object.fromEntries(
- keys.map((key) => [key as Kind[Type], color[key as Kind[Type]]])
- );
- delete colorToCompare[this.valueToModify];
- const stringColorToCompare = JSON.stringify(colorToCompare);
- if (this.prevBackgroundRange) {
- // If the color hasn't changed, no need to re-compute
- if (this.prevColor === stringColorToCompare) {
- return this.prevBackgroundRange;
- }
- const now = performance.now();
- // Only compute every 100ms
- if (this.timePrevOp != null && now - this.timePrevOp < 100) {
- // Start a timeout of 100ms so that if the last update was cancelled, we'll still have a correct render
- this.throttleId = window.setTimeout(() => this.requestUpdate(), 100);
- return this.prevBackgroundRange;
- }
+ const coord = this.referenceColor.space.coords[this.valueToModify];
+ if (!coord) {
+ return this.prevBackgroundRange;
}
-
- const nbOfPoints = Math.round((this.max - this.min) / this.step);
-
- const colorArray = new Uint8ClampedArray(nbOfPoints * 4);
- for (let i = 0; i <= nbOfPoints; i++) {
- color[this.valueToModify] = i * this.step + this.min;
- const rgb = color.to("srgb");
- const position = 4 * i;
- colorArray[position + 0] = rgb.r * 255; // R value
- colorArray[position + 1] = rgb.g * 255; // G value
- colorArray[position + 2] = rgb.b * 255; // B value
- colorArray[position + 3] = 255; // A value
+ const range = coord.refRange || coord.range;
+ if (!range) {
+ return this.prevBackgroundRange;
}
- const canvas = document.createElement("canvas");
- canvas.width = nbOfPoints;
- canvas.height = 1;
+ const from = new Color(this.referenceColor);
+ from[this.valueToModify] = range[0];
- const imageData = new ImageData(colorArray, nbOfPoints, 1);
- const ctx = canvas.getContext("2d", { alpha: false })!;
- ctx.putImageData(imageData, 0, 0);
+ const to = new Color(this.referenceColor);
+ to[this.valueToModify] = range[1];
+
+ const stops = Color.steps(
+ from.range(to, { space: this.referenceColor.space.id, hue: "raw" }),
+ { steps: 50 }
+ );
- const dataUrl = canvas.toDataURL();
- this.prevBackgroundRange = dataUrl;
- this.prevColor = stringColorToCompare;
- this.timePrevOp = performance.now();
- return dataUrl;
+ this.prevBackgroundRange = `linear-gradient(to right, ${stops.join(", ")})`;
+ return this.prevBackgroundRange;
};
override render() {
@@ -113,8 +87,7 @@ export class ColorSlider extends LitElement {
.max=${this.max}
.step=${this.step}
.value=${this.value}
- style="--previous-track-background: url(${this
- .prevBackgroundRange}); --track-background: url(${this.getBackgroundRange()})"
+ style="--track-background: ${this.getBackgroundRange()}"
type="range"
@input=${(event: Event) => {
const element = event.target as HTMLInputElement;
--
2.51.2
From ad6265297e9030beb340f7f0cfd9ba16f4ab25b7 Mon Sep 17 00:00:00 2001
From: Ayc0
Date: Sat, 18 May 2024 00:45:50 +0200
Subject: [PATCH 4/6] make canvas smaller + allow to have multiple canvas
---
src/components/page-color-visualizer/LCHPaint/LCHPaint.ts | 6 +++---
src/pages/color-visualizer.astro | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
index 42903d1..6372f55 100644
--- a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
+++ b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts
@@ -9,9 +9,9 @@ import { createGenerateColors } from "./generate-lch-colors";
@customElement("lch-paint")
export class LCHPaint extends LitElement {
@property({ type: Number })
- width = 500;
+ width = 300;
@property({ type: Number })
- height = 300;
+ height = 200;
@query("canvas")
canvas?: HTMLCanvasElement | null;
@@ -133,7 +133,7 @@ export class LCHPaint extends LitElement {
:host canvas {
border-radius: 5px;
border: 1px solid grey;
- max-width: calc(100vw - 4em);
+ width: 100%;
/* Avoid making the page scroll on mobile when we are pressing on it */
touch-action: none;
}
diff --git a/src/pages/color-visualizer.astro b/src/pages/color-visualizer.astro
index fd00b37..47ee66e 100644
--- a/src/pages/color-visualizer.astro
+++ b/src/pages/color-visualizer.astro
@@ -39,7 +39,7 @@ import Page from '@layouts/Page.astro';
-
+
--
2.51.2
From 9179c6446aa32847dd7f6044d2155837ef754888 Mon Sep 17 00:00:00 2001
From: Ayc0
Date: Sat, 18 May 2024 01:02:08 +0200
Subject: [PATCH 5/6] add oklch gradient
---
.../OkLCHPaint/OkLCHPaint.ts | 163 ++++++++++++++++++
.../generate-oklch-colors.ts | 33 ++++
.../generate-oklch-colors.worker.ts | 21 +++
.../OkLCHPaint/generate-oklch-colors/index.ts | 29 ++++
src/components/page-color-visualizer/index.ts | 1 +
src/pages/color-visualizer.astro | 9 +-
6 files changed, 255 insertions(+), 1 deletion(-)
create mode 100644 src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts
create mode 100644 src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.ts
create mode 100644 src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.worker.ts
create mode 100644 src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts
diff --git a/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts b/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts
new file mode 100644
index 0000000..5c2a4ea
--- /dev/null
+++ b/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts
@@ -0,0 +1,163 @@
+import { LitElement, html, css } from "lit";
+import { customElement, property, eventOptions } from "lit/decorators.js";
+import { query } from "lit/decorators/query.js";
+
+import { colorController } from "../color-controller";
+
+import { createGenerateColors } from "./generate-oklch-colors";
+
+@customElement("oklch-paint")
+export class OkLCHPaint extends LitElement {
+ @property({ type: Number })
+ width = 300;
+ @property({ type: Number })
+ height = 200;
+
+ @query("canvas")
+ canvas?: HTMLCanvasElement | null;
+
+ @query(".marker")
+ marker?: HTMLDivElement | null;
+
+ isPressed = false;
+
+ generateColors = createGenerateColors();
+
+ @eventOptions({ passive: true })
+ onPositionChange(event: PointerEvent) {
+ const canvas = this.canvas;
+ if (!canvas || !this.isPressed) {
+ return;
+ }
+ const rect = canvas.getBoundingClientRect();
+
+ const x = Math.min(Math.max(event.clientX - rect.x, 0), rect.width);
+ const y = Math.min(Math.max(event.clientY - rect.y, 0), rect.height);
+
+ const l = 1 - y / rect.height;
+ const c = (x / rect.width) * 0.4;
+
+ colorController([
+ {
+ type: "oklch",
+ kind: "l",
+ value: l,
+ },
+ {
+ type: "oklch",
+ kind: "c",
+ value: c,
+ },
+ ]);
+ }
+
+ updateMarkerPosition = () => {
+ const marker = this.marker;
+ const canvas = this.canvas;
+ if (!marker || !canvas) {
+ return;
+ }
+ const rect = canvas.getBoundingClientRect();
+ const oklch = colorController().to("oklch");
+ const x = Math.floor((oklch.c / 0.4) * rect.width);
+ const y = Math.floor((1 - oklch.l) * rect.height);
+
+ marker.style.cssText = `transform: translate(calc(${x}px - 50%), calc(${y}px - 50%))`;
+ };
+
+ updateCanvasColors = () => {
+ const canvas = this.canvas;
+ if (!canvas) {
+ return;
+ }
+ const ctx = canvas.getContext("2d");
+ if (!ctx) {
+ return;
+ }
+
+ const oklch = colorController().to("oklch");
+
+ this.generateColors(oklch.h, this.width, this.height).then((colorArray) => {
+ const imageData = new ImageData(colorArray, this.width, this.height);
+ ctx.putImageData(imageData, 0, 0);
+ });
+ };
+
+ constructor() {
+ super();
+ colorController.on(() => {
+ this.updateCanvasColors();
+ this.updateMarkerPosition();
+ });
+ }
+
+ override firstUpdated() {
+ this.updateMarkerPosition();
+ }
+
+ override updated(changed: Map) {
+ if (changed.has("width") || changed.has("height")) {
+ this.updateCanvasColors();
+ }
+ }
+
+ override render() {
+ return html`
+
+
+
+
+ `;
+ }
+
+ static override styles = css`
+ :host canvas {
+ border-radius: 5px;
+ border: 1px solid grey;
+ width: 100%;
+ /* Avoid making the page scroll on mobile when we are pressing on it */
+ touch-action: none;
+ }
+
+ :host .wrapper {
+ position: relative;
+ }
+
+ :host .marker {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ pointer-events: none;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ border: 1px solid var(--contrast);
+ z-index: 1;
+ }
+ `;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "oklch-paint": OkLCHPaint;
+ }
+}
diff --git a/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.ts b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.ts
new file mode 100644
index 0000000..23b87f8
--- /dev/null
+++ b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.ts
@@ -0,0 +1,33 @@
+import Color from "colorjs.io";
+
+interface Step {
+ coordinates: { x: number; y: number };
+ colors: {
+ r: number;
+ g: number;
+ b: number;
+ };
+}
+
+export function* generateColors(
+ hue: number,
+ width: number,
+ height: number
+): Generator {
+ const color = new Color("oklch", [0.5, 0.2, hue]);
+ for (let x = 0; x < width; x += 1) {
+ for (let y = 0; y < height; y += 1) {
+ color.c = (x / width) * 0.4; // chroma goes to 0.4
+ color.l = 1 - y / height;
+ const rgb = color.to("srgb");
+ yield {
+ coordinates: { x, y },
+ colors: {
+ r: Math.floor(rgb.r * 255),
+ g: Math.floor(rgb.g * 255),
+ b: Math.floor(rgb.b * 255),
+ },
+ };
+ }
+ }
+}
diff --git a/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.worker.ts b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.worker.ts
new file mode 100644
index 0000000..f25a7ab
--- /dev/null
+++ b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/generate-oklch-colors.worker.ts
@@ -0,0 +1,21 @@
+import { generateColors } from "./generate-oklch-colors";
+
+function send(message: Uint8ClampedArray) {
+ postMessage(message);
+}
+
+onmessage = function (event: MessageEvent<[number, number, number]>) {
+ const [hue, width, height] = event.data;
+
+ const colorArray = new Uint8ClampedArray(width * height * 4);
+
+ for (const { coordinates, colors } of generateColors(hue, width, height)) {
+ const position = 4 * (coordinates.y * width + coordinates.x);
+ colorArray[position + 0] = colors.r; // R value
+ colorArray[position + 1] = colors.g; // G value
+ colorArray[position + 2] = colors.b; // B value
+ colorArray[position + 3] = 255; // A value
+ }
+
+ send(colorArray);
+};
diff --git a/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts
new file mode 100644
index 0000000..0b1a850
--- /dev/null
+++ b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts
@@ -0,0 +1,29 @@
+import GenerateColorsWorker from "./generate-oklch-colors.worker.ts?worker";
+
+let prevWorker: Worker | null = null;
+const isRunningMap = new WeakMap();
+
+export const createGenerateColors = () => {
+ return (
+ hue: number,
+ width: number,
+ height: number
+ ): Promise => {
+ if (prevWorker && isRunningMap.get(prevWorker)) {
+ prevWorker.terminate();
+ }
+
+ const worker = new GenerateColorsWorker();
+ prevWorker = worker;
+
+ return new Promise((res) => {
+ worker.onmessage = (event: MessageEvent) => {
+ isRunningMap.set(worker, false);
+ res(event.data.slice());
+ };
+
+ isRunningMap.set(worker, true);
+ worker.postMessage([hue, width, height]);
+ });
+ };
+};
diff --git a/src/components/page-color-visualizer/index.ts b/src/components/page-color-visualizer/index.ts
index ee77c54..56da9f6 100644
--- a/src/components/page-color-visualizer/index.ts
+++ b/src/components/page-color-visualizer/index.ts
@@ -1,4 +1,5 @@
import "./LCHPaint/LCHPaint.ts";
+import "./OkLCHPaint/OkLCHPaint.ts";
import "./ColorPicker/ColorPicker.ts";
import { colorController } from "./color-controller.ts";
diff --git a/src/pages/color-visualizer.astro b/src/pages/color-visualizer.astro
index 47ee66e..c354d06 100644
--- a/src/pages/color-visualizer.astro
+++ b/src/pages/color-visualizer.astro
@@ -40,6 +40,13 @@ import Page from '@layouts/Page.astro';
-
+
+ In LCH:
+
+
+
+ In OkLCH:
+
+
--
2.51.2
From f266d9436b9717ebf48c44e1fc46e5c7027008d8 Mon Sep 17 00:00:00 2001
From: Ayc0
Date: Sat, 18 May 2024 01:02:53 +0200
Subject: [PATCH 6/6] collapse LCH on mobile
---
src/components/page-color-visualizer/ColorPicker/ColorPicker.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
index dfa1998..f02367c 100644
--- a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
+++ b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts
@@ -186,7 +186,7 @@ export class ColorPicker extends LitElement {
-
+
LCH
${this.renderInput({