-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathditherworker.js
73 lines (65 loc) · 3.31 KB
/
ditherworker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
onmessage = function (e) {
const result = dither(e.data.imageData, e.data.pixelSize, e.data.cutoff, e.data.blackRGBA, e.data.whiteRGBA)
const reply = {}
reply.imageData = result
reply.pixelSize = e.data.pixelSize
reply.cutoff = e.data.cutoff
postMessage(reply)
}
function getRGBAArrayBuffer(color) {
let buffer = new ArrayBuffer(4)
for (let i = 0; i < 4; ++i) {
buffer[i] = color[i]
}
return buffer
}
function dither(imageData, scaleFactor, cutoff, blackRGBA, whiteRGBA) {
const blackRGBABuffer = getRGBAArrayBuffer(blackRGBA)
const whiteRGBABuffer = getRGBAArrayBuffer(whiteRGBA)
let output = new ImageData(imageData.width * scaleFactor, imageData.height * scaleFactor)
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] = imageData.data[i + 1] = imageData.data[i + 2] = Math.floor(imageData.data[i] * 0.3 + imageData.data[i + 1] * 0.59 + imageData.data[i + 2] * 0.11)
}
// most implementations I see just distribute error into the existing image, wrapping around edge pixels
// this implementation uses a sliding window of floats for more accuracy (probably not needed really)
let slidingErrorWindow = [new Float32Array(imageData.width), new Float32Array(imageData.width), new Float32Array(imageData.width)]
const offsets = [[1, 0], [2, 0], [-1, 1], [0, 1], [1, 1], [0, 2]]
for (let y = 0, limY = imageData.height; y < limY; ++y) {
for (let x = 0, limX = imageData.width; x < limX; ++x) {
let i = ((y * limX) + x) * 4;
let accumulatedError = Math.floor(slidingErrorWindow[0][x])
let expectedMono = imageData.data[i] + accumulatedError
let monoValue = expectedMono
if (monoValue <= Math.floor(cutoff * 255)) {
monoValue = 0
} else {
monoValue = 255
}
let error = (expectedMono - monoValue) / 8.0
for (let q = 0; q < offsets.length; ++q) {
let offsetX = offsets[q][0] + x
let offsetY = offsets[q][1] + y
if ((offsetX >= 0) && (offsetX < slidingErrorWindow[0].length))
slidingErrorWindow[offsets[q][1]][offsetX] += error
}
// this is stupid but we have to do the pixel scaling ourselves because safari insists on interpolating putImageData
// which gives us blurry pixels (and it doesn't support the createImageBitmap call with an ImageData instance which
// would make this easy)
let rgba = (monoValue == 0) ? blackRGBABuffer : whiteRGBABuffer
for (let scaleY = 0; scaleY < scaleFactor; ++scaleY) {
let pixelOffset = (((y * scaleFactor + scaleY) * output.width) + (x * scaleFactor)) * 4
for (let scaleX = 0; scaleX < scaleFactor; ++scaleX) {
output.data[pixelOffset] = rgba[0]
output.data[pixelOffset + 1] = rgba[1]
output.data[pixelOffset + 2] = rgba[2]
output.data[pixelOffset + 3] = rgba[3]
pixelOffset += 4
}
}
}
// move the sliding window
slidingErrorWindow.push(slidingErrorWindow.shift())
slidingErrorWindow[2].fill(0, 0, slidingErrorWindow[2].length)
}
return output
}