diff options
Diffstat (limited to 'js/lut-cam')
33 files changed, 485 insertions, 0 deletions
diff --git a/js/lut-cam/android-icon-144x144.png b/js/lut-cam/android-icon-144x144.png new file mode 100644 index 0000000..ae37a7e --- /dev/null +++ b/js/lut-cam/android-icon-144x144.png Binary files differdiff --git a/js/lut-cam/android-icon-192x192.png b/js/lut-cam/android-icon-192x192.png new file mode 100644 index 0000000..4fd03e4 --- /dev/null +++ b/js/lut-cam/android-icon-192x192.png Binary files differdiff --git a/js/lut-cam/android-icon-36x36.png b/js/lut-cam/android-icon-36x36.png new file mode 100644 index 0000000..d5fbc51 --- /dev/null +++ b/js/lut-cam/android-icon-36x36.png Binary files differdiff --git a/js/lut-cam/android-icon-48x48.png b/js/lut-cam/android-icon-48x48.png new file mode 100644 index 0000000..ae93a97 --- /dev/null +++ b/js/lut-cam/android-icon-48x48.png Binary files differdiff --git a/js/lut-cam/android-icon-72x72.png b/js/lut-cam/android-icon-72x72.png new file mode 100644 index 0000000..89d3e98 --- /dev/null +++ b/js/lut-cam/android-icon-72x72.png Binary files differdiff --git a/js/lut-cam/android-icon-96x96.png b/js/lut-cam/android-icon-96x96.png new file mode 100644 index 0000000..a4fcd87 --- /dev/null +++ b/js/lut-cam/android-icon-96x96.png Binary files differdiff --git a/js/lut-cam/apple-icon-114x114.png b/js/lut-cam/apple-icon-114x114.png new file mode 100644 index 0000000..2a2af04 --- /dev/null +++ b/js/lut-cam/apple-icon-114x114.png Binary files differdiff --git a/js/lut-cam/apple-icon-120x120.png b/js/lut-cam/apple-icon-120x120.png new file mode 100644 index 0000000..dd9823f --- /dev/null +++ b/js/lut-cam/apple-icon-120x120.png Binary files differdiff --git a/js/lut-cam/apple-icon-144x144.png b/js/lut-cam/apple-icon-144x144.png new file mode 100644 index 0000000..ae37a7e --- /dev/null +++ b/js/lut-cam/apple-icon-144x144.png Binary files differdiff --git a/js/lut-cam/apple-icon-152x152.png b/js/lut-cam/apple-icon-152x152.png new file mode 100644 index 0000000..c43bf96 --- /dev/null +++ b/js/lut-cam/apple-icon-152x152.png Binary files differdiff --git a/js/lut-cam/apple-icon-180x180.png b/js/lut-cam/apple-icon-180x180.png new file mode 100644 index 0000000..f7435e7 --- /dev/null +++ b/js/lut-cam/apple-icon-180x180.png Binary files differdiff --git a/js/lut-cam/apple-icon-57x57.png b/js/lut-cam/apple-icon-57x57.png new file mode 100644 index 0000000..7f5dfa5 --- /dev/null +++ b/js/lut-cam/apple-icon-57x57.png Binary files differdiff --git a/js/lut-cam/apple-icon-60x60.png b/js/lut-cam/apple-icon-60x60.png new file mode 100644 index 0000000..3a6a826 --- /dev/null +++ b/js/lut-cam/apple-icon-60x60.png Binary files differdiff --git a/js/lut-cam/apple-icon-72x72.png b/js/lut-cam/apple-icon-72x72.png new file mode 100644 index 0000000..89d3e98 --- /dev/null +++ b/js/lut-cam/apple-icon-72x72.png Binary files differdiff --git a/js/lut-cam/apple-icon-76x76.png b/js/lut-cam/apple-icon-76x76.png new file mode 100644 index 0000000..9dc77b1 --- /dev/null +++ b/js/lut-cam/apple-icon-76x76.png Binary files differdiff --git a/js/lut-cam/apple-icon-precomposed.png b/js/lut-cam/apple-icon-precomposed.png new file mode 100644 index 0000000..8e17e9c --- /dev/null +++ b/js/lut-cam/apple-icon-precomposed.png Binary files differdiff --git a/js/lut-cam/apple-icon.png b/js/lut-cam/apple-icon.png new file mode 100644 index 0000000..8e17e9c --- /dev/null +++ b/js/lut-cam/apple-icon.png Binary files differdiff --git a/js/lut-cam/browserconfig.xml b/js/lut-cam/browserconfig.xml new file mode 100644 index 0000000..c554148 --- /dev/null +++ b/js/lut-cam/browserconfig.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig> \ No newline at end of file diff --git a/js/lut-cam/favicon-16x16.png b/js/lut-cam/favicon-16x16.png new file mode 100644 index 0000000..9293108 --- /dev/null +++ b/js/lut-cam/favicon-16x16.png Binary files differdiff --git a/js/lut-cam/favicon-32x32.png b/js/lut-cam/favicon-32x32.png new file mode 100644 index 0000000..b6b0694 --- /dev/null +++ b/js/lut-cam/favicon-32x32.png Binary files differdiff --git a/js/lut-cam/favicon-96x96.png b/js/lut-cam/favicon-96x96.png new file mode 100644 index 0000000..a4fcd87 --- /dev/null +++ b/js/lut-cam/favicon-96x96.png Binary files differdiff --git a/js/lut-cam/favicon.ico b/js/lut-cam/favicon.ico new file mode 100644 index 0000000..8ce4e6e --- /dev/null +++ b/js/lut-cam/favicon.ico Binary files differdiff --git a/js/lut-cam/index.html b/js/lut-cam/index.html new file mode 100644 index 0000000..0f2e2a0 --- /dev/null +++ b/js/lut-cam/index.html @@ -0,0 +1,118 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>LUT Cam</title> + <meta name="description" content="lut cam is a web-based camera with a bunch of preset LUT profiles baked in for you to choose from. It produces images that look sort of like what you can get out of a single-use camera."> + <link rel="apple-touch-icon" sizes="57x57" href="apple-icon-57x57.png"> + <link rel="apple-touch-icon" sizes="60x60" href="apple-icon-60x60.png"> + <link rel="apple-touch-icon" sizes="72x72" href="apple-icon-72x72.png"> + <link rel="apple-touch-icon" sizes="76x76" href="apple-icon-76x76.png"> + <link rel="apple-touch-icon" sizes="114x114" href="apple-icon-114x114.png"> + <link rel="apple-touch-icon" sizes="120x120" href="apple-icon-120x120.png"> + <link rel="apple-touch-icon" sizes="144x144" href="apple-icon-144x144.png"> + <link rel="apple-touch-icon" sizes="152x152" href="apple-icon-152x152.png"> + <link rel="apple-touch-icon" sizes="180x180" href="apple-icon-180x180.png"> + <link rel="icon" type="image/png" sizes="192x192" href="android-icon-192x192.png"> + <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> + <link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"> + <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> + <link rel="manifest" href="manifest.json"> + <meta name="msapplication-TileColor" content="#ffffff"> + <meta name="msapplication-TileImage" content="ms-icon-144x144.png"> + <meta name="theme-color" content="#ffffff"> + <style> + body, html { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: beige; + } + #canvas { + width: 100%; + height: 100%; + object-fit: cover; + display: none; + } + #controls { + position: absolute; + bottom: 10px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + } + #lut-container { + position: absolute; + top: 10px; + right: 10px; + } + button, select { + padding: 10px; + font-size: 18px; + cursor: pointer; + } + + button, select { + border: none; + cursor: pointer; + transition: background-color 0.3s ease; + } + + button:hover, button:focus { + outline: none; + } + + button.capture { + background-color: teal; + color: #FFFFFF; + } + + button.capture:disabled { + background-color: #ccc; + color: #5c5c5c; + } + + </style> +</head> +<body> + +<canvas id="canvas"></canvas> + +<div id="lut-container"> + <select id="lut-select" disabled> + <option value="none">None</option> + <option value="lut1">Inverted</option> + <option value="lut2">Vapor</option> + <option value="lut3">Subtle Cool Tone</option> + <option value="lut4">Subtle Warm Tone</option> + <option value="lut5">Subtle Green Tone</option> + <option value="lut6">Subtle Yellow Tone</option> + <option value="lut7">Desaturated</option> + <option value="lut8">Saturated</option> + <!-- <option value="lut9">Cool Tint</option> + <option value="lut10">Warm Tint</option> --> + <option value="lut11">Greyscale</option> + <option value="lut12">Sepia</option> + <option value="lut13">High Contrast</option> + </select> +</div> + +<div id="controls"> + <div id="focus-container"> + <input style="display: none;" type="range" id="focus-slider" min="0" max="100" step="1" value="50" disabled> + </div> + <button id="toggle-camera">Turn Camera On</button> + <button id="capture" disabled class="capture">Capture Image</button> +</div> + + +<script src="lut.js"></script> +</body> +</html> diff --git a/js/lut-cam/lut-extraction/index.html b/js/lut-cam/lut-extraction/index.html new file mode 100644 index 0000000..4202e8f --- /dev/null +++ b/js/lut-cam/lut-extraction/index.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>LUT Profile Extractor</title> + <style> + body { + width: 100%; + height: 100%; + background-color: beige; + } + </style> +</head> +<body> + <h1>LUT Profile Extractor</h1> + <input type="file" id="lut-upload" accept="image/png"> + <canvas id="lut-preview" style="display:none;"></canvas> + <canvas id="sample-output"></canvas> + <img id="sample-image" src="sample.jpeg" alt="Photograph of a colorful parrot." /> + <h2>LUT Profile JS Code:</h2> + <pre id="lut-code"></pre> + <script src="script.js"></script> +</body> +</html> diff --git a/js/lut-cam/lut-extraction/sample.jpeg b/js/lut-cam/lut-extraction/sample.jpeg new file mode 100644 index 0000000..adea4b9 --- /dev/null +++ b/js/lut-cam/lut-extraction/sample.jpeg Binary files differdiff --git a/js/lut-cam/lut-extraction/script.js b/js/lut-cam/lut-extraction/script.js new file mode 100644 index 0000000..f4baeee --- /dev/null +++ b/js/lut-cam/lut-extraction/script.js @@ -0,0 +1,86 @@ +document.getElementById('lut-upload').addEventListener('change', function(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + const img = new Image(); + img.src = e.target.result; + img.onload = function() { + const lutCanvas = document.getElementById('lut-preview'); + const lutContext = lutCanvas.getContext('2d'); + lutCanvas.width = img.width; + lutCanvas.height = img.height; + lutContext.drawImage(img, 0, 0); + + const lutData = lutContext.getImageData(0, 0, lutCanvas.width, lutCanvas.height).data; + const lutWidth = img.width; + const lutHeight = img.height; + const lutSize = Math.cbrt(lutWidth * lutHeight); + + const sampleImage = document.getElementById('sample-image'); + sampleImage.onload = function() { + applyLUTToSampleImage(lutData, lutWidth, lutHeight, lutSize); + }; + + if (sampleImage.complete) { + applyLUTToSampleImage(lutData, lutWidth, lutHeight, lutSize); + } + + outputLUTAsJSCode(lutData, lutWidth, lutHeight, lutSize); + }; + }; + reader.readAsDataURL(file); + } +}); + +function applyLUTToSampleImage(lutData, lutWidth, lutHeight, lutSize) { + const sampleImage = document.getElementById('sample-image'); + const outputCanvas = document.getElementById('sample-output'); + const outputContext = outputCanvas.getContext('2d'); + + outputCanvas.width = sampleImage.width; + outputCanvas.height = sampleImage.height; + outputContext.drawImage(sampleImage, 0, 0); + + const imageData = outputContext.getImageData(0, 0, outputCanvas.width, outputCanvas.height); + const pixels = imageData.data; + + for (let i = 0; i < pixels.length; i += 4) { + const r = pixels[i]; + const g = pixels[i + 1]; + const b = pixels[i + 2]; + + const lutX = Math.floor(r / 255 * (lutSize - 1)); + const lutY = Math.floor(g / 255 * (lutSize - 1)); + const lutZ = Math.floor(b / 255 * (lutSize - 1)); + + const lutIndex = ((lutZ * lutSize + lutY) * lutSize + lutX) * 4; + + pixels[i] = lutData[lutIndex]; + pixels[i + 1] = lutData[lutIndex + 1]; + pixels[i + 2] = lutData[lutIndex + 2]; + } + + outputContext.putImageData(imageData, 0, 0); +} + +function outputLUTAsJSCode(lutData, lutWidth, lutHeight, lutSize) { + const lutCodeElement = document.getElementById('lut-code'); + let lutCode = 'const lut = [\n'; + + for (let z = 0; z < lutSize; z++) { + lutCode += ' [\n'; + for (let y = 0; y < lutSize; y++) { + lutCode += ' [\n'; + for (let x = 0; x < lutSize; x++) { + const index = ((z * lutSize + y) * lutSize + x) * 4; + lutCode += ` { r: ${lutData[index]}, g: ${lutData[index + 1]}, b: ${lutData[index + 2]} },\n`; + } + lutCode += ' ],\n'; + } + lutCode += ' ],\n'; + } + + lutCode += '];\n'; + lutCodeElement.textContent = lutCode; +} diff --git a/js/lut-cam/lut.js b/js/lut-cam/lut.js new file mode 100644 index 0000000..ff2bb41 --- /dev/null +++ b/js/lut-cam/lut.js @@ -0,0 +1,170 @@ +const canvas = document.getElementById('canvas'); +const ctx = canvas.getContext('2d'); +const video = document.createElement('video'); +const toggleCameraButton = document.getElementById('toggle-camera'); +const lutSelect = document.getElementById('lut-select'); +const captureButton = document.getElementById('capture'); + +let cameraOn = false; +let stream = null; + +// Set the canvas dimensions to match the window size +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; + +const LUTs = { + 'none': null, + 'lut1': (r, g, b) => [255 - r, 255 - g, 255 - b], + 'lut2': (r, g, b) => [r * 1.2, g * 0.8, b * 1.5], + 'lut3': (r, g, b) => [r * 0.9, g * 0.9, b * 1.1], + 'lut4': (r, g, b) => [r * 1.1, g * 0.9, b * 0.9], + 'lut5': (r, g, b) => [r * 0.9, g * 1.1, b * 0.9], + 'lut6': (r, g, b) => [r * 1.1, g * 1.1, b * 0.9], + 'lut7': (r, g, b) => [r * 0.9, g * 0.9, b * 0.9], + 'lut8': (r, g, b) => [r * 1.1, g * 1.1, b * 1.1], + 'lut9': (r, g, b) => [r * 0.9, g * 1.1, b * 1.1], + 'lut10': (r, g, b) => [r * 1.1, g * 0.9, b * 0.9], + 'lut11': (r, g, b) => { const avg = (r + g + b) / 3; return [avg, avg, avg]; }, + 'lut12': (r, g, b) => { const avg = (r + g + b) / 3; return [avg * 1.1, avg * 0.9, avg * 0.9]; }, + 'lut13': (r, g, b) => [r * 1.5, g * 1.5, b * 1.5] +}; + +let currentLUT = null; + +const focusSlider = document.getElementById('focus-slider'); +let track = null; + +function startCamera() { + navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } }) + .then(s => { + stream = s; + video.srcObject = stream; + video.play(); + canvas.style.display = 'block'; // Show the canvas + lutSelect.disabled = false; + captureButton.disabled = false; + captureButton.active = true; + focusSlider.disabled = false; + + track = stream.getVideoTracks()[0]; + const settings = track.getSettings(); + + // So, I don't seem to have a device where this is supported + // Check if focus control is supported with this browser and device combo + if ('focusDistance' in settings) { + // If it is available, enable the slider for focus control + focusSlider.style.display = 'block'; + + // Set initial focus value, and a fallback + focusSlider.value = settings.focusDistance || focusSlider.min; + + focusSlider.addEventListener('input', () => { + track.applyConstraints({ + advanced: [{ focusDistance: focusSlider.value }] + }); + }); + } else { + console.warn('Focus control is not supported on this device.'); + focusSlider.style.display = 'none'; + } + + // Draw the video feed to the canvas + video.addEventListener('play', function() { + function step() { + if (!cameraOn) return; + drawVideoProportional(); + applyLUT(); + requestAnimationFrame(step); + } + requestAnimationFrame(step); + }); + }) + .catch(err => { + console.error('Error accessing camera: ', err); + }); +} + +function stopCamera() { + if (stream) { + stream.getTracks().forEach(track => track.stop()); + video.pause(); + canvas.style.display = 'none'; // hide the canvas + lutSelect.disabled = true; + captureButton.disabled = true; + captureButton.active = false; + focusSlider.disabled = true; + stream = null; + } +} + + +function drawVideoProportional() { + const videoAspectRatio = video.videoWidth / video.videoHeight; + const canvasAspectRatio = canvas.width / canvas.height; + + let drawWidth, drawHeight; + + if (canvasAspectRatio > videoAspectRatio) { + drawHeight = canvas.height; + drawWidth = videoAspectRatio * drawHeight; + } else { + drawWidth = canvas.width; + drawHeight = drawWidth / videoAspectRatio; + } + + const offsetX = (canvas.width - drawWidth) / 2; + const offsetY = (canvas.height - drawHeight) / 2; + + ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas + ctx.drawImage(video, offsetX, offsetY, drawWidth, drawHeight); // Draw the video normally +} + +function applyLUT() { + if (!currentLUT) return; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + const [r, g, b] = currentLUT(data[i], data[i + 1], data[i + 2]); + data[i] = r; + data[i + 1] = g; + data[i + 2] = b; + } + + ctx.putImageData(imageData, 0, 0); +} + +toggleCameraButton.addEventListener('click', () => { + cameraOn = !cameraOn; + if (cameraOn) { + startCamera(); + toggleCameraButton.textContent = 'Turn Camera Off'; + } else { + stopCamera(); + toggleCameraButton.textContent = 'Turn Camera On'; + } +}); + +lutSelect.addEventListener('change', () => { + const selectedLUT = lutSelect.value; + currentLUT = LUTs[selectedLUT]; +}); + +captureButton.addEventListener('click', () => { + const link = document.createElement('a'); + link.download = 'captured-image.png'; + link.href = canvas.toDataURL('image/png'); + link.click(); +}); + +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/service-worker.js') + .then(registration => { + console.log('ServiceWorker registration successful with scope: ', registration.scope); + }, err => { + console.log('ServiceWorker registration failed: ', err); + }); + }); +} \ No newline at end of file diff --git a/js/lut-cam/manifest.json b/js/lut-cam/manifest.json new file mode 100644 index 0000000..1f51b17 --- /dev/null +++ b/js/lut-cam/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "Lut Cam", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/js/lut-cam/ms-icon-144x144.png b/js/lut-cam/ms-icon-144x144.png new file mode 100644 index 0000000..ae37a7e --- /dev/null +++ b/js/lut-cam/ms-icon-144x144.png Binary files differdiff --git a/js/lut-cam/ms-icon-150x150.png b/js/lut-cam/ms-icon-150x150.png new file mode 100644 index 0000000..d9edbdb --- /dev/null +++ b/js/lut-cam/ms-icon-150x150.png Binary files differdiff --git a/js/lut-cam/ms-icon-310x310.png b/js/lut-cam/ms-icon-310x310.png new file mode 100644 index 0000000..9512221 --- /dev/null +++ b/js/lut-cam/ms-icon-310x310.png Binary files differdiff --git a/js/lut-cam/ms-icon-70x70.png b/js/lut-cam/ms-icon-70x70.png new file mode 100644 index 0000000..45b1734 --- /dev/null +++ b/js/lut-cam/ms-icon-70x70.png Binary files differdiff --git a/js/lut-cam/service-worker.js b/js/lut-cam/service-worker.js new file mode 100644 index 0000000..3ed58c1 --- /dev/null +++ b/js/lut-cam/service-worker.js @@ -0,0 +1,43 @@ +const CACHE_NAME = 'lut-cam-cache-v1'; +const urlsToCache = [ + '/', + './', + 'index.html', + 'lut.js', + 'service-worker.js', + 'android-icon-192x192.png', + 'android-icon-512x512.png', + 'favicon.ico', + 'favicon-16x16.png', + 'favicon-32x32.png', + 'favicon-96x96.png', + 'apple-icon-57x57.png', + 'apple-icon-60x60.png', + 'apple-icon-72x72.png', + 'apple-icon-76x76.png', + 'apple-icon-114x114.png', + 'apple-icon-120x120.png', + 'apple-icon-144x144.png', + 'apple-icon-152x152.png' +]; + +self.addEventListener('install', event => { + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + return cache.addAll(urlsToCache); + }) + ); +}); + +self.addEventListener('fetch', event => { + event.respondWith( + caches.match(event.request) + .then(response => { + if (response) { + return response; + } + return fetch(event.request); + }) + ); +}); \ No newline at end of file |