info-display/dither.ts

70 lines
1.8 KiB
TypeScript
Raw Normal View History

2023-05-25 22:59:22 +02:00
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
}
}