@thi.ng/pixel-dither
Version:
Extensible image dithering w/ various algorithm presets
54 lines (53 loc) • 1.62 kB
JavaScript
import { isNumber } from "@thi.ng/checks/is-number";
import { clamp } from "@thi.ng/math/interval";
const __init = (x, y, size, val, step, mat) => {
if (size === 1) {
!mat[y] && (mat[y] = []);
mat[y][x] = val;
return mat;
}
size >>= 1;
const step4 = step << 2;
__init(x, y, size, val, step4, mat);
__init(x + size, y + size, size, val + step, step4, mat);
__init(x + size, y, size, val + step * 2, step4, mat);
__init(x, y + size, size, val + step * 3, step4, mat);
return mat;
};
const defBayer = (size) => ({
mat: __init(0, 0, size, 0, 1, []),
invSize: 1 / (size * size),
mask: size - 1
});
const __orderedDither1 = ({ mat, mask, invSize }, dsteps, drange, srange, x, y, val) => {
val = dsteps * (val / srange) + mat[y & mask][x & mask] * invSize - 0.5 | 0;
dsteps--;
return clamp(val, 0, dsteps) * ((drange - 1) / dsteps);
};
const orderedDither = (img, size, numColors) => {
const { data, format, width } = img;
const steps = isNumber(numColors) ? new Array(format.channels.length).fill(numColors) : numColors;
const mat = isNumber(size) ? defBayer(size) : size;
for (let i = 0, n = data.length, nc = format.channels.length, x = 0, y = 0; i < n; i++) {
let col = data[i];
for (let j = 0; j < nc; j++) {
const ch = format.channels[j];
const num = ch.num;
const cs = steps[j];
cs > 0 && (col = ch.setInt(
col,
__orderedDither1(mat, cs, num, num, x, y, ch.int(col))
));
}
data[i] = col;
if (++x === width) {
x = 0;
y++;
}
}
return img;
};
export {
defBayer,
orderedDither
};