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); }