// 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, // Initialize the blur manager init() { this._setupEventListeners(); }, // Private methods _setupEventListeners() { this._slider = document.getElementById('blur-slider'); this._slider.addEventListener('input', (e) => { this._currentBlur = parseInt(e.target.value); // Update the display value as a percentage document.getElementById('blur-value').textContent = `${Math.round((this._currentBlur / 20) * 100)}%`; 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 || radius <= 0) return imageData; const { data, width, height } = imageData; const newData = new Uint8ClampedArray(data); // Create Gaussian kernel const kernel = this._createGaussianKernel(radius); const kernelSize = kernel.length; const kernelRadius = Math.floor(kernelSize / 2); // Apply horizontal blur for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const idx = (y * width + x) * 4; let r = 0, g = 0, b = 0, a = 0; let weightSum = 0; for (let i = -kernelRadius; i <= kernelRadius; i++) { const px = x + i; if (px >= 0 && px < width) { const pixelIdx = (y * width + px) * 4; const weight = kernel[i + kernelRadius]; r += data[pixelIdx] * weight; g += data[pixelIdx + 1] * weight; b += data[pixelIdx + 2] * weight; a += data[pixelIdx + 3] * weight; weightSum += weight; } } newData[idx] = r / weightSum; newData[idx + 1] = g / weightSum; newData[idx + 2] = b / weightSum; newData[idx + 3] = a / weightSum; } } // Apply vertical blur const finalData = new Uint8ClampedArray(newData); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const idx = (y * width + x) * 4; let r = 0, g = 0, b = 0, a = 0; let weightSum = 0; for (let i = -kernelRadius; i <= kernelRadius; i++) { const py = y + i; if (py >= 0 && py < height) { const pixelIdx = (py * width + x) * 4; const weight = kernel[i + kernelRadius]; r += newData[pixelIdx] * weight; g += newData[pixelIdx + 1] * weight; b += newData[pixelIdx + 2] * weight; a += newData[pixelIdx + 3] * weight; weightSum += weight; } } finalData[idx] = r / weightSum; finalData[idx + 1] = g / weightSum; finalData[idx + 2] = b / weightSum; finalData[idx + 3] = a / weightSum; } } return new ImageData(finalData, width, height); }, // 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._notifyObservers(); } };