about summary refs log tree commit diff stats
path: root/js/lut-cam
diff options
context:
space:
mode:
Diffstat (limited to 'js/lut-cam')
-rw-r--r--js/lut-cam/android-icon-144x144.pngbin0 -> 3068 bytes
-rw-r--r--js/lut-cam/android-icon-192x192.pngbin0 -> 3253 bytes
-rw-r--r--js/lut-cam/android-icon-36x36.pngbin0 -> 1791 bytes
-rw-r--r--js/lut-cam/android-icon-48x48.pngbin0 -> 1953 bytes
-rw-r--r--js/lut-cam/android-icon-72x72.pngbin0 -> 2429 bytes
-rw-r--r--js/lut-cam/android-icon-96x96.pngbin0 -> 1993 bytes
-rw-r--r--js/lut-cam/apple-icon-114x114.pngbin0 -> 3027 bytes
-rw-r--r--js/lut-cam/apple-icon-120x120.pngbin0 -> 2916 bytes
-rw-r--r--js/lut-cam/apple-icon-144x144.pngbin0 -> 3068 bytes
-rw-r--r--js/lut-cam/apple-icon-152x152.pngbin0 -> 3882 bytes
-rw-r--r--js/lut-cam/apple-icon-180x180.pngbin0 -> 4656 bytes
-rw-r--r--js/lut-cam/apple-icon-57x57.pngbin0 -> 2258 bytes
-rw-r--r--js/lut-cam/apple-icon-60x60.pngbin0 -> 2291 bytes
-rw-r--r--js/lut-cam/apple-icon-72x72.pngbin0 -> 2429 bytes
-rw-r--r--js/lut-cam/apple-icon-76x76.pngbin0 -> 2572 bytes
-rw-r--r--js/lut-cam/apple-icon-precomposed.pngbin0 -> 3689 bytes
-rw-r--r--js/lut-cam/apple-icon.pngbin0 -> 3689 bytes
-rw-r--r--js/lut-cam/browserconfig.xml2
-rw-r--r--js/lut-cam/favicon-16x16.pngbin0 -> 1228 bytes
-rw-r--r--js/lut-cam/favicon-32x32.pngbin0 -> 1813 bytes
-rw-r--r--js/lut-cam/favicon-96x96.pngbin0 -> 1993 bytes
-rw-r--r--js/lut-cam/favicon.icobin0 -> 1150 bytes
-rw-r--r--js/lut-cam/index.html118
-rw-r--r--js/lut-cam/lut-extraction/index.html25
-rw-r--r--js/lut-cam/lut-extraction/sample.jpegbin0 -> 103831 bytes
-rw-r--r--js/lut-cam/lut-extraction/script.js86
-rw-r--r--js/lut-cam/lut.js170
-rw-r--r--js/lut-cam/manifest.json41
-rw-r--r--js/lut-cam/ms-icon-144x144.pngbin0 -> 3068 bytes
-rw-r--r--js/lut-cam/ms-icon-150x150.pngbin0 -> 3884 bytes
-rw-r--r--js/lut-cam/ms-icon-310x310.pngbin0 -> 9141 bytes
-rw-r--r--js/lut-cam/ms-icon-70x70.pngbin0 -> 2518 bytes
-rw-r--r--js/lut-cam/service-worker.js43
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