70 lines
No EOL
1.8 KiB
TypeScript
70 lines
No EOL
1.8 KiB
TypeScript
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
|
|
}
|
|
} |