about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-29 23:02:17 -0400
committerelioat <elioat@tilde.institute>2025-03-29 23:02:17 -0400
commitf185a42a9f1f9431e6208bed6516737d92efefbd (patch)
treed853fa92f79d5211f5381eed36458aed65a5da6d
parent771835d9e10686e0a89712af5cd1c3825c1cfc39 (diff)
downloadtour-f185a42a9f1f9431e6208bed6516737d92efefbd.tar.gz
*
-rw-r--r--js/leibovitz/dither.js169
1 files changed, 122 insertions, 47 deletions
diff --git a/js/leibovitz/dither.js b/js/leibovitz/dither.js
index 90c4ae2..9e1ffb1 100644
--- a/js/leibovitz/dither.js
+++ b/js/leibovitz/dither.js
@@ -162,20 +162,46 @@ const DitherManager = {
         ];
         const matrixSize = 4;
         const threshold = 128;
-        const levels = 4; // Number of quantization levels
+        const levels = 4;
+        const blockSize = this.currentBlockSize;
 
-        for (let y = 0; y < height; y++) {
-            for (let x = 0; x < width; x++) {
-                const idx = (y * width + x) * 4;
-                const matrixX = x % matrixSize;
-                const matrixY = y % matrixSize;
+        // Process in blocks
+        for (let y = 0; y < height; y += blockSize) {
+            for (let x = 0; x < width; x += blockSize) {
+                // Calculate block average
+                let blockSum = [0, 0, 0];
+                let pixelCount = 0;
+                
+                for (let by = 0; by < blockSize && y + by < height; by++) {
+                    for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                        const idx = ((y + by) * width + (x + bx)) * 4;
+                        for (let c = 0; c < 3; c++) {
+                            blockSum[c] += newData[idx + c];
+                        }
+                        pixelCount++;
+                    }
+                }
+
+                // Calculate block average
+                const blockAvg = blockSum.map(sum => sum / pixelCount);
+                
+                // Get matrix value for this block
+                const matrixX = Math.floor(x / blockSize) % matrixSize;
+                const matrixY = Math.floor(y / blockSize) % matrixSize;
                 const matrixValue = matrix[matrixY][matrixX] * 16;
 
-                // Process each color channel
+                // Apply dithering to the block
                 for (let c = 0; c < 3; c++) {
-                    const pixel = newData[idx + c];
-                    const quantizedPixel = this._quantize(pixel, levels);
-                    newData[idx + c] = quantizedPixel + matrixValue > threshold ? 255 : 0;
+                    const quantizedPixel = this._quantize(blockAvg[c], levels);
+                    const newPixel = quantizedPixel + matrixValue > threshold ? 255 : 0;
+
+                    // Fill the entire block with the new color
+                    for (let by = 0; by < blockSize && y + by < height; by++) {
+                        for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                            const idx = ((y + by) * width + (x + bx)) * 4;
+                            newData[idx + c] = newPixel;
+                        }
+                    }
                 }
             }
         }
@@ -183,43 +209,66 @@ const DitherManager = {
         return new ImageData(newData, width, height);
     },
 
-    // Atkinson dithering
+    // Atkinson dithering with block-based processing
     _atkinsonDither(data, width, height) {
         const newData = new Uint8ClampedArray(data);
         const threshold = 128;
-        const levels = 4; // Number of quantization levels
+        const levels = 4;
+        const blockSize = this.currentBlockSize;
+
+        // Process in blocks
+        for (let y = 0; y < height; y += blockSize) {
+            for (let x = 0; x < width; x += blockSize) {
+                // Calculate block average
+                let blockSum = [0, 0, 0];
+                let pixelCount = 0;
+                
+                for (let by = 0; by < blockSize && y + by < height; by++) {
+                    for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                        const idx = ((y + by) * width + (x + bx)) * 4;
+                        for (let c = 0; c < 3; c++) {
+                            blockSum[c] += newData[idx + c];
+                        }
+                        pixelCount++;
+                    }
+                }
 
-        for (let y = 0; y < height; y++) {
-            for (let x = 0; x < width; x++) {
-                const idx = (y * width + x) * 4;
+                // Calculate block average
+                const blockAvg = blockSum.map(sum => sum / pixelCount);
                 
-                // Process each color channel
+                // Apply dithering to the block average
                 for (let c = 0; c < 3; c++) {
-                    const oldPixel = newData[idx + c];
+                    const oldPixel = blockAvg[c];
                     const quantizedPixel = this._quantize(oldPixel, levels);
                     const newPixel = quantizedPixel > threshold ? 255 : 0;
                     const error = oldPixel - newPixel;
-                    
-                    newData[idx + c] = newPixel;
-                    
-                    // Distribute error to neighboring pixels (Atkinson's algorithm)
-                    if (x + 1 < width) {
-                        newData[idx + 4 + c] += error / 8; // right
-                    }
-                    if (x + 2 < width) {
-                        newData[idx + 8 + c] += error / 8; // right + 1
+
+                    // Fill the entire block with the new color
+                    for (let by = 0; by < blockSize && y + by < height; by++) {
+                        for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                            const idx = ((y + by) * width + (x + bx)) * 4;
+                            newData[idx + c] = newPixel;
+                        }
                     }
-                    if (y + 1 === height) continue;
-                    if (x > 0) {
-                        newData[idx + width * 4 - 4 + c] += error / 8; // bottom left
+
+                    // Distribute error to neighboring blocks (Atkinson pattern)
+                    if (x + blockSize < width) {
+                        this._distributeBlockError(newData, x + blockSize, y, c, error / 8, width, blockSize, height); // right
+                        if (x + blockSize * 2 < width) {
+                            this._distributeBlockError(newData, x + blockSize * 2, y, c, error / 8, width, blockSize, height); // right + 1
+                        }
                     }
-                    newData[idx + width * 4 + c] += error / 8; // bottom
-                    if (x + 1 < width) {
-                        newData[idx + width * 4 + 4 + c] += error / 8; // bottom right
+                    if (y + blockSize < height) {
+                        if (x - blockSize >= 0) {
+                            this._distributeBlockError(newData, x - blockSize, y + blockSize, c, error / 8, width, blockSize, height); // bottom left
+                        }
+                        this._distributeBlockError(newData, x, y + blockSize, c, error / 8, width, blockSize, height); // bottom
+                        if (x + blockSize < width) {
+                            this._distributeBlockError(newData, x + blockSize, y + blockSize, c, error / 8, width, blockSize, height); // bottom right
+                        }
                     }
-                    if (y + 2 === height) continue;
-                    if (x + 1 < width) {
-                        newData[idx + width * 8 + 4 + c] += error / 8; // bottom + 1 right
+                    if (y + blockSize * 2 < height && x + blockSize < width) {
+                        this._distributeBlockError(newData, x + blockSize, y + blockSize * 2, c, error / 8, width, blockSize, height); // bottom + 1
                     }
                 }
             }
@@ -229,11 +278,11 @@ const DitherManager = {
     }
 };
 
-// Bayer dithering implementation using 8x8 Bayer matrix
-// Pattern: https://en.wikipedia.org/wiki/Ordered_dithering
-function bayerDither(imageData, width, blockSize) {
+// Update the Bayer dithering to use blocks
+function bayerDither(imageData, width) {
     const data = new Uint8ClampedArray(imageData.data);
     const height = imageData.data.length / 4 / width;
+    const blockSize = DitherManager.currentBlockSize;
     
     // 8x8 Bayer matrix normalized to 0-1 range
     const bayerMatrix = [
@@ -245,19 +294,45 @@ function bayerDither(imageData, width, blockSize) {
         [34, 18, 46, 30, 33, 17, 45, 29],
         [10, 58,  6, 54,  9, 57,  5, 53],
         [42, 26, 38, 22, 41, 25, 37, 21]
-    ].map(row => row.map(x => x / 64)); // Normalize to 0-1
+    ].map(row => row.map(x => x / 64));
+
+    // Process in blocks
+    for (let y = 0; y < height; y += blockSize) {
+        for (let x = 0; x < width; x += blockSize) {
+            // Calculate block average
+            let blockSum = [0, 0, 0];
+            let pixelCount = 0;
+            
+            for (let by = 0; by < blockSize && y + by < height; by++) {
+                for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                    const idx = ((y + by) * width + (x + bx)) * 4;
+                    for (let c = 0; c < 3; c++) {
+                        blockSum[c] += data[idx + c];
+                    }
+                    pixelCount++;
+                }
+            }
 
-    for (let y = 0; y < height; y++) {
-        for (let x = 0; x < width; x++) {
-            const i = (y * width + x) * 4;
+            // Calculate block average
+            const blockAvg = blockSum.map(sum => sum / pixelCount);
             
-            // Get threshold from Bayer matrix (wrapping around with modulo)
-            const threshold = bayerMatrix[y % 8][x % 8];
+            // Get threshold from Bayer matrix for this block
+            const matrixX = Math.floor(x / blockSize) % 8;
+            const matrixY = Math.floor(y / blockSize) % 8;
+            const threshold = bayerMatrix[matrixY][matrixX];
             
-            // Apply threshold to each color channel
+            // Apply threshold to the block
             for (let c = 0; c < 3; c++) {
-                const normalizedPixel = data[i + c] / 255;
-                data[i + c] = normalizedPixel > threshold ? 255 : 0;
+                const normalizedPixel = blockAvg[c] / 255;
+                const newPixel = normalizedPixel > threshold ? 255 : 0;
+                
+                // Fill the entire block with the new color
+                for (let by = 0; by < blockSize && y + by < height; by++) {
+                    for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
+                        const idx = ((y + by) * width + (x + bx)) * 4;
+                        data[idx + c] = newPixel;
+                    }
+                }
             }
         }
     }