-
Notifications
You must be signed in to change notification settings - Fork 167
Use web worker for Quantize and Score steps #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Uint8ClampedArray>((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'}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I create a new web worker here, do I have to destroy it somehow afterwards? 🤔 |
||
|
||
worker.postMessage(imageBytes); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second argument of worker.postMessage(imageBytes, [imageBytes]); The supported types of transferable objects are So to fix this, we need to pass the underlying worker.postMessage(imageBytes.buffer, [imageBytes.buffer]); But in this case, we need in the web worker to use: const imageBytes = new Uint8ClampedArray(event.data); instead of: const imageBytes = event.data; Does it make any sense? Does Can we write worker.postMessage(imageBytes, [imageBytes.buffer]); instead of worker.postMessage(imageBytes.buffer, [imageBytes.buffer]); Is that even correct? The browser doesn't give any errors. But does it work? Does such a code make any sense at all? |
||
|
||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make any sense to use
transferControlToOffscreen()
without using this canvas in the web worker? 🤔If not, does it make any sense to move the
callback
function code into the web worker? 🤔