about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-06-29 19:10:09 -0400
committerelioat <elioat@tilde.institute>2024-06-29 19:10:09 -0400
commit18062ebd2bdc5d090676e6f606c33299b665f6cb (patch)
treeb6ce83fc119e919cca724d57f039abddaeb2cab4
parent3acf61d9f774418cafa99f9800aef9bb9741047c (diff)
downloadtour-18062ebd2bdc5d090676e6f606c33299b665f6cb.tar.gz
*
-rw-r--r--js/MAP.md1
-rw-r--r--js/dither/dither.js91
-rw-r--r--js/dither/index.html37
3 files changed, 129 insertions, 0 deletions
diff --git a/js/MAP.md b/js/MAP.md
index 6b1488e..2737154 100644
--- a/js/MAP.md
+++ b/js/MAP.md
@@ -4,6 +4,7 @@
 - `bird-words`, an exploration of Markov chain generation, sort of migrated to <https://git.sr.ht/~eli_oat/beak>
 - `blototboot`, irc bot and broken lisp interpreter
 - `canvas`, an exploration of the HTML canvas, lets you move a little sprite around a canvas
+- `dither`, Floyd-Steinberg dithering
 - `game-frame`, my attempt at creating a generic starting point for HTML/JS game dev
 - `games`, some games from other people
 - `gg`, testing out how to support game controllers from the browser
diff --git a/js/dither/dither.js b/js/dither/dither.js
new file mode 100644
index 0000000..f7e549f
--- /dev/null
+++ b/js/dither/dither.js
@@ -0,0 +1,91 @@
+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
+    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);
+}
\ No newline at end of file
diff --git a/js/dither/index.html b/js/dither/index.html
new file mode 100644
index 0000000..fc06402
--- /dev/null
+++ b/js/dither/index.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Floyd-Steinberg Dithering</title>
+    <style>
+        body {
+            margin: 0 auto;
+            display: block;
+            padding: 2em;
+         }
+        canvas, input, button {
+            max-width: 100%;
+            padding: 1em;
+            margin: 0 auto;
+            display: block;
+            border: 1px solid black;
+        }
+        button, input[type="file"] {
+            cursor: pointer;
+            padding: 1em 2em;
+            font-size: 1.25em;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="canvas"></canvas>
+    <br>
+    <br>
+    <input type="file" id="upload" accept="image/*">
+    <br>
+    <br>
+    <button id="download">Download Image</button>
+    <script src="dither.js"></script>
+</body>
+</html>