import Jimp from 'npm:jimp@0.22.8' function floydSteinberg (data: Buffer, w: number, h: number) { const errorMultiplier = .6 // 1 is normal, 0.5 is "reduced color bleeding" const filter = [ [0, 0, 0, 7 / 48, 5 / 48], [3 / 48, 5 / 48, 7 / 48, 5 / 48, 3 / 48], [1 / 48, 3 / 48, 5 / 48, 3 / 48, 1 / 48] ] const error = Array(h) let r: number let g: number let b: number for (let y = 0; y < h; y++) error[y] = new Float32Array(w) for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const id = ((y * w) + x) * 4 r = data[id] g = data[id + 1] b = data[id + 2] let avg = (r + g + b) / 3 avg -= error[y][x] * errorMultiplier let e = 0 if (avg < 128) { e = -avg avg = 0 } else { e = 255 - avg avg = 255 } data[id] = data[id + 1] = data[id + 2] = avg data[id + 3] = 255 for (let yy = 0; yy < 3; yy++) { for (let xx = -2; xx <= 2; xx++) { if (y + yy < 0 || h <= y + yy || x + xx < 0 || w <= x + xx) continue error[y + yy][x + xx] += e * filter[yy][xx + 2] } } } } return data } export default async function process (source: string, destinationWidth?: number, destinationHeight?: number, storeAs?: string): Uint8ClampedArray | null { try { const image = await Jimp.read(source) const width = destinationWidth ?? image.bitmap.width const height = destinationHeight ?? image.bitmap.height image.grayscale().cover(width, height) const data = floydSteinberg(image.bitmap.data, width, height) if (storeAs) { new Jimp({data, width, height }, (err, image) => image.write(storeAs)) } return new Uint8ClampedArray(data) } catch (error) { console.error('failed to process image', error) return null } }