diff options
author | elioat <elioat@tilde.institute> | 2025-03-30 09:34:25 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-30 09:34:25 -0400 |
commit | a564738c865b271a1d9a0161121e176627630141 (patch) | |
tree | a0e978c17e1cb9fc92ccd899eaa40d9ce002646d | |
parent | c169f89e17f4a241ac128c236876d244ba392d4e (diff) | |
download | tour-a564738c865b271a1d9a0161121e176627630141.tar.gz |
*
-rw-r--r-- | js/leibovitz/blur.js | 129 |
1 files changed, 77 insertions, 52 deletions
diff --git a/js/leibovitz/blur.js b/js/leibovitz/blur.js index 6c7aff9..08d1ec5 100644 --- a/js/leibovitz/blur.js +++ b/js/leibovitz/blur.js @@ -6,20 +6,21 @@ const BlurManager = { _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 = 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._slider.addEventListener('input', () => { + const value = this._slider.value; + this._value.textContent = `${value}%`; + this._currentBlur = parseInt(value); this._notifyObservers(); }); }, @@ -40,72 +41,95 @@ const BlurManager = { // Apply Gaussian blur to an image applyBlur(imageData, radius) { - if (!radius || radius <= 0) return imageData; + if (!radius) return imageData; const { data, width, height } = imageData; - const newData = new Uint8ClampedArray(data); + const tempData = new Uint8ClampedArray(data); - // Create Gaussian kernel - const kernel = this._createGaussianKernel(radius); - const kernelSize = kernel.length; - const kernelRadius = Math.floor(kernelSize / 2); + // Calculate the actual image boundaries + let minX = width, minY = height, maxX = 0, maxY = 0; + let hasContent = false; - // Apply horizontal blur + // Find the actual image boundaries for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - const idx = (y * width + x) * 4; + 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 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; + 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++; } } - newData[idx] = r / weightSum; - newData[idx + 1] = g / weightSum; - newData[idx + 2] = b / weightSum; - newData[idx + 3] = a / weightSum; + // 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; } } - // 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; + // 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 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; + 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++; } } - finalData[idx] = r / weightSum; - finalData[idx + 1] = g / weightSum; - finalData[idx + 2] = b / weightSum; - finalData[idx + 3] = a / weightSum; + // 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 new ImageData(finalData, width, height); + return imageData; }, // Create a 1D Gaussian kernel @@ -133,6 +157,7 @@ const BlurManager = { reset() { this._currentBlur = 0; this._slider.value = 0; + this._value.textContent = '0%'; this._notifyObservers(); } }; \ No newline at end of file |