[TypeScript] Quantization of relatively large images doesn't work in Chrome
FluorescentHallucinogen opened this issue · 4 comments
Quantization of images that contains >= 50139473 pixels doesn't work in Chrome.
Tested in Chrome x64 on Windows.
Not reproducible in Firefox.
This means e.g. an image with a resolution of 7080x7080 px can still be quantized, but 7081x7081 px cannot.
Minimal repro:
import { sourceColorFromImage } from '@material/material-color-utilities';
const image = new Image();
image.src = './7081x7081.png';
const sourceColor = await sourceColorFromImage(image);
console.log('sourceColor', sourceColor);
Error:
image_utils.ts:71 Uncaught RangeError: Invalid array length
at Array.push (<anonymous>)
at sourceColorFromImage (image_utils.ts:71:12)
at async (index):24:22
sourceColorFromImage @ image_utils.ts:71
load (async)
imageBytes @ image_utils.ts:56
sourceColorFromImage @ image_utils.ts:31
(anonymous) @ (index):24
Code line:
As far as I know, JavaScript arrays are zero-based and use 32-bit indexes: the index of the first element is 0, and the highest possible index is 4294967294 (2^32−2), for a maximum array size of 4,294,967,295 elements. Not 50,139,473. :)
@rodydavis @guidezpl Could you please take a look? 😉
@pennzht @marshallworks PTAL.
why not modify existing buffer?
const _forEach = (tarray, cb) => tarray.forEach(cb) ?? tarray;
return _forEach(new Uint32Array(data.buffer), (abgr, index, tarray) =>
tarray.set([abgr & 0xFF00FF00 | (abgr & 255) << 16 | abgr >> 16 & 255], index));
..or map new TypedArray
if raw buffer is necessary.
return new Uint32Array(data.buffer).map((abgr) =>
abgr & 0xFF00FF00 | (abgr & 255) << 16 | abgr >> 16 & 255);
Working sample tested with Chromium
<html>
<body>
<script type="module">
import { QuantizerCelebi, Score } from 'https://unpkg.com/@material/material-color-utilities';
const image = new Image();
image.crossOrigin = 'Anonymous';
image.src = 'https://r4.wallpaperflare.com/wallpaper/727/861/207/spiderman-ps4-spiderman-games-hd-wallpaper-2460826541419b3727663083655888cf.jpg';
async function sourceColorFromImage(image) {
const imageBytes = await new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
reject(new Error('Could not get canvas context'));
return;
}
const callback = () => {
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
let rect = [0, 0, image.width, image.height];
const area = image.dataset['area'];
if (area && /^\d+(\s*,\s*\d+){3}$/.test(area)) {
rect = area.split(/\s*,\s*/).map(s => {
// tslint:disable-next-line:ban
return parseInt(s, 10);
});
}
const [sx, sy, sw, sh] = rect;
resolve(context.getImageData(sx, sy, sw, sh).data);
};
if (image.complete) {
callback();
}
else {
image.onload = callback;
}
});
const bufferArray = new Uint32Array(imageBytes.buffer);
bufferArray.forEach((abgr, index, tarray) =>
tarray.set([abgr & 0xFF00FF00 | (abgr & 255) << 16 | abgr >> 16 & 255], index));
const result = QuantizerCelebi.quantize(bufferArray, 128);
const ranked = Score.score(result);
const top = ranked[0];
return top;
}
const sourceColor = await sourceColorFromImage(image);
console.log('sourceColor', sourceColor);
</script>
</body>
</html>