diff options
Diffstat (limited to 'js/leibovitz/blur.js')
-rw-r--r-- | js/leibovitz/blur.js | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/js/leibovitz/blur.js b/js/leibovitz/blur.js new file mode 100644 index 0000000..bc6cddf --- /dev/null +++ b/js/leibovitz/blur.js @@ -0,0 +1,167 @@ +/** + * Blur management module implementing optimized box blur algorithm. + * + * Implements a two-pass box blur algorithm with boundary optimization. + * Uses block-based processing for improved performance on large images. + * + * Implements the following design patterns: + * - Observer Pattern: state management and effect application + * - Factory Pattern: UI initialization + * - Strategy Pattern: blur algorithm implementation + * - Command Pattern: state reset operations + * + * The blur implementation uses a two-pass approach: + * 1. Horizontal pass: Applies blur along rows + * 2. Vertical pass: Applies blur along columns + * + * Features: + * - Boundary optimization for performance + * - Block-based processing + * - Two-pass implementation for better performance + * - Edge clamping to prevent artifacts + */ + +const BlurManager = { + // Private state + _currentBlur: 0, // Default blur (no blur) + _observers: new Set(), + _slider: null, + _value: null, + + init() { + this._slider = document.getElementById('blur-slider'); + this._value = document.getElementById('blur-value'); + this._setupEventListeners(); + }, + + _setupEventListeners() { + this._slider.addEventListener('input', () => { + const value = this._slider.value; + this._value.textContent = `${value}%`; + this._currentBlur = parseInt(value); + this._notifyObservers(); + }); + }, + + _notifyObservers() { + this._observers.forEach(observer => observer(this._currentBlur)); + }, + + /** + * Subscribes to blur 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); + }, + + getCurrentBlur() { + return this._currentBlur; + }, + + /** + * Applies optimized box blur to an image + * And implements two-pass blur with content-aware boundary detection + * Uses separate horizontal and vertical passes, which is more performant + * @param {ImageData} imageData - Source image data + * @param {number} radius - Blur radius + * @returns {ImageData} Blurred image data + */ + applyBlur(imageData, radius) { + if (!radius) return imageData; + + const { data, width, height } = imageData; + const tempData = new Uint8ClampedArray(data); + + // Calculate the actual image boundaries + let minX = width, minY = height, maxX = 0, maxY = 0; + let hasContent = false; + + // Find the actual image boundaries + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4; + if (data[i + 3] > 0) { // Check alpha channel + hasContent = true; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + } + + if (!hasContent) return imageData; + + // Add padding to boundaries to prevent edge artifacts + minX = Math.max(0, minX - radius); + minY = Math.max(0, minY - radius); + maxX = Math.min(width - 1, maxX + radius); + maxY = Math.min(height - 1, maxY + radius); + + // First pass: horizontal blur + for (let y = minY; y <= maxY; y++) { + for (let x = minX; x <= maxX; x++) { + let r = 0, g = 0, b = 0, a = 0; + let count = 0; + + for (let dx = -radius; dx <= radius; dx++) { + const nx = x + dx; + if (nx >= 0 && nx < width) { + const i = (y * width + nx) * 4; + r += data[i]; + g += data[i + 1]; + b += data[i + 2]; + a += data[i + 3]; + count++; + } + } + + // Store horizontal blur result + const i = (y * width + x) * 4; + tempData[i] = r / count; + tempData[i + 1] = g / count; + tempData[i + 2] = b / count; + tempData[i + 3] = a / count; + } + } + + // Second pass: vertical blur + for (let y = minY; y <= maxY; y++) { + for (let x = minX; x <= maxX; x++) { + let r = 0, g = 0, b = 0, a = 0; + let count = 0; + + for (let dy = -radius; dy <= radius; dy++) { + const ny = y + dy; + if (ny >= 0 && ny < height) { + const i = (ny * width + x) * 4; + r += tempData[i]; + g += tempData[i + 1]; + b += tempData[i + 2]; + a += tempData[i + 3]; + count++; + } + } + + // Store final blur result + const i = (y * width + x) * 4; + data[i] = r / count; + data[i + 1] = g / count; + data[i + 2] = b / count; + data[i + 3] = a / count; + } + } + + return imageData; + }, + + reset() { + this._currentBlur = 0; + this._slider.value = 0; + this._value.textContent = '0%'; + this._notifyObservers(); + } +}; \ No newline at end of file |