From 1fec521c46de3ffd1c523d45c7eda5484990a4f2 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Mon, 2 Dec 2024 01:00:55 +0100 Subject: [PATCH 1/5] use offscreen canvas to fully defer the computation to workers --- .../ColorPicker/ColorPicker.ts | 5 -- .../LCHPaint/LCHPaint.ts | 33 +++++----- .../generate-lch-colors.ts | 21 +++++- .../generate-lch-colors.worker.ts | 66 ++++++++++++++++--- .../LCHPaint/generate-lch-colors/index.ts | 42 +++--------- .../OkLCHPaint/OkLCHPaint.ts | 33 +++++----- .../generate-oklch-colors.ts | 21 +++++- .../generate-oklch-colors.worker.ts | 66 ++++++++++++++++--- .../OkLCHPaint/generate-oklch-colors/index.ts | 40 +++-------- 9 files changed, 206 insertions(+), 121 deletions(-) diff --git a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts index f99d704..13d97c3 100644 --- a/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts +++ b/src/components/page-color-visualizer/ColorPicker/ColorPicker.ts @@ -7,11 +7,6 @@ import "./ColorSlider"; import { colorController, type Type, type Kind } from "../color-controller"; import { ManateaController } from "../manatea-controller"; -function toFixed(number: number, decimal: number = 0) { - const dec = 10 ** decimal; - return Math.round(number * dec) / dec; -} - const view = window.matchMedia("(max-width: 595px)"); @customElement("color-picker") diff --git a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts index a75661d..8a60717 100644 --- a/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts +++ b/src/components/page-color-visualizer/LCHPaint/LCHPaint.ts @@ -24,7 +24,21 @@ export class LCHPaint extends LitElement { isPressed = false; - generateColors = createGenerateColors(); + #_generateColors?: ReturnType; + get generateColors() { + if (this.#_generateColors) { + return this.#_generateColors; + } + + const canvas = this.canvas; + if (!canvas) { + return undefined; + } + this.#_generateColors = createGenerateColors( + canvas.transferControlToOffscreen() + ); + return this.#_generateColors; + } @eventOptions({ passive: true }) onPositionChange(event: PointerEvent) { @@ -69,23 +83,8 @@ export class LCHPaint extends LitElement { }; updateCanvasColors = () => { - const canvas = this.canvas; - if (!canvas) { - return; - } - const ctx = canvas.getContext("2d", { colorSpace: "display-p3" }); - if (!ctx) { - return; - } - const lch = colorController().to("lch"); - - this.generateColors(lch.h, this.width, this.height).then((colorArray) => { - const imageData = new ImageData(colorArray, this.width, this.height, { - colorSpace: "display-p3", - }); - ctx.putImageData(imageData, 0, 0); - }); + this.generateColors?.(lch.h, this.width, this.height); }; constructor() { diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts index 77e90ed..727e17d 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts @@ -5,14 +5,31 @@ interface Step { colors: [number, number, number]; } -export function* generateColors( +function task(cb: () => void) { + const mc = new MessageChannel(); + mc.port1.postMessage(null); + mc.port2.addEventListener( + "message", + function taskCallback() { + cb(); + }, + { once: true } + ); + mc.port2.start(); +} + +export async function* generateColors( hue: number, width: number, height: number, colorSpace: "srgb" | "display-p3" -): Generator { +): AsyncGenerator { const color = new Color("lch", [50, 50, hue]); for (let x = 0; x < width; x += 1) { + // Add some timeouts to allow injecting a break within the loop, for new events + if (x !== 0) { + await new Promise((resolve) => task(resolve)); + } for (let y = 0; y < height; y += 1) { color.c = Math.floor((x / width) * 150); // chroma goes to 150 color.l = Math.floor((1 - y / height) * 100); diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts index bde8299..0186ca9 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts @@ -1,22 +1,59 @@ import { generateColors } from "./generate-lch-colors"; -function send(message: Uint8ClampedArray) { - postMessage(message); -} +let offscreenCanvas: OffscreenCanvas | undefined; + +type Message = [ + hue: number, + width: number, + height: number, + colorSpace: "srgb" | "display-p3" +]; + +let lastMessageReceived = {}; +let lastSuccessfulMessage: Message | undefined; + +onmessage = async function (event: MessageEvent) { + const key = {}; + lastMessageReceived = key; + if (event.data instanceof OffscreenCanvas) { + offscreenCanvas = event.data; + return; + } + + if (!offscreenCanvas) { + return; + } -onmessage = function ( - event: MessageEvent<[number, number, number, "srgb" | "display-p3"]> -) { const [hue, width, height, colorSpace] = event.data; + if ( + lastSuccessfulMessage && + lastSuccessfulMessage[0] === hue && + lastSuccessfulMessage[1] === width && + lastSuccessfulMessage[2] === height && + lastSuccessfulMessage[3] === colorSpace + ) { + return; + } + + const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); + + if (!ctx) { + return; + } + const colorArray = new Uint8ClampedArray(width * height * 4); - for (const { coordinates, colors } of generateColors( + // Is a for await loop, so that we can insert a new event in the middle of the loop + for await (const { coordinates, colors } of generateColors( hue, width, height, colorSpace )) { + if (lastMessageReceived !== key) { + return; + } const position = 4 * (coordinates.y * width + coordinates.x); colorArray[position + 0] = colors[0] * 255; // X in P3 colorArray[position + 1] = colors[1] * 255; // Y in P3 @@ -24,5 +61,18 @@ onmessage = function ( colorArray[position + 3] = 255; // A value } - send(colorArray); + if (lastMessageReceived !== key) { + return; + } + + const imageData = new ImageData(colorArray, width, height, { + colorSpace: "display-p3", + }); + + if (lastMessageReceived !== key) { + return; + } + + lastSuccessfulMessage = event.data; + ctx.putImageData(imageData, 0, 0); }; 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 index 78157a9..90efc4d 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/index.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/index.ts @@ -1,38 +1,16 @@ import GenerateColorsWorker from "./generate-lch-colors.worker.ts?worker"; -// TODO: use https://docs.rs/palette/latest/palette/ and WASM instead of a web worker - -let prevWorker: Worker | null = null; -const isRunningMap = new WeakMap(); - const doesSupportColorSpace = new ImageData(1, 1).colorSpace !== undefined; +const worker = new GenerateColorsWorker(); -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, - doesSupportColorSpace ? "display-p3" : "srgb", - ]); - }); +export const createGenerateColors = (offscreenCanvas: OffscreenCanvas) => { + worker.postMessage(offscreenCanvas, [offscreenCanvas]); + return (hue: number, width: number, height: number) => { + worker.postMessage([ + hue, + width, + height, + doesSupportColorSpace ? "display-p3" : "srgb", + ]); }; }; diff --git a/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts b/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts index fd844fb..6c3f695 100644 --- a/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts +++ b/src/components/page-color-visualizer/OkLCHPaint/OkLCHPaint.ts @@ -24,7 +24,21 @@ export class OkLCHPaint extends LitElement { isPressed = false; - generateColors = createGenerateColors(); + #_generateColors?: ReturnType; + get generateColors() { + if (this.#_generateColors) { + return this.#_generateColors; + } + + const canvas = this.canvas; + if (!canvas) { + return undefined; + } + this.#_generateColors = createGenerateColors( + canvas.transferControlToOffscreen() + ); + return this.#_generateColors; + } @eventOptions({ passive: true }) onPositionChange(event: PointerEvent) { @@ -69,23 +83,8 @@ export class OkLCHPaint extends LitElement { }; updateCanvasColors = () => { - const canvas = this.canvas; - if (!canvas) { - return; - } - const ctx = canvas.getContext("2d", { colorSpace: "display-p3" }); - 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, { - colorSpace: "display-p3", - }); - ctx.putImageData(imageData, 0, 0); - }); + this.generateColors?.(oklch.h, this.width, this.height); }; constructor() { 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 index e9d2e04..157136b 100644 --- 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 @@ -5,14 +5,31 @@ interface Step { colors: [number, number, number]; } -export function* generateColors( +function task(cb: () => void) { + const mc = new MessageChannel(); + mc.port1.postMessage(null); + mc.port2.addEventListener( + "message", + function taskCallback() { + cb(); + }, + { once: true } + ); + mc.port2.start(); +} + +export async function* generateColors( hue: number, width: number, height: number, colorSpace: "srgb" | "display-p3" -): Generator { +): AsyncGenerator { const color = new Color("oklch", [0.5, 0.2, hue]); for (let x = 0; x < width; x += 1) { + // Add some timeouts to allow injecting a break within the loop, for new events + if (x !== 0) { + await new Promise((resolve) => task(resolve)); + } for (let y = 0; y < height; y += 1) { color.c = (x / width) * 0.4; // chroma goes to 0.4 color.l = 1 - y / height; 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 index 63ce6f6..196b7aa 100644 --- 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 @@ -1,22 +1,59 @@ import { generateColors } from "./generate-oklch-colors"; -function send(message: Uint8ClampedArray) { - postMessage(message); -} +let offscreenCanvas: OffscreenCanvas | undefined; + +type Message = [ + hue: number, + width: number, + height: number, + colorSpace: "srgb" | "display-p3" +]; + +let lastMessageReceived = {}; +let lastSuccessfulMessage: Message | undefined; + +onmessage = async function (event: MessageEvent) { + const key = {}; + lastMessageReceived = key; + if (event.data instanceof OffscreenCanvas) { + offscreenCanvas = event.data; + return; + } + + if (!offscreenCanvas) { + return; + } -onmessage = function ( - event: MessageEvent<[number, number, number, "srgb" | "display-p3"]> -) { const [hue, width, height, colorSpace] = event.data; + if ( + lastSuccessfulMessage && + lastSuccessfulMessage[0] === hue && + lastSuccessfulMessage[1] === width && + lastSuccessfulMessage[2] === height && + lastSuccessfulMessage[3] === colorSpace + ) { + return; + } + + const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); + + if (!ctx) { + return; + } + const colorArray = new Uint8ClampedArray(width * height * 4); - for (const { coordinates, colors } of generateColors( + // Is a for await loop, so that we can insert a new event in the middle of the loop + for await (const { coordinates, colors } of generateColors( hue, width, height, colorSpace )) { + if (lastMessageReceived !== key) { + return; + } const position = 4 * (coordinates.y * width + coordinates.x); colorArray[position + 0] = colors[0] * 255; // X in P3 colorArray[position + 1] = colors[1] * 255; // Y in P3 @@ -24,5 +61,18 @@ onmessage = function ( colorArray[position + 3] = 255; // A value } - send(colorArray); + if (lastMessageReceived !== key) { + return; + } + + const imageData = new ImageData(colorArray, width, height, { + colorSpace: "display-p3", + }); + + if (lastMessageReceived !== key) { + return; + } + + lastSuccessfulMessage = event.data; + ctx.putImageData(imageData, 0, 0); }; 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 index 2344f9a..c146469 100644 --- a/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts +++ b/src/components/page-color-visualizer/OkLCHPaint/generate-oklch-colors/index.ts @@ -1,36 +1,16 @@ import GenerateColorsWorker from "./generate-oklch-colors.worker.ts?worker"; -let prevWorker: Worker | null = null; -const isRunningMap = new WeakMap(); - const doesSupportColorSpace = new ImageData(1, 1).colorSpace !== undefined; +const worker = new GenerateColorsWorker(); -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, - doesSupportColorSpace ? "display-p3" : "srgb", - ]); - }); +export const createGenerateColors = (offscreenCanvas: OffscreenCanvas) => { + worker.postMessage(offscreenCanvas, [offscreenCanvas]); + return (hue: number, width: number, height: number) => { + worker.postMessage([ + hue, + width, + height, + doesSupportColorSpace ? "display-p3" : "srgb", + ]); }; }; -- 2.51.2 From d2fc314504bb6c3d8228d8c0ddedc248e6dd8858 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Mon, 2 Dec 2024 01:27:23 +0100 Subject: [PATCH 2/5] re-compute every 500ms --- .../generate-lch-colors.ts | 17 ++------- .../generate-lch-colors.worker.ts | 12 +++---- .../generate-oklch-colors.ts | 16 ++------- .../generate-oklch-colors.worker.ts | 12 +++---- .../page-color-visualizer/worker-utils.ts | 36 +++++++++++++++++++ 5 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 src/components/page-color-visualizer/worker-utils.ts diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts index 727e17d..4a57bd5 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts @@ -1,21 +1,10 @@ import Color from "colorjs.io"; +import { task } from "../../worker-utils"; + interface Step { coordinates: { x: number; y: number }; - colors: [number, number, number]; -} - -function task(cb: () => void) { - const mc = new MessageChannel(); - mc.port1.postMessage(null); - mc.port2.addEventListener( - "message", - function taskCallback() { - cb(); - }, - { once: true } - ); - mc.port2.start(); + colors: [r: number, g: number, b: number]; } export async function* generateColors( diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts index 0186ca9..8de0e88 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts @@ -1,4 +1,5 @@ import { generateColors } from "./generate-lch-colors"; +import { createScheduler } from "../../worker-utils"; let offscreenCanvas: OffscreenCanvas | undefined; @@ -9,12 +10,12 @@ type Message = [ colorSpace: "srgb" | "display-p3" ]; -let lastMessageReceived = {}; +const scheduler = createScheduler(); let lastSuccessfulMessage: Message | undefined; onmessage = async function (event: MessageEvent) { const key = {}; - lastMessageReceived = key; + scheduler.start(key); if (event.data instanceof OffscreenCanvas) { offscreenCanvas = event.data; return; @@ -37,7 +38,6 @@ onmessage = async function (event: MessageEvent) { } const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); - if (!ctx) { return; } @@ -51,7 +51,7 @@ onmessage = async function (event: MessageEvent) { height, colorSpace )) { - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } const position = 4 * (coordinates.y * width + coordinates.x); @@ -61,7 +61,7 @@ onmessage = async function (event: MessageEvent) { colorArray[position + 3] = 255; // A value } - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } @@ -69,7 +69,7 @@ onmessage = async function (event: MessageEvent) { colorSpace: "display-p3", }); - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } 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 index 157136b..f2993cc 100644 --- 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 @@ -1,21 +1,9 @@ import Color from "colorjs.io"; +import { task } from "../../worker-utils"; interface Step { coordinates: { x: number; y: number }; - colors: [number, number, number]; -} - -function task(cb: () => void) { - const mc = new MessageChannel(); - mc.port1.postMessage(null); - mc.port2.addEventListener( - "message", - function taskCallback() { - cb(); - }, - { once: true } - ); - mc.port2.start(); + colors: [r: number, g: number, b: number]; } export async function* generateColors( 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 index 196b7aa..732979a 100644 --- 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 @@ -1,4 +1,5 @@ import { generateColors } from "./generate-oklch-colors"; +import { createScheduler } from "../../worker-utils"; let offscreenCanvas: OffscreenCanvas | undefined; @@ -9,12 +10,12 @@ type Message = [ colorSpace: "srgb" | "display-p3" ]; -let lastMessageReceived = {}; +const scheduler = createScheduler(); let lastSuccessfulMessage: Message | undefined; onmessage = async function (event: MessageEvent) { const key = {}; - lastMessageReceived = key; + scheduler.start(key); if (event.data instanceof OffscreenCanvas) { offscreenCanvas = event.data; return; @@ -37,7 +38,6 @@ onmessage = async function (event: MessageEvent) { } const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); - if (!ctx) { return; } @@ -51,7 +51,7 @@ onmessage = async function (event: MessageEvent) { height, colorSpace )) { - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } const position = 4 * (coordinates.y * width + coordinates.x); @@ -61,7 +61,7 @@ onmessage = async function (event: MessageEvent) { colorArray[position + 3] = 255; // A value } - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } @@ -69,7 +69,7 @@ onmessage = async function (event: MessageEvent) { colorSpace: "display-p3", }); - if (lastMessageReceived !== key) { + if (!scheduler.shouldRun(key)) { return; } diff --git a/src/components/page-color-visualizer/worker-utils.ts b/src/components/page-color-visualizer/worker-utils.ts new file mode 100644 index 0000000..d3a711a --- /dev/null +++ b/src/components/page-color-visualizer/worker-utils.ts @@ -0,0 +1,36 @@ +export function task(cb: () => void) { + const mc = new MessageChannel(); + mc.port1.postMessage(null); + mc.port2.addEventListener( + "message", + function taskCallback() { + cb(); + }, + { once: true } + ); + mc.port2.start(); +} + +export function createScheduler() { + let lastStarted = {}; + const toRunNoMatterWhat = new Set<{}>(); + let lastUnstoppableStart = -Infinity; + return { + start: (key: {}): void => { + const now = performance.now(); + lastStarted = key; + + // Force run every 0.5s + if (now - lastUnstoppableStart > 500) { + lastUnstoppableStart = now; + toRunNoMatterWhat.add(key); + } + }, + shouldRun: (key: {}): boolean => { + if (toRunNoMatterWhat.has(key)) { + return true; + } + return key === lastStarted; + }, + }; +} -- 2.51.2 From 05b14cfd0005ab837b1010ec636fe77bdf8704ce Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Mon, 2 Dec 2024 01:56:25 +0100 Subject: [PATCH 3/5] redo system to be more performant --- .../generate-lch-colors.ts | 10 +---- .../generate-lch-colors.worker.ts | 43 +++++++++++-------- .../generate-oklch-colors.ts | 9 +--- .../generate-oklch-colors.worker.ts | 43 +++++++++++-------- .../page-color-visualizer/worker-utils.ts | 36 ---------------- 5 files changed, 54 insertions(+), 87 deletions(-) delete mode 100644 src/components/page-color-visualizer/worker-utils.ts diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts index 4a57bd5..890c589 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts @@ -1,24 +1,18 @@ import Color from "colorjs.io"; -import { task } from "../../worker-utils"; - interface Step { coordinates: { x: number; y: number }; colors: [r: number, g: number, b: number]; } -export async function* generateColors( +export function* generateColors( hue: number, width: number, height: number, colorSpace: "srgb" | "display-p3" -): AsyncGenerator { +): Generator { const color = new Color("lch", [50, 50, hue]); for (let x = 0; x < width; x += 1) { - // Add some timeouts to allow injecting a break within the loop, for new events - if (x !== 0) { - await new Promise((resolve) => task(resolve)); - } for (let y = 0; y < height; y += 1) { color.c = Math.floor((x / width) * 150); // chroma goes to 150 color.l = Math.floor((1 - y / height) * 100); diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts index 8de0e88..79bae56 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts @@ -1,5 +1,4 @@ import { generateColors } from "./generate-lch-colors"; -import { createScheduler } from "../../worker-utils"; let offscreenCanvas: OffscreenCanvas | undefined; @@ -10,12 +9,14 @@ type Message = [ colorSpace: "srgb" | "display-p3" ]; -const scheduler = createScheduler(); let lastSuccessfulMessage: Message | undefined; -onmessage = async function (event: MessageEvent) { - const key = {}; - scheduler.start(key); +let isDrawing = false; +let queuedMessage: Message | undefined; + +onmessage = function handleMessage( + event: MessageEvent +) { if (event.data instanceof OffscreenCanvas) { offscreenCanvas = event.data; return; @@ -25,6 +26,12 @@ onmessage = async function (event: MessageEvent) { return; } + if (isDrawing) { + queuedMessage = event.data; + return; + } + isDrawing = true; + const [hue, width, height, colorSpace] = event.data; if ( @@ -44,16 +51,12 @@ onmessage = async function (event: MessageEvent) { const colorArray = new Uint8ClampedArray(width * height * 4); - // Is a for await loop, so that we can insert a new event in the middle of the loop - for await (const { coordinates, colors } of generateColors( + for (const { coordinates, colors } of generateColors( hue, width, height, colorSpace )) { - if (!scheduler.shouldRun(key)) { - return; - } const position = 4 * (coordinates.y * width + coordinates.x); colorArray[position + 0] = colors[0] * 255; // X in P3 colorArray[position + 1] = colors[1] * 255; // Y in P3 @@ -61,18 +64,22 @@ onmessage = async function (event: MessageEvent) { colorArray[position + 3] = 255; // A value } - if (!scheduler.shouldRun(key)) { - return; - } - const imageData = new ImageData(colorArray, width, height, { colorSpace: "display-p3", }); - if (!scheduler.shouldRun(key)) { - return; - } - lastSuccessfulMessage = event.data; ctx.putImageData(imageData, 0, 0); + // ctx.putImageData takes some time to finish + setTimeout(() => { + isDrawing = false; + }); + + if (queuedMessage) { + const m = queuedMessage; + queuedMessage = undefined; + handleMessage.call(this, { data: m } as MessageEvent< + Message | OffscreenCanvas + >); + } }; 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 index f2993cc..0ed83dc 100644 --- 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 @@ -1,23 +1,18 @@ import Color from "colorjs.io"; -import { task } from "../../worker-utils"; interface Step { coordinates: { x: number; y: number }; colors: [r: number, g: number, b: number]; } -export async function* generateColors( +export function* generateColors( hue: number, width: number, height: number, colorSpace: "srgb" | "display-p3" -): AsyncGenerator { +): Generator { const color = new Color("oklch", [0.5, 0.2, hue]); for (let x = 0; x < width; x += 1) { - // Add some timeouts to allow injecting a break within the loop, for new events - if (x !== 0) { - await new Promise((resolve) => task(resolve)); - } for (let y = 0; y < height; y += 1) { color.c = (x / width) * 0.4; // chroma goes to 0.4 color.l = 1 - y / height; 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 index 732979a..ed75589 100644 --- 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 @@ -1,5 +1,4 @@ import { generateColors } from "./generate-oklch-colors"; -import { createScheduler } from "../../worker-utils"; let offscreenCanvas: OffscreenCanvas | undefined; @@ -10,12 +9,14 @@ type Message = [ colorSpace: "srgb" | "display-p3" ]; -const scheduler = createScheduler(); let lastSuccessfulMessage: Message | undefined; -onmessage = async function (event: MessageEvent) { - const key = {}; - scheduler.start(key); +let isDrawing = false; +let queuedMessage: Message | undefined; + +onmessage = function handleMessage( + event: MessageEvent +) { if (event.data instanceof OffscreenCanvas) { offscreenCanvas = event.data; return; @@ -25,6 +26,12 @@ onmessage = async function (event: MessageEvent) { return; } + if (isDrawing) { + queuedMessage = event.data; + return; + } + isDrawing = true; + const [hue, width, height, colorSpace] = event.data; if ( @@ -44,16 +51,12 @@ onmessage = async function (event: MessageEvent) { const colorArray = new Uint8ClampedArray(width * height * 4); - // Is a for await loop, so that we can insert a new event in the middle of the loop - for await (const { coordinates, colors } of generateColors( + for (const { coordinates, colors } of generateColors( hue, width, height, colorSpace )) { - if (!scheduler.shouldRun(key)) { - return; - } const position = 4 * (coordinates.y * width + coordinates.x); colorArray[position + 0] = colors[0] * 255; // X in P3 colorArray[position + 1] = colors[1] * 255; // Y in P3 @@ -61,18 +64,22 @@ onmessage = async function (event: MessageEvent) { colorArray[position + 3] = 255; // A value } - if (!scheduler.shouldRun(key)) { - return; - } - const imageData = new ImageData(colorArray, width, height, { colorSpace: "display-p3", }); - if (!scheduler.shouldRun(key)) { - return; - } - lastSuccessfulMessage = event.data; ctx.putImageData(imageData, 0, 0); + // ctx.putImageData takes some time to finish + setTimeout(() => { + isDrawing = false; + }); + + if (queuedMessage) { + const m = queuedMessage; + queuedMessage = undefined; + handleMessage.call(this, { data: m } as MessageEvent< + Message | OffscreenCanvas + >); + } }; diff --git a/src/components/page-color-visualizer/worker-utils.ts b/src/components/page-color-visualizer/worker-utils.ts deleted file mode 100644 index d3a711a..0000000 --- a/src/components/page-color-visualizer/worker-utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -export function task(cb: () => void) { - const mc = new MessageChannel(); - mc.port1.postMessage(null); - mc.port2.addEventListener( - "message", - function taskCallback() { - cb(); - }, - { once: true } - ); - mc.port2.start(); -} - -export function createScheduler() { - let lastStarted = {}; - const toRunNoMatterWhat = new Set<{}>(); - let lastUnstoppableStart = -Infinity; - return { - start: (key: {}): void => { - const now = performance.now(); - lastStarted = key; - - // Force run every 0.5s - if (now - lastUnstoppableStart > 500) { - lastUnstoppableStart = now; - toRunNoMatterWhat.add(key); - } - }, - shouldRun: (key: {}): boolean => { - if (toRunNoMatterWhat.has(key)) { - return true; - } - return key === lastStarted; - }, - }; -} -- 2.51.2 From bff8224583f9b6d07c932d2c35f920412aa247ff Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Mon, 2 Dec 2024 02:00:30 +0100 Subject: [PATCH 4/5] unify colors --- .../generate-lch-colors.ts | 6 +- .../generate-lch-colors.worker.ts | 86 +--------------- .../generate-oklch-colors.ts | 6 +- .../generate-oklch-colors.worker.ts | 86 +--------------- .../page-color-visualizer/worker-utils.ts | 97 +++++++++++++++++++ 5 files changed, 105 insertions(+), 176 deletions(-) create mode 100644 src/components/page-color-visualizer/worker-utils.ts diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts index 890c589..52b4116 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.ts @@ -1,9 +1,5 @@ import Color from "colorjs.io"; - -interface Step { - coordinates: { x: number; y: number }; - colors: [r: number, g: number, b: number]; -} +import type { Step } from "@components/page-color-visualizer/worker-utils"; export function* generateColors( hue: number, diff --git a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts index 79bae56..fa87557 100644 --- a/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts +++ b/src/components/page-color-visualizer/LCHPaint/generate-lch-colors/generate-lch-colors.worker.ts @@ -1,85 +1,5 @@ -import { generateColors } from "./generate-lch-colors"; - -let offscreenCanvas: OffscreenCanvas | undefined; - -type Message = [ - hue: number, - width: number, - height: number, - colorSpace: "srgb" | "display-p3" -]; - -let lastSuccessfulMessage: Message | undefined; - -let isDrawing = false; -let queuedMessage: Message | undefined; - -onmessage = function handleMessage( - event: MessageEvent -) { - if (event.data instanceof OffscreenCanvas) { - offscreenCanvas = event.data; - return; - } - - if (!offscreenCanvas) { - return; - } - - if (isDrawing) { - queuedMessage = event.data; - return; - } - isDrawing = true; +import { createOnMessage } from "@components/page-color-visualizer/worker-utils"; - const [hue, width, height, colorSpace] = event.data; - - if ( - lastSuccessfulMessage && - lastSuccessfulMessage[0] === hue && - lastSuccessfulMessage[1] === width && - lastSuccessfulMessage[2] === height && - lastSuccessfulMessage[3] === colorSpace - ) { - return; - } - - const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); - if (!ctx) { - return; - } - - const colorArray = new Uint8ClampedArray(width * height * 4); - - for (const { coordinates, colors } of generateColors( - hue, - width, - height, - colorSpace - )) { - const position = 4 * (coordinates.y * width + coordinates.x); - colorArray[position + 0] = colors[0] * 255; // X in P3 - colorArray[position + 1] = colors[1] * 255; // Y in P3 - colorArray[position + 2] = colors[2] * 255; // Z in P3 - colorArray[position + 3] = 255; // A value - } - - const imageData = new ImageData(colorArray, width, height, { - colorSpace: "display-p3", - }); - - lastSuccessfulMessage = event.data; - ctx.putImageData(imageData, 0, 0); - // ctx.putImageData takes some time to finish - setTimeout(() => { - isDrawing = false; - }); +import { generateColors } from "./generate-lch-colors"; - if (queuedMessage) { - const m = queuedMessage; - queuedMessage = undefined; - handleMessage.call(this, { data: m } as MessageEvent< - Message | OffscreenCanvas - >); - } -}; +onmessage = createOnMessage(generateColors); 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 index 0ed83dc..f02671a 100644 --- 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 @@ -1,9 +1,5 @@ import Color from "colorjs.io"; - -interface Step { - coordinates: { x: number; y: number }; - colors: [r: number, g: number, b: number]; -} +import type { Step } from "@components/page-color-visualizer/worker-utils"; export function* generateColors( hue: number, 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 index ed75589..51fedf8 100644 --- 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 @@ -1,85 +1,5 @@ -import { generateColors } from "./generate-oklch-colors"; - -let offscreenCanvas: OffscreenCanvas | undefined; - -type Message = [ - hue: number, - width: number, - height: number, - colorSpace: "srgb" | "display-p3" -]; - -let lastSuccessfulMessage: Message | undefined; - -let isDrawing = false; -let queuedMessage: Message | undefined; - -onmessage = function handleMessage( - event: MessageEvent -) { - if (event.data instanceof OffscreenCanvas) { - offscreenCanvas = event.data; - return; - } - - if (!offscreenCanvas) { - return; - } - - if (isDrawing) { - queuedMessage = event.data; - return; - } - isDrawing = true; +import { createOnMessage } from "@components/page-color-visualizer/worker-utils"; - const [hue, width, height, colorSpace] = event.data; - - if ( - lastSuccessfulMessage && - lastSuccessfulMessage[0] === hue && - lastSuccessfulMessage[1] === width && - lastSuccessfulMessage[2] === height && - lastSuccessfulMessage[3] === colorSpace - ) { - return; - } - - const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); - if (!ctx) { - return; - } - - const colorArray = new Uint8ClampedArray(width * height * 4); - - for (const { coordinates, colors } of generateColors( - hue, - width, - height, - colorSpace - )) { - const position = 4 * (coordinates.y * width + coordinates.x); - colorArray[position + 0] = colors[0] * 255; // X in P3 - colorArray[position + 1] = colors[1] * 255; // Y in P3 - colorArray[position + 2] = colors[2] * 255; // Z in P3 - colorArray[position + 3] = 255; // A value - } - - const imageData = new ImageData(colorArray, width, height, { - colorSpace: "display-p3", - }); - - lastSuccessfulMessage = event.data; - ctx.putImageData(imageData, 0, 0); - // ctx.putImageData takes some time to finish - setTimeout(() => { - isDrawing = false; - }); +import { generateColors } from "./generate-oklch-colors"; - if (queuedMessage) { - const m = queuedMessage; - queuedMessage = undefined; - handleMessage.call(this, { data: m } as MessageEvent< - Message | OffscreenCanvas - >); - } -}; +onmessage = createOnMessage(generateColors); diff --git a/src/components/page-color-visualizer/worker-utils.ts b/src/components/page-color-visualizer/worker-utils.ts new file mode 100644 index 0000000..61e0f7f --- /dev/null +++ b/src/components/page-color-visualizer/worker-utils.ts @@ -0,0 +1,97 @@ +export interface Step { + coordinates: { x: number; y: number }; + colors: [r: number, g: number, b: number]; +} + +type GenerateColors = ( + hue: number, + width: number, + height: number, + colorSpace: "srgb" | "display-p3" +) => Generator; + +export const createOnMessage = (generateColors: GenerateColors) => { + let offscreenCanvas: OffscreenCanvas | undefined; + + type Message = [ + hue: number, + width: number, + height: number, + colorSpace: "srgb" | "display-p3" + ]; + + let lastSuccessfulMessage: Message | undefined; + + let isDrawing = false; + let queuedMessage: Message | undefined; + + function handleMessage(event: MessageEvent) { + if (event.data instanceof OffscreenCanvas) { + offscreenCanvas = event.data; + return; + } + + if (!offscreenCanvas) { + return; + } + + if (isDrawing) { + queuedMessage = event.data; + return; + } + isDrawing = true; + + const [hue, width, height, colorSpace] = event.data; + + if ( + lastSuccessfulMessage && + lastSuccessfulMessage[0] === hue && + lastSuccessfulMessage[1] === width && + lastSuccessfulMessage[2] === height && + lastSuccessfulMessage[3] === colorSpace + ) { + return; + } + + const ctx = offscreenCanvas.getContext("2d", { colorSpace: "display-p3" }); + if (!ctx) { + return; + } + + const colorArray = new Uint8ClampedArray(width * height * 4); + + for (const { coordinates, colors } of generateColors( + hue, + width, + height, + colorSpace + )) { + const position = 4 * (coordinates.y * width + coordinates.x); + colorArray[position + 0] = colors[0] * 255; // X in P3 + colorArray[position + 1] = colors[1] * 255; // Y in P3 + colorArray[position + 2] = colors[2] * 255; // Z in P3 + colorArray[position + 3] = 255; // A value + } + + const imageData = new ImageData(colorArray, width, height, { + colorSpace: "display-p3", + }); + + lastSuccessfulMessage = event.data; + ctx.putImageData(imageData, 0, 0); + // ctx.putImageData takes some time to finish + setTimeout(() => { + isDrawing = false; + }); + + if (queuedMessage) { + const message = queuedMessage; + queuedMessage = undefined; + handleMessage({ data: message } as MessageEvent< + Message | OffscreenCanvas + >); + } + } + + return handleMessage; +}; -- 2.51.2 From d8c156003f879af67844df10f606beb91045126e Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Mon, 2 Dec 2024 02:02:39 +0100 Subject: [PATCH 5/5] move call to queue in the timeout --- .../page-color-visualizer/worker-utils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/page-color-visualizer/worker-utils.ts b/src/components/page-color-visualizer/worker-utils.ts index 61e0f7f..9e2b58c 100644 --- a/src/components/page-color-visualizer/worker-utils.ts +++ b/src/components/page-color-visualizer/worker-utils.ts @@ -82,15 +82,15 @@ export const createOnMessage = (generateColors: GenerateColors) => { // ctx.putImageData takes some time to finish setTimeout(() => { isDrawing = false; - }); - if (queuedMessage) { - const message = queuedMessage; - queuedMessage = undefined; - handleMessage({ data: message } as MessageEvent< - Message | OffscreenCanvas - >); - } + if (queuedMessage) { + const message = queuedMessage; + queuedMessage = undefined; + handleMessage({ data: message } as MessageEvent< + Message | OffscreenCanvas + >); + } + }); } return handleMessage; -- 2.51.2