diff options
author | elioat <elioat@tilde.institute> | 2025-03-30 14:17:25 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-30 14:17:25 -0400 |
commit | dc35422ab591a9240485da2e69d10020eb1de5ff (patch) | |
tree | 1d315ce4b369040b1fd7c71b70ebe2bec999061c /js/leibovitz/dither.js | |
parent | 79e9336d5334c229092ff228e1146a6fdf33793c (diff) | |
download | tour-dc35422ab591a9240485da2e69d10020eb1de5ff.tar.gz |
*
Diffstat (limited to 'js/leibovitz/dither.js')
-rw-r--r-- | js/leibovitz/dither.js | 143 |
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); |