From 8852a5ce8f42009795635ee07fa879404daa24c5 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Sat, 2 Dec 2023 19:28:12 +0500 Subject: [PATCH] Use web worker --- typescript/tsconfig.json | 2 +- typescript/utils/image_utils.ts | 40 +++++++++---------- typescript/utils/image_utils_converter.ts | 48 +++++++++++++++++++++++ typescript/utils/image_utils_worker.ts | 24 ++++++++++++ 4 files changed, 93 insertions(+), 21 deletions(-) create mode 100755 typescript/utils/image_utils_converter.ts create mode 100755 typescript/utils/image_utils_worker.ts diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json index f603e467..50dce251 100644 --- a/typescript/tsconfig.json +++ b/typescript/tsconfig.json @@ -8,7 +8,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, - "module": "es2015", + "module": "es2020", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, "noImplicitAny": true, diff --git a/typescript/utils/image_utils.ts b/typescript/utils/image_utils.ts index 7800f74e..15b169f6 100644 --- a/typescript/utils/image_utils.ts +++ b/typescript/utils/image_utils.ts @@ -15,10 +15,7 @@ * limitations under the License. */ -import {QuantizerCelebi} from '../quantize/quantizer_celebi.js'; -import {Score} from '../score/score.js'; - -import {argbFromRgb} from './color_utils.js'; +import {rankedColorsFromImageBytes} from './image_utils_converter.js'; /** * Get the source color from an image. @@ -29,7 +26,8 @@ import {argbFromRgb} from './color_utils.js'; export async function sourceColorFromImage(image: HTMLImageElement) { // Convert Image data to Pixel Array const imageBytes = await new Promise((resolve, reject) => { - const canvas = document.createElement('canvas'); + const element = document.createElement('canvas'); + const canvas = 'OffscreenCanvas' in window ? element.transferControlToOffscreen() : element; const context = canvas.getContext('2d'); if (!context) { reject(new Error('Could not get canvas context')); @@ -38,6 +36,7 @@ export async function sourceColorFromImage(image: HTMLImageElement) { const callback = () => { canvas.width = image.width; canvas.height = image.height; + // @ts-ignore context.drawImage(image, 0, 0); let rect = [0, 0, image.width, image.height]; const area = image.dataset['area']; @@ -48,6 +47,7 @@ export async function sourceColorFromImage(image: HTMLImageElement) { }); } const [sx, sy, sw, sh] = rect; + // @ts-ignore resolve(context.getImageData(sx, sy, sw, sh).data); }; if (image.complete) { @@ -57,23 +57,23 @@ export async function sourceColorFromImage(image: HTMLImageElement) { } }); - // Convert Image data to Pixel Array - const pixels: number[] = []; - for (let i = 0; i < imageBytes.length; i += 4) { - const r = imageBytes[i]; - const g = imageBytes[i + 1]; - const b = imageBytes[i + 2]; - const a = imageBytes[i + 3]; - if (a < 255) { - continue; - } - const argb = argbFromRgb(r, g, b); - pixels.push(argb); + let ranked: number[]; + + if (window.Worker) { + const worker = new Worker(new URL('./image_utils_worker.js', import.meta.url), {type: 'module'}); + + worker.postMessage(imageBytes); + + ranked = await new Promise((resolve) => { + worker.onmessage = (event) => { + const ranked = event.data; + resolve(ranked); + }; + }); + } else { + ranked = rankedColorsFromImageBytes(imageBytes); } - // Convert Pixels to Material Colors - const result = QuantizerCelebi.quantize(pixels, 128); - const ranked = Score.score(result); const top = ranked[0]; return top; } diff --git a/typescript/utils/image_utils_converter.ts b/typescript/utils/image_utils_converter.ts new file mode 100755 index 00000000..ddbe5c07 --- /dev/null +++ b/typescript/utils/image_utils_converter.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {QuantizerCelebi} from '../quantize/quantizer_celebi.js'; +import {Score} from '../score/score.js'; + +import {argbFromRgb} from './color_utils.js'; + +/** + * Get ranked colors from image bytes. + * + * @param imageBytes The image bytes + * @return Ranked colors - the colors most suitable for creating a UI theme + */ +export function rankedColorsFromImageBytes(imageBytes: Uint8ClampedArray) { + // Convert Image data to Pixel Array + const pixels: number[] = []; + for (let i = 0; i < imageBytes.length; i += 4) { + const r = imageBytes[i]; + const g = imageBytes[i + 1]; + const b = imageBytes[i + 2]; + const a = imageBytes[i + 3]; + if (a < 255) { + continue; + } + const argb = argbFromRgb(r, g, b); + pixels.push(argb); + } + + // Convert Pixels to Material Colors + const result = QuantizerCelebi.quantize(pixels, 128); + const ranked = Score.score(result); + return ranked; +} diff --git a/typescript/utils/image_utils_worker.ts b/typescript/utils/image_utils_worker.ts new file mode 100755 index 00000000..3428ffda --- /dev/null +++ b/typescript/utils/image_utils_worker.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {rankedColorsFromImageBytes} from './image_utils_converter.js'; + +self.onmessage = (event) => { + const imageBytes = event.data; + const ranked = rankedColorsFromImageBytes(imageBytes); + self.postMessage(ranked); +}