mirror of
https://github.com/tiennm99/rplace.git
synced 2026-10-11 03:13:48 +00:00
perf(importer): O(1) palette lookup via 5-bit LUT, auto-fit on load
Resolves browser freeze when importing large images. - image-to-palette.js: add lazy 32^3 Uint8Array LUT (32 KB) for nearest-color lookup. Replaces 256-iteration linear scan per pixel in quantizeNearest, error-diffusion, and ordered-dither paths. - ImageImporter.svelte: cap initial resizeW/resizeH to canvas bounds so a full-res photo does not run the pipeline at source size before the user can touch Fit.
This commit is contained in:
1 parent
cfbac2a586
commit
d6503676c6
2 files changed
+33
-5
No files matched your search
@@ -191,8 +191,14 @@
|
|||||||
srcRgba = data;
|
srcRgba = data;
|
||||||
srcWidth = w;
|
srcWidth = w;
|
||||||
srcHeight = h;
|
srcHeight = h;
|
||||||
resizeW = w;
|
// Cap initial output to canvas bounds. A full-res photo (e.g. 6000×4000)
|
||||||
resizeH = h;
|
// running through the palette pipeline on load would otherwise freeze
|
||||||
|
// the main thread. User can still scale up via the W/H inputs.
|
||||||
|
const maxW = Math.max(1, CANVAS_WIDTH - originX);
|
||||||
|
const maxH = Math.max(1, CANVAS_HEIGHT - originY);
|
||||||
|
const ratio = Math.min(maxW / w, maxH / h, 1);
|
||||||
|
resizeW = Math.max(1, Math.floor(w * ratio));
|
||||||
|
resizeH = Math.max(1, Math.floor(h * ratio));
|
||||||
// paletteIndices recomputed reactively via $effect below.
|
// paletteIndices recomputed reactively via $effect below.
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorText = `Failed to load image: ${err.message || err}`;
|
errorText = `Failed to load image: ${err.message || err}`;
|
||||||
|
|||||||
@@ -25,6 +25,28 @@ export function nearestColorIndex(r, g, b) {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5-bit-per-channel RGB→palette LUT. 32³ = 32,768 entries (32 KB).
|
||||||
|
// Build cost ≈ 32³ × 256 ≈ 8M ops, amortized across millions of pixel lookups.
|
||||||
|
let _lut = null;
|
||||||
|
function buildLut() {
|
||||||
|
const lut = new Uint8Array(32 * 32 * 32);
|
||||||
|
for (let r5 = 0; r5 < 32; r5++) {
|
||||||
|
const r = (r5 << 3) | 4;
|
||||||
|
for (let g5 = 0; g5 < 32; g5++) {
|
||||||
|
const g = (g5 << 3) | 4;
|
||||||
|
for (let b5 = 0; b5 < 32; b5++) {
|
||||||
|
const b = (b5 << 3) | 4;
|
||||||
|
lut[(r5 << 10) | (g5 << 5) | b5] = nearestColorIndex(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lut;
|
||||||
|
}
|
||||||
|
function lutNearest(r, g, b) {
|
||||||
|
if (!_lut) _lut = buildLut();
|
||||||
|
return _lut[((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3)];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an RGBA pixel buffer into a flat array of palette indices.
|
* Convert an RGBA pixel buffer into a flat array of palette indices.
|
||||||
* Pixels with alpha < threshold become -1 (caller should skip on upload).
|
* Pixels with alpha < threshold become -1 (caller should skip on upload).
|
||||||
@@ -99,7 +121,7 @@ function quantizeNearest(rgba, width, height, alphaThreshold) {
|
|||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const off = i * 4;
|
const off = i * 4;
|
||||||
if (rgba[off + 3] < alphaThreshold) { out[i] = -1; continue; }
|
if (rgba[off + 3] < alphaThreshold) { out[i] = -1; continue; }
|
||||||
out[i] = nearestColorIndex(rgba[off], rgba[off + 1], rgba[off + 2]);
|
out[i] = lutNearest(rgba[off], rgba[off + 1], rgba[off + 2]);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -122,7 +144,7 @@ function runErrorDiffusion(rgba, width, height, alphaThreshold, kernel) {
|
|||||||
if (rgba[i * 4 + 3] < alphaThreshold) { out[i] = -1; continue; }
|
if (rgba[i * 4 + 3] < alphaThreshold) { out[i] = -1; continue; }
|
||||||
|
|
||||||
const r = buf[i * 3], g = buf[i * 3 + 1], b = buf[i * 3 + 2];
|
const r = buf[i * 3], g = buf[i * 3 + 1], b = buf[i * 3 + 2];
|
||||||
const idx = nearestColorIndex(
|
const idx = lutNearest(
|
||||||
r < 0 ? 0 : r > 255 ? 255 : r,
|
r < 0 ? 0 : r > 255 ? 255 : r,
|
||||||
g < 0 ? 0 : g > 255 ? 255 : g,
|
g < 0 ? 0 : g > 255 ? 255 : g,
|
||||||
b < 0 ? 0 : b > 255 ? 255 : b,
|
b < 0 ? 0 : b > 255 ? 255 : b,
|
||||||
@@ -165,7 +187,7 @@ function runOrderedDither(rgba, width, height, alphaThreshold, matrix) {
|
|||||||
const r = rgba[i * 4] + bias;
|
const r = rgba[i * 4] + bias;
|
||||||
const g = rgba[i * 4 + 1] + bias;
|
const g = rgba[i * 4 + 1] + bias;
|
||||||
const b = rgba[i * 4 + 2] + bias;
|
const b = rgba[i * 4 + 2] + bias;
|
||||||
out[i] = nearestColorIndex(
|
out[i] = lutNearest(
|
||||||
r < 0 ? 0 : r > 255 ? 255 : r,
|
r < 0 ? 0 : r > 255 ? 255 : r,
|
||||||
g < 0 ? 0 : g > 255 ? 255 : g,
|
g < 0 ? 0 : g > 255 ? 255 : g,
|
||||||
b < 0 ? 0 : b > 255 ? 255 : b,
|
b < 0 ? 0 : b > 255 ? 255 : b,
|
||||||
|
|||||||
Reference in new issue
Block a user