about summary refs log tree commit diff stats
path: root/js/leibovitz/dither.js
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-30 14:17:25 -0400
committerelioat <elioat@tilde.institute>2025-03-30 14:17:25 -0400
commitdc35422ab591a9240485da2e69d10020eb1de5ff (patch)
tree1d315ce4b369040b1fd7c71b70ebe2bec999061c /js/leibovitz/dither.js
parent79e9336d5334c229092ff228e1146a6fdf33793c (diff)
downloadtour-dc35422ab591a9240485da2e69d10020eb1de5ff.tar.gz
*
Diffstat (limited to 'js/leibovitz/dither.js')
-rw-r--r--js/leibovitz/dither.js143
1 files changed, 101 insertions, 42 deletions
diff --git a/js/leibovitz/dither.js b/js/leibovitz/dither.js
index 90d6325..3689354 100644
--- a/js/leibovitz/dither.js
+++ b/js/leibovitz/dither.js
@@ -1,5 +1,9 @@
-// Dithering management module
-// Uses the Observer pattern to notify the main camera module of dithering changes
+/**
+ * Dithering management module implementing various dithering algorithms
+ * Uses the Observer pattern for state management and effect application
+ * Implements block-based processing for performance optimization
+ * Uses the Strategy pattern for algorithm selection
+ */
 
 const DitherManager = {
     // Private state
@@ -9,13 +13,19 @@ const DitherManager = {
     _pixelSizeControl: null,
     currentBlockSize: 4,
 
-    // Initialize the dither manager
+    /**
+     * Initializes the dither manager and sets up UI controls
+     * Implements the Factory pattern for UI initialization
+     */
     init() {
         this._setupEventListeners();
         this._pixelSizeControl = document.getElementById('pixel-size-control');
     },
 
-    // Private methods
+    /**
+     * Sets up event listeners for UI controls
+     * Implements the Observer pattern for state changes
+     */
     _setupEventListeners() {
         this._modeSelect = document.getElementById('dither-select');
         this._modeSelect.addEventListener('change', (e) => {
@@ -43,7 +53,11 @@ const DitherManager = {
         this._observers.forEach(observer => observer(this._currentMode));
     },
 
-    // Public methods
+    /**
+     * Subscribes to dithering state changes
+     * @param {Function} observer - Callback function for state changes
+     * @returns {Function} Unsubscribe function
+     */
     subscribe(observer) {
         this._observers.add(observer);
         return () => this._observers.delete(observer);
@@ -53,7 +67,13 @@ const DitherManager = {
         return this._currentMode;
     },
 
-    // Apply dithering to an image
+    /**
+     * Applies selected dithering algorithm to image data
+     * Implements the Strategy pattern for algorithm selection
+     * @param {ImageData} imageData - Source image data
+     * @param {string} mode - Selected dithering algorithm
+     * @returns {ImageData} Processed image data
+     */
     applyDither(imageData, mode) {
         if (!mode || mode === 'none') return imageData;
 
@@ -75,20 +95,34 @@ const DitherManager = {
         }
     },
 
-    // Quantize a value to create chunkier output
+    /**
+     * Quantizes a value to create chunkier output
+     * Implements the Strategy pattern for quantization
+     * @param {number} value - Input value
+     * @param {number} levels - Number of quantization levels
+     * @returns {number} Quantized value
+     */
     _quantize(value, levels = 4) {
         const step = 255 / (levels - 1);
         return Math.round(value / step) * step;
     },
 
-    // Floyd-Steinberg dithering
+    /**
+     * Applies Floyd-Steinberg dithering algorithm
+     * Implements error diffusion with block-based processing
+     * Uses a 4x4 error distribution pattern for smoother results
+     * @param {Uint8ClampedArray} data - Image data
+     * @param {number} width - Image width
+     * @param {number} height - Image height
+     * @returns {ImageData} Dithered image data
+     */
     _floydSteinbergDither(data, width, height) {
         const newData = new Uint8ClampedArray(data);
         const threshold = 128;
         const levels = 4;
         const blockSize = this.currentBlockSize;
 
-        // Process in blocks
+        // Process in blocks for performance
         for (let y = 0; y < height; y += blockSize) {
             for (let x = 0; x < width; x += blockSize) {
                 // Calculate block average
@@ -143,7 +177,17 @@ const DitherManager = {
         return new ImageData(newData, width, height);
     },
 
-    // Helper method to distribute error to blocks
+    /**
+     * Distributes error to neighboring blocks
+     * @param {Uint8ClampedArray} data - Image data
+     * @param {number} x - X coordinate
+     * @param {number} y - Y coordinate
+     * @param {number} channel - Color channel
+     * @param {number} error - Error value to distribute
+     * @param {number} width - Image width
+     * @param {number} blockSize - Size of processing blocks
+     * @param {number} height - Image height
+     */
     _distributeBlockError(data, x, y, channel, error, width, blockSize, height) {
         for (let by = 0; by < blockSize && y + by < height; by++) {
             for (let bx = 0; bx < blockSize && x + bx < width; bx++) {
@@ -153,7 +197,15 @@ const DitherManager = {
         }
     },
 
-    // Ordered dithering (Bayer matrix)
+    /**
+     * Applies ordered dithering using a Bayer matrix
+     * Implements threshold-based dithering with block processing
+     * Uses a 4x4 Bayer matrix pattern for structured dithering
+     * @param {Uint8ClampedArray} data - Image data
+     * @param {number} width - Image width
+     * @param {number} height - Image height
+     * @returns {ImageData} Dithered image data
+     */
     _orderedDither(data, width, height) {
         const newData = new Uint8ClampedArray(data);
         const matrix = [
@@ -211,7 +263,14 @@ const DitherManager = {
         return new ImageData(newData, width, height);
     },
 
-    // Atkinson dithering with block-based processing
+    /**
+     * Applies Atkinson dithering algorithm
+     * Implements error diffusion with block-based processing
+     * @param {Uint8ClampedArray} data - Image data
+     * @param {number} width - Image width
+     * @param {number} height - Image height
+     * @returns {ImageData} Dithered image data
+     */
     _atkinsonDither(data, width, height) {
         const newData = new Uint8ClampedArray(data);
         const threshold = 128;
@@ -255,22 +314,22 @@ const DitherManager = {
 
                     // Distribute error to neighboring blocks (Atkinson pattern)
                     if (x + blockSize < width) {
-                        this._distributeBlockError(newData, x + blockSize, y, c, error / 8, width, blockSize, height); // right
+                        this._distributeBlockError(newData, x + blockSize, y, c, error / 8, width, blockSize, height);
                         if (x + blockSize * 2 < width) {
-                            this._distributeBlockError(newData, x + blockSize * 2, y, c, error / 8, width, blockSize, height); // right + 1
+                            this._distributeBlockError(newData, x + blockSize * 2, y, c, error / 8, width, blockSize, height);
                         }
                     }
                     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 - blockSize, y + blockSize, c, error / 8, width, blockSize, height);
                         }
-                        this._distributeBlockError(newData, x, y + blockSize, c, error / 8, width, blockSize, height); // bottom
+                        this._distributeBlockError(newData, x, y + blockSize, c, error / 8, width, blockSize, height);
                         if (x + blockSize < width) {
-                            this._distributeBlockError(newData, x + blockSize, y + blockSize, c, error / 8, width, blockSize, height); // bottom right
+                            this._distributeBlockError(newData, x + blockSize, y + blockSize, c, error / 8, width, blockSize, height);
                         }
                     }
                     if (y + blockSize * 2 < height && x + blockSize < width) {
-                        this._distributeBlockError(newData, x + blockSize, y + blockSize * 2, c, error / 8, width, blockSize, height); // bottom + 1
+                        this._distributeBlockError(newData, x + blockSize, y + blockSize * 2, c, error / 8, width, blockSize, height);
                     }
                 }
             }
@@ -279,12 +338,19 @@ const DitherManager = {
         return new ImageData(newData, width, height);
     },
 
-    // Add this as a method in DitherManager
+    /**
+     * Applies Bayer dithering algorithm
+     * Implements threshold-based dithering with block processing
+     * @param {Uint8ClampedArray} data - Image data
+     * @param {number} width - Image width
+     * @param {number} height - Image height
+     * @returns {ImageData} Dithered image data
+     */
     _bayerDither(data, width, height) {
         const newData = new Uint8ClampedArray(data);
         const blockSize = this.currentBlockSize;
         
-        // 4x4 Bayer matrix (simpler and more visible pattern than 8x8)
+        // 4x4 Bayer matrix for pattern generation
         const bayerMatrix = [
             [ 0, 8, 2, 10],
             [12, 4, 14, 6 ],
@@ -292,8 +358,7 @@ const DitherManager = {
             [15, 7, 13, 5 ]
         ];
         
-        // Scale factor to make the pattern more pronounced
-        const scaleFactor = 16; // Increase this value to make pattern more visible
+        const scaleFactor = 16;
         
         // Process in blocks
         for (let y = 0; y < height; y += blockSize) {
@@ -322,7 +387,6 @@ const DitherManager = {
                 
                 // Apply dithering to the block
                 for (let c = 0; c < 3; c++) {
-                    // Normalize pixel value and apply threshold
                     const normalizedPixel = blockAvg[c];
                     const newPixel = normalizedPixel > threshold ? 255 : 0;
                     
@@ -346,7 +410,7 @@ const DitherManager = {
     }
 };
 
-// Update the Bayer dithering to use blocks
+// Legacy functions for backward compatibility
 function bayerDither(imageData, width) {
     const data = new Uint8ClampedArray(imageData.data);
     const height = imageData.data.length / 4 / width;
@@ -424,13 +488,12 @@ function floydSteinbergDither(imageData, width, blockSize) {
 
     const newData = new Uint8ClampedArray(data);
     const threshold = 128;
-    const levels = 4; // Number of quantization levels
+    const levels = 4;
 
     for (let y = 0; y < height; y++) {
         for (let x = 0; x < width; x++) {
             const idx = (y * width + x) * 4;
             
-            // Process each color channel
             for (let c = 0; c < 3; c++) {
                 const oldPixel = data[idx + c];
                 const quantizedPixel = DitherManager._quantize(oldPixel, levels);
@@ -439,17 +502,16 @@ function floydSteinbergDither(imageData, width, blockSize) {
                 
                 newData[idx + c] = newPixel;
                 
-                // Distribute error to neighboring pixels
                 if (x + 1 < width) {
-                    newData[idx + 4 + c] += error * 7 / 16; // right
+                    newData[idx + 4 + c] += error * 7 / 16;
                 }
                 if (y + 1 === height) continue;
                 if (x > 0) {
-                    newData[idx + width * 4 - 4 + c] += error * 3 / 16; // bottom left
+                    newData[idx + width * 4 - 4 + c] += error * 3 / 16;
                 }
-                newData[idx + width * 4 + c] += error * 5 / 16; // bottom
+                newData[idx + width * 4 + c] += error * 5 / 16;
                 if (x + 1 < width) {
-                    newData[idx + width * 4 + 4 + c] += error * 1 / 16; // bottom right
+                    newData[idx + width * 4 + 4 + c] += error * 1 / 16;
                 }
             }
         }
@@ -464,13 +526,12 @@ function atkinsonDither(imageData, width, blockSize) {
 
     const newData = new Uint8ClampedArray(data);
     const threshold = 128;
-    const levels = 4; // Number of quantization levels
+    const levels = 4;
 
     for (let y = 0; y < height; y++) {
         for (let x = 0; x < width; x++) {
             const idx = (y * width + x) * 4;
             
-            // Process each color channel
             for (let c = 0; c < 3; c++) {
                 const oldPixel = data[idx + c];
                 const quantizedPixel = DitherManager._quantize(oldPixel, levels);
@@ -479,24 +540,23 @@ function atkinsonDither(imageData, width, blockSize) {
                 
                 newData[idx + c] = newPixel;
                 
-                // Distribute error to neighboring pixels (Atkinson's algorithm)
                 if (x + 1 < width) {
-                    newData[idx + 4 + c] += error / 8; // right
+                    newData[idx + 4 + c] += error / 8;
                 }
                 if (x + 2 < width) {
-                    newData[idx + 8 + c] += error / 8; // right + 1
+                    newData[idx + 8 + c] += error / 8;
                 }
                 if (y + 1 === height) continue;
                 if (x > 0) {
-                    newData[idx + width * 4 - 4 + c] += error / 8; // bottom left
+                    newData[idx + width * 4 - 4 + c] += error / 8;
                 }
-                newData[idx + width * 4 + c] += error / 8; // bottom
+                newData[idx + width * 4 + c] += error / 8;
                 if (x + 1 < width) {
-                    newData[idx + width * 4 + 4 + c] += error / 8; // bottom right
+                    newData[idx + width * 4 + 4 + c] += error / 8;
                 }
                 if (y + 2 === height) continue;
                 if (x + 1 < width) {
-                    newData[idx + width * 8 + 4 + c] += error / 8; // bottom + 1 right
+                    newData[idx + width * 8 + 4 + c] += error / 8;
                 }
             }
         }
@@ -518,7 +578,7 @@ function orderedDither(imageData, width, blockSize) {
     ];
     const matrixSize = 4;
     const threshold = 128;
-    const levels = 4; // Number of quantization levels
+    const levels = 4;
 
     for (let y = 0; y < height; y++) {
         for (let x = 0; x < width; x++) {
@@ -527,7 +587,6 @@ function orderedDither(imageData, width, blockSize) {
             const matrixY = y % matrixSize;
             const matrixValue = matrix[matrixY][matrixX] * 16;
 
-            // Process each color channel
             for (let c = 0; c < 3; c++) {
                 const pixel = data[idx + c];
                 const quantizedPixel = DitherManager._quantize(pixel, levels);