diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/leibovitz/balance.js | 96 | ||||
-rw-r--r-- | js/leibovitz/blur.js | 43 | ||||
-rw-r--r-- | js/leibovitz/color.js | 38 | ||||
-rw-r--r-- | js/leibovitz/contrast.js | 27 | ||||
-rw-r--r-- | js/leibovitz/dither.js | 53 | ||||
-rw-r--r-- | js/leibovitz/leibovitz.js | 71 | ||||
-rw-r--r-- | js/leibovitz/manifest.json | 9 | ||||
-rw-r--r-- | js/pipe.js | 4 |
8 files changed, 194 insertions, 147 deletions
diff --git a/js/leibovitz/balance.js b/js/leibovitz/balance.js index 73f60e8..aeff62e 100644 --- a/js/leibovitz/balance.js +++ b/js/leibovitz/balance.js @@ -1,53 +1,80 @@ /** - * White balance management module implementing temperature-based color adjustment - * Uses the Observer pattern for state management and effect application - * Implements a non-linear temperature adjustment algorithm with RGB channel scaling - * Uses a static class pattern for state management + * White balance management module implementing temperature-based color adjustment. + * + * Implements white balance adjustment using temperature-based RGB channel scaling. + * Provides non-linear temperature adjustment for natural color correction. + * + * Implements the following design patterns: + * - Observer Pattern: state management and effect application + * - Factory Pattern: UI initialization + * - Strategy Pattern: temperature adjustment algorithm + * - Command Pattern: state reset operations + * + * White balance adjustment process: + * 1. Convert temperature to ratio relative to neutral (6500K) + * 2. Apply non-linear scaling (0.2 factor) to red and blue channels + * 3. Warmer temps (<6500K) increase red, decrease blue + * 4. Cooler temps (>6500K) increase blue, decrease red + * + * Features: + * - Temperature-based color adjustment + * - Non-linear response curve + * - Preserves green channel + * - Real-time updates */ -class BalanceManager { +const BalanceManager = { + // Private state + _observers: new Set(), + _slider: null, + _value: null, + /** * Initializes the balance manager and sets up UI controls - * Implements the Factory pattern for UI initialization */ - static init() { - this.balanceSlider = document.getElementById('balance-slider'); - this.balanceValue = document.getElementById('balance-value'); + init() { + this._slider = document.getElementById('balance-slider'); + this._value = document.getElementById('balance-value'); this._setupEventListeners(); - } + }, + + _setupEventListeners() { + this._slider.addEventListener('input', () => { + const value = this._slider.value; + this._value.textContent = `${value}K`; + this._notifyObservers(); + }); + }, + + _notifyObservers() { + this._observers.forEach(observer => observer(this.getCurrentBalance())); + }, /** - * Sets up event listeners for UI controls - * Implements the Observer pattern for state changes + * Subscribes to balance state changes + * @param {Function} observer - Callback function for state changes + * @returns {Function} Unsubscribe function */ - static _setupEventListeners() { - this.balanceSlider.addEventListener('input', () => { - const value = this.balanceSlider.value; - this.balanceValue.textContent = `${value}K`; - }); - } + subscribe(observer) { + this._observers.add(observer); + return () => this._observers.delete(observer); + }, /** * Gets the current white balance temperature * @returns {number} Current temperature in Kelvin (2000K-10000K) */ - static getCurrentBalance() { - return parseInt(this.balanceSlider.value); - } + getCurrentBalance() { + return parseInt(this._slider.value); + }, /** * Applies white balance adjustment to an image - * Implements temperature-based RGB channel scaling with non-linear response + * And implements temperature-based RGB channel scaling with non-linear response * @param {ImageData} imageData - Source image data * @returns {ImageData} White balanced image data - * - * Algorithm: - * 1. Convert temperature to ratio relative to neutral (6500K) - * 2. Apply non-linear scaling (0.2 factor) to red and blue channels - * 3. Warmer temps (<6500K) increase red, decrease blue - * 4. Cooler temps (>6500K) increase blue, decrease red */ - static applyBalance(imageData) { + applyBalance(imageData) { const balance = this.getCurrentBalance(); if (!balance || balance === 6500) return imageData; // 6500K is neutral @@ -63,5 +90,14 @@ class BalanceManager { } return imageData; + }, + + /** + * Resets balance effect to default state + */ + reset() { + this._slider.value = 6500; + this._value.textContent = '6500K'; + this._notifyObservers(); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/js/leibovitz/blur.js b/js/leibovitz/blur.js index 27fa480..bc6cddf 100644 --- a/js/leibovitz/blur.js +++ b/js/leibovitz/blur.js @@ -1,8 +1,24 @@ /** - * Blur management module implementing optimized box blur - * Uses the Observer pattern for state management and effect application - * Implements two-pass box blur algorithm with boundary detection - * Uses content-aware optimization for performance + * 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 = { @@ -12,20 +28,12 @@ const BlurManager = { _slider: null, _value: null, - /** - * Initializes the blur manager and sets up UI controls - * Implements the Factory pattern for UI initialization - */ init() { this._slider = document.getElementById('blur-slider'); this._value = document.getElementById('blur-value'); this._setupEventListeners(); }, - /** - * Sets up event listeners for UI controls - * Implements the Observer pattern for state changes - */ _setupEventListeners() { this._slider.addEventListener('input', () => { const value = this._slider.value; @@ -55,8 +63,8 @@ const BlurManager = { /** * Applies optimized box blur to an image - * Implements two-pass blur with content-aware boundary detection - * Uses separate horizontal and vertical passes for performance + * 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 @@ -93,14 +101,12 @@ const BlurManager = { 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) { @@ -128,7 +134,6 @@ const BlurManager = { 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) { @@ -153,10 +158,6 @@ const BlurManager = { return imageData; }, - /** - * Resets blur effect to default state - * Implements the Command pattern for state reset - */ reset() { this._currentBlur = 0; this._slider.value = 0; diff --git a/js/leibovitz/color.js b/js/leibovitz/color.js index 1438403..78f4ebc 100644 --- a/js/leibovitz/color.js +++ b/js/leibovitz/color.js @@ -1,8 +1,27 @@ /** - * Color tint management module implementing HSL-based color manipulation - * Uses the Observer pattern for state management and effect application - * Implements HSL color space transformation with circular interpolation - * Uses noise reduction and smooth blending for quality + * Color tint management module implementing HSL-based color manipulation. + * + * Implements color tinting using HSL color space transformation with circular interpolation. + * Features noise reduction and smooth blending for high-quality results. + * + * Implements the following design patterns: + * - Observer Pattern: state management and effect application + * - Factory Pattern: UI initialization + * - Strategy Pattern: color manipulation algorithms + * - Command Pattern: state reset operations + * + * Color manipulation process: + * 1. Convert RGB to HSL color space + * 2. Apply circular interpolation for hue blending + * 3. Smooth blending for saturation and lightness + * 4. Noise reduction through value rounding + * 5. Convert back to RGB color space + * + * Features: + * - Circular interpolation for natural hue transitions + * - Noise reduction through value rounding + * - Smooth blending with quadratic easing + * - HSL color space for better color manipulation */ const ColorManager = { @@ -11,18 +30,10 @@ const ColorManager = { _observers: new Set(), _colorInput: null, - /** - * Initializes the color manager and sets up UI controls - * Implements the Factory pattern for UI initialization - */ init() { this._setupEventListeners(); }, - /** - * Sets up event listeners for UI controls - * Implements the Observer pattern for state changes - */ _setupEventListeners() { this._colorInput = document.getElementById('color-tint'); this._colorInput.addEventListener('input', (e) => { @@ -73,7 +84,6 @@ const ColorManager = { /** * Applies color tint to an image using HSL color space - * Implements circular interpolation for hue blending * Uses noise reduction and smooth blending for quality * @param {ImageData} imageData - Source image data * @param {string} color - Hex color value @@ -98,7 +108,7 @@ const ColorManager = { const [h, s, l] = this._rgbToHsl(r, g, b); // Blend the tint color with the original color - // This creates a more natural LUT effect + // This tries to create a more natural LUT effect const blendFactor = 0.15; // Reduced from 0.3 to 0.15 for smoother effect // Smooth blending for hue (circular interpolation) diff --git a/js/leibovitz/contrast.js b/js/leibovitz/contrast.js index 01312ad..c2b1a28 100644 --- a/js/leibovitz/contrast.js +++ b/js/leibovitz/contrast.js @@ -1,7 +1,25 @@ /** - * Contrast management module implementing contrast adjustment - * Uses the Observer pattern for state management and effect application - * Implements linear contrast adjustment algorithm + * Contrast management module implementing linear contrast adjustment. + * + * Implements contrast adjustment using a linear scaling algorithm. + * Provides real-time contrast control with immediate visual feedback. + * + * Implements the following design patterns: + * - Observer Pattern: state management and effect application + * - Factory Pattern: UI initialization + * - Strategy Pattern: contrast adjustment algorithm + * - Command Pattern: state reset operations + * + * Contrast adjustment process: + * 1. Calculate contrast factor using formula: (259 * (contrast + 255)) / (255 * (259 - contrast)) + * 2. Apply linear scaling to each color channel + * 3. Maintain color balance while adjusting contrast + * + * Features: + * - Linear contrast adjustment + * - Per-channel processing + * - Real-time updates + * - Preserves color relationships */ const ContrastManager = { @@ -12,7 +30,6 @@ const ContrastManager = { /** * Initializes the contrast manager and sets up UI controls - * Implements the Factory pattern for UI initialization */ init() { this._setupEventListeners(); @@ -20,7 +37,6 @@ const ContrastManager = { /** * Sets up event listeners for UI controls - * Implements the Observer pattern for state changes */ _setupEventListeners() { this._slider = document.getElementById('contrast-slider'); @@ -75,7 +91,6 @@ const ContrastManager = { /** * Resets contrast effect to default state - * Implements the Command pattern for state reset */ reset() { this._currentContrast = 1.0; diff --git a/js/leibovitz/dither.js b/js/leibovitz/dither.js index b011eca..e74f1be 100644 --- a/js/leibovitz/dither.js +++ b/js/leibovitz/dither.js @@ -1,15 +1,33 @@ /** - * Dithering management module implementing various dithering algorithms - * Uses the Observer pattern for state management and effect application - * Implements block-based processing for performance optimization - * Uses the Strategy pattern for algorithm selection + * Dithering management module implementing multiple dithering algorithms. + * + * Implements a couple dithering algorithms with block-based processing. + * Block-based processing is faster, and has better performance. + * Supports multiple dithering patterns with configurable block sizes. + * + * Implements the following design patterns: + * - Observer Pattern: state management and effect application + * - Factory Pattern: UI initialization + * - Strategy Pattern: dithering algorithm selection + * - Command Pattern: state reset operations + * + * Supported dithering algorithms: + * - Floyd-Steinberg: Error diffusion with standard distribution pattern + * - Ordered: Matrix-based threshold dithering + * - Atkinson: Error diffusion with 1/8 error distribution + * - Bayer: Pattern-based threshold dithering * * Each color channel (Red, Green, Blue) has 4 possible values: - * 0 -> Black/None - * 85 -> Low - * 170 -> Medium - * 255 -> Full + * - 0 -> Black/None + * - 85 -> Low + * - 170 -> Medium + * - 255 -> Full * + * Features: + * - Block-based processing for performance + * - Multiple dithering algorithms + * - Configurable block sizes + * - Error diffusion patterns */ const DitherManager = { @@ -22,17 +40,12 @@ const DitherManager = { /** * Initializes the dither manager and sets up UI controls - * Implements the Factory pattern for UI initialization */ init() { this._setupEventListeners(); this._pixelSizeControl = document.getElementById('pixel-size-control'); }, - /** - * Sets up event listeners for UI controls - * Implements the Observer pattern for state changes - */ _setupEventListeners() { this._modeSelect = document.getElementById('dither-select'); this._modeSelect.addEventListener('change', (e) => { @@ -43,7 +56,7 @@ const DitherManager = { this._notifyObservers(); }); - // Only add the event listener if the element exists + // Only add the event listener if the element actually exists const blockSizeSlider = document.getElementById('block-size-slider'); if (blockSizeSlider) { blockSizeSlider.addEventListener('input', (e) => { @@ -76,7 +89,6 @@ const DitherManager = { /** * Applies selected dithering algorithm to image data - * Implements the Strategy pattern for algorithm selection * @param {ImageData} imageData - Source image data * @param {string} mode - Selected dithering algorithm * @returns {ImageData} Processed image data @@ -104,7 +116,6 @@ const DitherManager = { /** * Quantizes a value to create chunkier output - * Implements the Strategy pattern for quantization * @param {number} value - Input value * @param {number} levels - Number of quantization levels * @returns {number} Quantized value @@ -116,7 +127,6 @@ const DitherManager = { /** * Applies Floyd-Steinberg dithering algorithm - * Implements error diffusion with block-based processing * Uses a 4x4 error distribution pattern for smoother results * @param {Uint8ClampedArray} data - Image data * @param {number} width - Image width @@ -129,7 +139,7 @@ const DitherManager = { const levels = 4; const blockSize = this.currentBlockSize; - // Process in blocks for performance + // Process in blocks, block by block for (let y = 0; y < height; y += blockSize) { for (let x = 0; x < width; x += blockSize) { // Calculate block average @@ -206,8 +216,8 @@ const DitherManager = { /** * Applies ordered dithering using a Bayer matrix - * Implements threshold-based dithering with block processing - * Uses a 4x4 Bayer matrix pattern for structured dithering + * And implements threshold-based dithering with block processing + * Also uses a 4x4 Bayer matrix pattern for structured dithering * @param {Uint8ClampedArray} data - Image data * @param {number} width - Image width * @param {number} height - Image height @@ -272,7 +282,6 @@ const DitherManager = { /** * Applies Atkinson dithering algorithm - * Implements error diffusion with block-based processing * @param {Uint8ClampedArray} data - Image data * @param {number} width - Image width * @param {number} height - Image height @@ -347,7 +356,6 @@ const DitherManager = { /** * Applies Bayer dithering algorithm - * Implements threshold-based dithering with block processing * @param {Uint8ClampedArray} data - Image data * @param {number} width - Image width * @param {number} height - Image height @@ -357,7 +365,6 @@ const DitherManager = { const newData = new Uint8ClampedArray(data); const blockSize = this.currentBlockSize; - // 4x4 Bayer matrix for pattern generation const bayerMatrix = [ [ 0, 8, 2, 10], [12, 4, 14, 6 ], diff --git a/js/leibovitz/leibovitz.js b/js/leibovitz/leibovitz.js index 030e7d2..5cd6f2d 100644 --- a/js/leibovitz/leibovitz.js +++ b/js/leibovitz/leibovitz.js @@ -1,8 +1,27 @@ /** - * Main application entry point for the app. - * Implements a functional architecture with separate managers for each effect. - * Uses the Observer pattern for state management and effect application. - * Implements the State pattern for mode management (camera/edit). + * Start here. + * + * Susan Sontag: + * > The camera makes everyone a tourist in other people's reality, + * > and eventually in one's own. + * + * Uses multiple design patterns for state management and applying effects: + * - Observer Pattern: state management and effect application across modules + * - State Pattern: mode management (camera/edit) + * - Factory Pattern: UI initialization and media device creation + * - Strategy Pattern: algorithm selection when applying an effect + * - Command Pattern: canvas operations and state reset + * - Chain of Responsibility: sequential effect application + * + * + * Separate manager modules for each effect: + * - ColorManager: HSL-based color manipulation + * - DitherManager: multiple dithering algorithms + * - ContrastManager: linear contrast adjustment + * - BlurManager: optimized box blur + * - BalanceManager: temperature-based color adjustment + * + * */ const canvas = document.getElementById('canvas'); @@ -23,7 +42,7 @@ let track = null; let isEditMode = false; let originalImage = null; // Store the original image for edit mode -// Initialize managers - each implements the Observer pattern for state changes +// Initialize managers ColorManager.init(); DitherManager.init(); ContrastManager.init(); @@ -32,7 +51,6 @@ BalanceManager.init(); /** * Updates visibility of controls based on camera/edit mode state - * Uses the State pattern to manage UI visibility */ function updateSliderControlsVisibility() { const settingsContainer = document.getElementById('settings-container'); @@ -47,7 +65,6 @@ function updateSliderControlsVisibility() { /** * Updates canvas dimensions while maintaining aspect ratio - * Implements the Strategy pattern for different aspect ratio calculations */ function updateCanvasSize() { const container = document.querySelector('.preview-container'); @@ -71,7 +88,6 @@ function updateCanvasSize() { } } -// Observer pattern: Listen for window resize events window.addEventListener('resize', () => { if (cameraOn || isEditMode) { updateCanvasSize(); @@ -83,10 +99,6 @@ window.addEventListener('resize', () => { updateCanvasSize(); -/** - * Clears the canvas and resets its state - * Implements the Command pattern for canvas operations - */ function clearCanvas() { const container = document.querySelector('.preview-container'); const containerWidth = container.clientWidth; @@ -132,6 +144,7 @@ function startCamera() { const settings = track.getSettings(); // Feature detection for focus control + // Relatively untested because I don't have a device with focus control if ('focusDistance' in settings) { focusControl.style.display = 'flex'; focusSlider.disabled = false; @@ -173,7 +186,6 @@ function startCamera() { /** * Stops camera stream and resets UI state - * Implements the Command pattern for cleanup operations */ function stopCamera() { if (stream) { @@ -193,7 +205,6 @@ function stopCamera() { /** * Loads and displays an image file - * Implements the Factory pattern for image creation * Uses aspect ratio preservation strategy for responsive display */ function loadImage(file) { @@ -243,8 +254,7 @@ function loadImage(file) { } /** - * Applies all effects in sequence using the Chain of Responsibility pattern - * Each effect is applied using the Strategy pattern for algorithm selection + * Sequentially applies all effects to the original image */ function applyEffects() { ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -258,7 +268,6 @@ function applyEffects() { /** * Draws video feed maintaining aspect ratio - * Implements the Strategy pattern for aspect ratio handling */ function drawVideoProportional() { ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -282,9 +291,6 @@ function drawVideoProportional() { ctx.drawImage(video, offsetX, offsetY, drawWidth, drawHeight); } -/** - * Applies color tint effect using the Strategy pattern - */ function applyColorTint() { const currentColor = ColorManager.getCurrentColor(); if (!currentColor) return; @@ -294,18 +300,12 @@ function applyColorTint() { ctx.putImageData(tintedImageData, 0, 0); } -/** - * Applies white balance effect using the Strategy pattern - */ function applyBalance() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const balancedImageData = BalanceManager.applyBalance(imageData); ctx.putImageData(balancedImageData, 0, 0); } -/** - * Applies contrast effect using the Strategy pattern - */ function applyContrast() { const currentContrast = ContrastManager.getCurrentContrast(); if (!currentContrast || currentContrast === 1.0) return; @@ -315,9 +315,6 @@ function applyContrast() { ctx.putImageData(contrastedImageData, 0, 0); } -/** - * Applies dithering effect using the Strategy pattern - */ function applyDither() { const currentMode = DitherManager.getCurrentMode(); if (!currentMode || currentMode === 'none') return; @@ -327,9 +324,7 @@ function applyDither() { ctx.putImageData(ditheredImageData, 0, 0); } -/** - * Applies blur effect using the Strategy pattern - */ + function applyBlur() { const currentBlur = BlurManager.getCurrentBlur(); if (!currentBlur) return; @@ -341,7 +336,6 @@ function applyBlur() { /** * Captures the current canvas state with effects - * Implements the Command pattern for image capture */ captureButton.addEventListener('click', () => { const currentColor = ColorManager.getCurrentColor(); @@ -366,9 +360,6 @@ captureButton.addEventListener('click', () => { link.click(); }); -/** - * Toggles camera state using the State pattern - */ toggleCameraButton.addEventListener('click', () => { cameraOn = !cameraOn; if (cameraOn) { @@ -380,9 +371,6 @@ toggleCameraButton.addEventListener('click', () => { } }); -/** - * Handles image upload using the Factory pattern - */ editImageButton.addEventListener('click', () => { if (!cameraOn) { imageInput.click(); @@ -398,7 +386,6 @@ imageInput.addEventListener('change', (e) => { /** * Service Worker registration for offline functionality - * Implements the Service Worker pattern for PWA support */ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -413,9 +400,6 @@ if ('serviceWorker' in navigator) { ColorManager._setupEventListeners(); -/** - * Resets all effects using the Command pattern - */ function resetEffects() { if (isEditMode && originalImage) { applyEffects(); @@ -424,7 +408,6 @@ function resetEffects() { /** * Reset handlers for each effect manager - * Implements the Command pattern for state reset */ BlurManager.reset = function() { this._currentBlur = 0; diff --git a/js/leibovitz/manifest.json b/js/leibovitz/manifest.json index 03dc94f..1ddc0b2 100644 --- a/js/leibovitz/manifest.json +++ b/js/leibovitz/manifest.json @@ -50,15 +50,6 @@ "type": "image/png" } ], - "screenshots": [ - { - "src": "screenshot1.png", - "sizes": "1080x1920", - "type": "image/png", - "platform": "narrow", - "label": "Leibovitz Camera App" - } - ], "categories": ["photo", "camera", "art"], "prefer_related_applications": false, "shortcuts": [ diff --git a/js/pipe.js b/js/pipe.js index 69ccae3..ace5fb9 100644 --- a/js/pipe.js +++ b/js/pipe.js @@ -1,2 +1,6 @@ const pipe = (...args) => args.reduce((acc, el) => el(acc)); +/* alt implementation +const pipe = (...fns) => (initialValue) => + fns.reduce((acc, fn) => fn(acc), initialValue); +*/ \ No newline at end of file |