diff options
author | elioat <elioat@tilde.institute> | 2025-03-29 23:20:25 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-29 23:20:25 -0400 |
commit | 752545e3350096d7afee89d14623f9a9f9f5076e (patch) | |
tree | 420caa0711e8a3d1b7e7104a63f0f93a8786f7d3 | |
parent | 237686136e5037858e1c4182645991dfe3e7ff98 (diff) | |
download | tour-752545e3350096d7afee89d14623f9a9f9f5076e.tar.gz |
*
-rw-r--r-- | js/leibovitz/blur.js | 138 | ||||
-rw-r--r-- | js/leibovitz/index.html | 6 | ||||
-rw-r--r-- | js/leibovitz/leibovitz.js | 15 |
3 files changed, 158 insertions, 1 deletions
diff --git a/js/leibovitz/blur.js b/js/leibovitz/blur.js new file mode 100644 index 0000000..6c7aff9 --- /dev/null +++ b/js/leibovitz/blur.js @@ -0,0 +1,138 @@ +// 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(); + } +}; \ No newline at end of file diff --git a/js/leibovitz/index.html b/js/leibovitz/index.html index acc592a..0f430f0 100644 --- a/js/leibovitz/index.html +++ b/js/leibovitz/index.html @@ -165,6 +165,11 @@ <input type="range" id="block-size-slider" min="1" max="12" value="4" step="1"> <span id="block-size-value">4px</span> </div> + <div class="contrast-control"> + <label for="blur-slider">Blur</label> + <input type="range" id="blur-slider" min="0" max="20" value="0" step="1"> + <span id="blur-value">0%</span> + </div> </div> <div id="controls"> @@ -178,6 +183,7 @@ <script src="dither.js"></script> <script src="contrast.js"></script> <script src="color.js"></script> +<script src="blur.js"></script> <script src="leibovitz.js"></script> </body> </html> diff --git a/js/leibovitz/leibovitz.js b/js/leibovitz/leibovitz.js index 01ce5ae..73ff7eb 100644 --- a/js/leibovitz/leibovitz.js +++ b/js/leibovitz/leibovitz.js @@ -11,6 +11,7 @@ let stream = null; ColorManager.init(); DitherManager.init(); ContrastManager.init(); +BlurManager.init(); // Set the canvas dimensions to match the window size function updateCanvasSize() { @@ -73,6 +74,7 @@ function startCamera() { drawVideoProportional(); applyContrast(); applyColorTint(); + applyBlur(); applyDither(); requestAnimationFrame(step); } @@ -132,6 +134,15 @@ function applyDither() { ctx.putImageData(ditheredImageData, 0, 0); } +function applyBlur() { + const currentBlur = BlurManager.getCurrentBlur(); + if (!currentBlur) return; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const blurredImageData = BlurManager.applyBlur(imageData, currentBlur); + ctx.putImageData(blurredImageData, 0, 0); +} + captureButton.addEventListener('click', () => { const currentColor = ColorManager.getCurrentColor(); const borderWidth = 4; @@ -179,4 +190,6 @@ if ('serviceWorker' in navigator) { console.log('ServiceWorker registration failed: ', err); }); }); -} \ No newline at end of file +} + +ColorManager._setupEventListeners(); \ No newline at end of file |