about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-30 09:46:28 -0400
committerelioat <elioat@tilde.institute>2025-03-30 09:46:28 -0400
commitc512927296ebccb6daedc1aae2fe3245aab1bd3a (patch)
treeacf06ea42415cf633dc6d7d5f62e267685f58d39
parenta564738c865b271a1d9a0161121e176627630141 (diff)
downloadtour-c512927296ebccb6daedc1aae2fe3245aab1bd3a.tar.gz
*
-rw-r--r--js/leibovitz/index.html27
-rw-r--r--js/leibovitz/leibovitz.js138
2 files changed, 164 insertions, 1 deletions
diff --git a/js/leibovitz/index.html b/js/leibovitz/index.html
index c8eb9af..997760f 100644
--- a/js/leibovitz/index.html
+++ b/js/leibovitz/index.html
@@ -335,6 +335,20 @@
         }
 
         @media (max-width: 600px) {
+            #controls {
+                flex-direction: column;
+            }
+
+            #toggle-camera, button.capture, #edit-image {
+                width: 100%;
+                border-right: none;
+                border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+            }
+
+            #toggle-camera:last-child, button.capture:last-child, #edit-image:last-child {
+                border-bottom: none;
+            }
+
             .slide-controls {
                 flex-direction: column;
                 gap: 12px;
@@ -359,6 +373,17 @@
                 text-align: left;
             }
         }
+        #edit-image {
+            display: block;
+            padding: 20px;
+            font-family: 'ChicagoFLF', sans-serif;
+            border-radius: 0;
+            text-align: center;
+            background-color: #f0f0f0;
+        }
+        #edit-image.hidden {
+            display: none;
+        }
     </style>
 </head>
 <body>
@@ -412,7 +437,9 @@
 
 <div id="controls">
     <button id="toggle-camera">Camera On</button>
+    <button id="edit-image" class="edit-image">Edit Image</button>
     <button id="capture" disabled class="capture">Capture Image</button>
+    <input type="file" id="image-input" accept="image/*" style="display: none;">
 </div>
 
 <script src="dither.js"></script>
diff --git a/js/leibovitz/leibovitz.js b/js/leibovitz/leibovitz.js
index 3b43d86..aa230f9 100644
--- a/js/leibovitz/leibovitz.js
+++ b/js/leibovitz/leibovitz.js
@@ -3,6 +3,8 @@ const ctx = canvas.getContext('2d');
 const video = document.createElement('video');
 const toggleCameraButton = document.getElementById('toggle-camera');
 const captureButton = document.getElementById('capture');
+const editImageButton = document.getElementById('edit-image');
+const imageInput = document.getElementById('image-input');
 const focusControl = document.getElementById('focus-control');
 const focusSlider = document.getElementById('focus-slider');
 const focusValue = document.getElementById('focus-value');
@@ -10,6 +12,8 @@ const focusValue = document.getElementById('focus-value');
 let cameraOn = false;
 let stream = null;
 let track = null;
+let isEditMode = false;
+let originalImage = null; // Store the original image
 
 // Initialize managers
 ColorManager.init();
@@ -54,6 +58,19 @@ window.addEventListener('resize', updateCanvasSize);
 updateCanvasSize();
 
 function startCamera() {
+    // If we're in edit mode, show confirmation dialog
+    if (isEditMode) {
+        if (!confirm('Switching to camera mode will discard your current image and edits. Continue?')) {
+            return; // User cancelled
+        }
+        
+        // Clear edit mode state
+        isEditMode = false;
+        originalImage = null;
+        canvas.style.display = 'none';
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+    }
+
     navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } })
         .then(s => {
             stream = s;
@@ -62,6 +79,9 @@ function startCamera() {
             canvas.style.display = 'block'; // Show the canvas
             captureButton.disabled = false;
             captureButton.active = true;
+            editImageButton.classList.add('hidden');
+            isEditMode = false;
+            originalImage = null; // Clear the original image
 
             track = stream.getVideoTracks()[0];
             const settings = track.getSettings();
@@ -115,9 +135,68 @@ function stopCamera() {
         focusSlider.disabled = true;
         focusControl.style.display = 'none';
         stream = null;
+        editImageButton.classList.remove('hidden');
     }
 }
 
+function loadImage(file) {
+    const reader = new FileReader();
+    reader.onload = function(e) {
+        const img = new Image();
+        img.onload = function() {
+            // Calculate dimensions to maintain aspect ratio
+            const container = document.querySelector('.preview-container');
+            const containerWidth = container.clientWidth;
+            const containerHeight = container.clientHeight;
+            
+            const imgAspect = img.width / img.height;
+            const containerAspect = containerWidth / containerHeight;
+            
+            if (containerAspect > imgAspect) {
+                canvas.height = containerHeight;
+                canvas.width = containerHeight * imgAspect;
+            } else {
+                canvas.width = containerWidth;
+                canvas.height = containerWidth / imgAspect;
+            }
+            
+            // Store the original image
+            originalImage = img;
+            
+            // Draw the image
+            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+            canvas.style.display = 'block';
+            captureButton.disabled = false;
+            captureButton.active = true;
+            
+            // Start the effect loop
+            function step() {
+                if (!isEditMode) return;
+                applyEffects();
+                requestAnimationFrame(step);
+            }
+            requestAnimationFrame(step);
+        };
+        img.src = e.target.result;
+    };
+    reader.readAsDataURL(file);
+}
+
+function applyEffects() {
+    // Clear the canvas
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    
+    // Draw the original image
+    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
+    
+    // Apply effects in sequence
+    applyContrast();
+    applyColorTint();
+    applyBlur();
+    applyBalance();
+    applyDither();
+}
+
 function drawVideoProportional() {
     const videoAspectRatio = video.videoWidth / video.videoHeight;
     const canvasAspectRatio = canvas.width / canvas.height;
@@ -219,6 +298,19 @@ toggleCameraButton.addEventListener('click', () => {
     }
 });
 
+editImageButton.addEventListener('click', () => {
+    if (!cameraOn) {
+        imageInput.click();
+    }
+});
+
+imageInput.addEventListener('change', (e) => {
+    if (e.target.files && e.target.files[0]) {
+        isEditMode = true;
+        loadImage(e.target.files[0]);
+    }
+});
+
 if ('serviceWorker' in navigator) {
     window.addEventListener('load', () => {
         navigator.serviceWorker.register('/service-worker.js')
@@ -230,4 +322,48 @@ if ('serviceWorker' in navigator) {
     });
 }
 
-ColorManager._setupEventListeners();
\ No newline at end of file
+ColorManager._setupEventListeners();
+
+// Update the reset handlers in each manager to trigger a redraw
+function resetEffects() {
+    if (isEditMode && originalImage) {
+        applyEffects();
+    }
+}
+
+// Add reset handlers to each manager
+BlurManager.reset = function() {
+    this._currentBlur = 0;
+    this._slider.value = 0;
+    this._value.textContent = '0%';
+    this._notifyObservers();
+    resetEffects();
+};
+
+ContrastManager.reset = function() {
+    this._currentContrast = 1.0;
+    this._slider.value = 0;
+    document.getElementById('contrast-value').textContent = '0';
+    this._notifyObservers();
+    resetEffects();
+};
+
+ColorManager.reset = function() {
+    this._currentColor = null;
+    this._colorInput.value = '#ffffff';
+    this._notifyObservers();
+    resetEffects();
+};
+
+BalanceManager.reset = function() {
+    this.balanceSlider.value = 6500;
+    this.balanceValue.textContent = '6500K';
+    resetEffects();
+};
+
+DitherManager.reset = function() {
+    this._currentMode = 'none';
+    this._modeSelect.value = 'none';
+    this._notifyObservers();
+    resetEffects();
+};
\ No newline at end of file