about summary refs log tree commit diff stats
path: root/js/dither/dither.js
blob: 7bfdc13a4eb9231901e2132cc0794d137fc9df42 (plain) (blame)
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const upload = document.getElementById('upload');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

upload.addEventListener('change', function (e) {
    const file = e.target.files[0];
    const reader = new FileReader();
    reader.onload = function (event) {
        const img = new Image();
        img.onload = function () {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0);
            applyDithering();
        };
        img.src = event.target.result;
    };
    reader.readAsDataURL(file);
});

// TIL how to do this!
document.getElementById('download').addEventListener('click', function() {
    const dataUrl = canvas.toDataURL('image/png'); // Convert the canvas to a data URL
    const a = document.createElement('a'); // Create an anchor element
    a.href = dataUrl; // Set the href of the anchor to the data URL
    a.download = 'dithered-image.png'; // Set the download attribute to the desired file name
    a.setAttribute('rel', 'noopener noreferrer');
    a.setAttribute('target', '_blank');
    document.body.appendChild(a); // Append the anchor to the body
    a.click(); // Trigger a click on the anchor to start the download
    document.body.removeChild(a); // Remove the anchor from the body
});

function applyDithering() {
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    const width = canvas.width;
    const height = canvas.height;

    // Find the gray value of a pixel
    const getGrayValue = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;

    // Set the pixel value
    const setPixel = (x, y, value) => {
        const index = (y * width + x) * 4;
        const gray = value < 128 ? 0 : 255;
        data[index] = gray;
        data[index + 1] = gray;
        data[index + 2] = gray;
        data[index + 3] = 255;
    };

    // https://en.wikipedia.org/wiki/Floyd–Steinberg_dithering

    // Loop through each pixel in the image
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const index = (y * width + x) * 4;
            const oldR = data[index];
            const oldG = data[index + 1];
            const oldB = data[index + 2];
            const oldGray = getGrayValue(oldR, oldG, oldB);
            const newGray = oldGray < 128 ? 0 : 255;
            const error = oldGray - newGray;

            setPixel(x, y, oldGray);

            // Spread error to neighboring pixels
            if (x + 1 < width) {
                data[(y * width + x + 1) * 4] += error * 7 / 16;
                data[(y * width + x + 1) * 4 + 1] += error * 7 / 16;
                data[(y * width + x + 1) * 4 + 2] += error * 7 / 16;
            }
            if (y + 1 < height) {
                if (x - 1 >= 0) {
                    data[((y + 1) * width + x - 1) * 4] += error * 3 / 16;
                    data[((y + 1) * width + x - 1) * 4 + 1] += error * 3 / 16;
                    data[((y + 1) * width + x - 1) * 4 + 2] += error * 3 / 16;
                }
                data[((y + 1) * width + x) * 4] += error * 5 / 16;
                data[((y + 1) * width + x) * 4 + 1] += error * 5 / 16;
                data[((y + 1) * width + x) * 4 + 2] += error * 5 / 16;
                if (x + 1 < width) {
                    data[((y + 1) * width + x + 1) * 4] += error * 1 / 16;
                    data[((y + 1) * width + x + 1) * 4 + 1] += error * 1 / 16;
                    data[((y + 1) * width + x + 1) * 4 + 2] += error * 1 / 16;
                }
            }
        }
    }

    ctx.putImageData(imageData, 0, 0);
}