// Blur management module // Uses the Observer pattern to notify the main camera module of blur changes const BlurManager = { // Private state _currentBlur: 0, // Default blur (no blur) _observers: new Set(), _slider: null, _value: null, // Initialize the blur manager init() { this._slider = document.getElementById('blur-slider'); this._value = document.getElementById('blur-value'); this._setupEventListeners(); }, // Private methods _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)); }, // Public methods subscribe(observer) { this._observers.add(observer); return () => this._observers.delete(observer); }, getCurrentBlur() { return this._currentBlur; }, // Apply Gaussian blur to an image 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); // Optimized box blur implementation // 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; // Calculate horizontal blur for this pixel 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; // Calculate vertical blur for this pixel 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; }, // Create a 1D Gaussian kernel _createGaussianKernel(radius) { const sigma = radius / 3; const kernelSize = Math.ceil(radius * 2 + 1); const kernel = new Array(kernelSize); let sum = 0; for (let i = 0; i < kernelSize; i++) { const x = i - radius; kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma)); sum += kernel[i]; } // Normalize the kernel for (let i = 0; i < kernelSize; i++) { kernel[i] /= sum; } return kernel; }, // Add reset method reset() { this._currentBlur = 0; this._slider.value = 0; this._value.textContent = '0%'; this._notifyObservers(); } };