<script> (function() // ----------------------------- CONFIGURATION --------------------------------- const LUT_SIZE = 16; // 16x16 look-up table const CELL_SIZE = 32; // 512/16 = 32px per cell const CANVAS_SIZE = LUT_SIZE * CELL_SIZE; // 512
.info-bar display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: 1rem; margin-top: 1.2rem; margin-bottom: 0.8rem; color: #bfd9ff; font-weight: 500; lut generator 0.15
input[type="range"]:focus outline: none; but typical LUT painter: // brush adds or sets
.controls display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; margin: 1.5rem 0 0.8rem 0; but user expects varying
/* main instrument panel */ .lut-container background: rgba(22, 28, 38, 0.85); backdrop-filter: blur(2px); border-radius: 3rem; padding: 1.8rem 2rem 2rem 2rem; box-shadow: 0 20px 35px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255,255,255,0.05); border: 1px solid rgba(72, 187, 255, 0.2);
const canvas = document.getElementById('lutCanvas'); const ctx = canvas.getContext('2d'); // LUT data: 2D array of floats 0..1 (representing intensity / output value) let lut = Array(LUT_SIZE).fill().map(() => Array(LUT_SIZE).fill(0.0)); // UI elements const coordDisplay = document.getElementById('coordDisplay'); const valueDisplay = document.getElementById('valueDisplay'); const editStrengthSlider = document.getElementById('editStrength'); // painting state let isPainting = false; let lastCol = -1, lastRow = -1; // -------------------------- HELPER FUNCTIONS --------------------------------- function clamp(value, min, max) return Math.min(max, Math.max(min, value)); // update canvas from current LUT data (draw color-mapped grid) function renderLUT() if (!ctx) return; for (let row = 0; row < LUT_SIZE; row++) for (let col = 0; col < LUT_SIZE; col++) const intensity = lut[row][col]; // Color palette: from deep purple/blue (0) to bright amber/white (1) // Enhanced aesthetic: start (0) = #0f172a -> mid (0.5) = #efb36e -> high (1) = #faf0ca const r = Math.floor( 35 + intensity * 220 ); const g = Math.floor( 40 + intensity * 180 ); const b = Math.floor( 80 + intensity * 140 ); const fillColor = `rgb($r, $g, $b)`; ctx.fillStyle = fillColor; ctx.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE); // subtle grid lines (modern thin border) ctx.strokeStyle = "rgba(20, 40, 65, 0.7)"; ctx.lineWidth = 0.8; ctx.strokeRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE); // additional outer glow ctx.strokeStyle = "#3b82f680"; ctx.lineWidth = 2; ctx.strokeRect(2, 2, CANVAS_SIZE-4, CANVAS_SIZE-4); // update value display for a given cell function updateValueDisplay(col, row) if (col >= 0 && col < LUT_SIZE && row >= 0 && row < LUT_SIZE) const val = lut[row][col]; valueDisplay.innerText = `LUT value: $val.toFixed(3)`; coordDisplay.innerHTML = `📍 [$col, $row]`; else valueDisplay.innerText = `LUT value: —`; // set single LUT cell with edit strength influence (additive / replace depending on mode) // we implement painting: newVal = oldVal + delta * strength, clamped 0..1 // delta = (targetIntensity - oldVal) but we define simple absolute set with weight: // In paint mode we apply an absolute target = editStrength? Actually better: paint sets to value based on editStrength: // if edit strength = 0.2 -> increment 0.2 per click? but more intuitive: painting sets to a value: // we use editStrength as a "blend factor" between current value and 1.0 for additive? but typical LUT painter: // brush adds or sets. Let's implement "additive brush" with strength, but also holding modifier? // For simplicity, use "click/drag" sets the cell to current edit strength (absolute) ? but user expects varying. // Better: paint modifies cell value by adding editStrength (capped) or subtract? but it's more intuitive to set directly? // we'll do direct set: mouse click sets value = editStrength (0..1). However, for dragging we may want to increment? // but for natural LUT design: let brush = set value to editStrength. but hold shift? no, keep minimalist: each click/drag sets cell value = editStrength. // but that overwrites, might be annoying. Alternative: "add/subtract"? I'll implement painting with "value = editStrength" for direct mapping, // but also allow "shift + drag" to smooth? but we have clear random etc. // Let's refine: LUT Generator 0.15 features: click/drag to "draw" with current strength value (absolute). // So you can paint exact value from 0 to 1 using slider. function setCellValue(col, row, value) // Paint at canvas coordinates (mouse) function handlePaint(mouseX, mouseY) const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; // canvas physical size 512 / display width const scaleY = canvas.height / rect.height; const canvasX = (mouseX - rect.left) * scaleX; const canvasY = (mouseY - rect.top) * scaleY; if (canvasX >= 0 && canvasX < CANVAS_SIZE && canvasY >= 0 && canvasY < CANVAS_SIZE) const col = Math.floor(canvasX / CELL_SIZE); const row = Math.floor(canvasY / CELL_SIZE); const strength = parseFloat(editStrengthSlider.value); setCellValue(col, row, strength); lastCol = col; lastRow = row; else lastCol = -1; lastRow = -1; // hover update function updateHover(mouseX, mouseY) const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const canvasX = (mouseX - rect.left) * scaleX; const canvasY = (mouseY - rect.top) * scaleY; if (canvasX >= 0 && canvasX < CANVAS_SIZE && canvasY >= 0 && canvasY < CANVAS_SIZE) const col = Math.floor(canvasX / CELL_SIZE); const row = Math.floor(canvasY / CELL_SIZE); coordDisplay.innerHTML = `📍 [$col, $row]`; const val = lut[row][col]; valueDisplay.innerText = `LUT value: $val.toFixed(3)`; else coordDisplay.innerHTML = `⏺ hover: [— , —]`; valueDisplay.innerText = `LUT value: —`; // --------------- LUT GENERATION PRESETS (v0.15 features) ------------------- function randomizeLUT() for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) lut[i][j] = Math.random(); renderLUT(); updateValueDisplay(lastCol, lastRow); function clearLUT() for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) lut[i][j] = 0.0; renderLUT(); updateValueDisplay(lastCol, lastRow); function gradientDiagonal() // diagonal gradient from top-left (0) to bottom-right (1) for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) const t = (i + j) / (2 * (LUT_SIZE - 1)); lut[i][j] = clamp(t, 0, 1); renderLUT(); function sinusoidalRipple() // organic wave pattern for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) const x = j / (LUT_SIZE-1) * Math.PI * 2; const y = i / (LUT_SIZE-1) * Math.PI * 2; let val = (Math.sin(x * 1.8) * Math.cos(y * 1.8) + 1) / 2; val = clamp(val, 0, 1); lut[i][j] = val; renderLUT(); function checkerboardPattern() const block = 8; // 8x8 checkerboard inside 16x16 => 2x2 blocks each cell size for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) const check = (Math.floor(i / (LUT_SIZE/block)) + Math.floor(j / (LUT_SIZE/block))) % 2; lut[i][j] = check === 0 ? 0.2 : 0.85; renderLUT(); function invertLUT() for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) lut[i][j] = 1.0 - lut[i][j]; renderLUT(); updateValueDisplay(lastCol, lastRow); function smoothLUT() // 3x3 box blur (non-edge clamp, reflect boundaries) const original = lut.map(row => [...row]); for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) let sum = 0; let count = 0; for (let di = -1; di <= 1; di++) for (let dj = -1; dj <= 1; dj++) const ni = i + di; const nj = j + dj; if (ni >= 0 && ni < LUT_SIZE && nj >= 0 && nj < LUT_SIZE) sum += original[ni][nj]; count++; lut[i][j] = sum / count; renderLUT(); updateValueDisplay(lastCol, lastRow); // -------------------------- EVENT LISTENERS ---------------------------------- function attachCanvasEvents() // Mouse / touch painting const getCanvasCoords = (e) => let clientX, clientY; if (e.touches) clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; e.preventDefault(); else clientX = e.clientX; clientY = e.clientY; return clientX, clientY ; ; const onPointerStart = (e) => e.preventDefault(); isPainting = true; const clientX, clientY = getCanvasCoords(e); handlePaint(clientX, clientY); ; const onPointerMove = (e) => if (!isPainting) const clientX, clientY = getCanvasCoords(e); updateHover(clientX, clientY); return; const clientX, clientY = getCanvasCoords(e); handlePaint(clientX, clientY); ; const onPointerEnd = () => isPainting = false; lastCol = -1; lastRow = -1; ; canvas.addEventListener('mousedown', onPointerStart); window.addEventListener('mousemove', onPointerMove); window.addEventListener('mouseup', onPointerEnd); canvas.addEventListener('touchstart', onPointerStart, passive: false ); window.addEventListener('touchmove', onPointerMove, passive: false ); window.addEventListener('touchend', onPointerEnd); canvas.addEventListener('mouseleave', () => if (!isPainting) coordDisplay.innerHTML = `⏺ hover: [— , —]`; valueDisplay.innerText = `LUT value: —`; else onPointerEnd(); ); // reset LUT with interesting start: slight gradient (so not empty) function initDefaultLUT() for (let i = 0; i < LUT_SIZE; i++) for (let j = 0; j < LUT_SIZE; j++) // subtle radial-ish default: center bright const cx = (j - 7.5) / 8; const cy = (i - 7.5) / 8; const dist = Math.sqrt(cx*cx + cy*cy); lut[i][j] = clamp(1.0 - dist * 0.9, 0.2, 0.95); renderLUT(); // bind UI buttons function bindControls() document.getElementById('randomizeBtn').addEventListener('click', randomizeLUT); document.getElementById('smoothBtn').addEventListener('click', smoothLUT); document.getElementById('gradientBtn').addEventListener('click', gradientDiagonal); document.getElementById('clearBtn').addEventListener('click', clearLUT); document.getElementById('sinusoidalBtn').addEventListener('click', sinusoidalRipple); document.getElementById('checkerBtn').addEventListener('click', checkerboardPattern); document.getElementById('invertBtn').addEventListener('click', invertLUT); // additional style: editStrength slider shows current value in tooltip style? not necessary, but functional // optional: double click resets to default? function extraFeatures() canvas.addEventListener('dblclick', (e) => // double click to reset default radient initDefaultLUT(); const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const mouseX = e.clientX; const mouseY = e.clientY; const canvasX = (mouseX - rect.left) * scaleX; const canvasY = (mouseY - rect.top) * scaleY; if (canvasX >=0 && canvasX < CANVAS_SIZE && canvasY >=0 && canvasY < CANVAS_SIZE) const col = Math.floor(canvasX / CELL_SIZE); const row = Math.floor(canvasY / CELL_SIZE); updateValueDisplay(col, row); else updateValueDisplay(-1,-1); ); // resize/initialize canvas properly function initCanvas() canvas.width = CANVAS_SIZE; canvas.height = CANVAS_SIZE; canvas.style.width = '100%'; canvas.style.height = 'auto'; ctx.imageSmoothingEnabled = false; // keep sharp pixels function startup() initCanvas(); initDefaultLUT(); attachCanvasEvents(); bindControls(); extraFeatures(); // initial hover display cleanup renderLUT(); coordDisplay.innerHTML = `⏺ hover: [— , —]`; valueDisplay.innerText = `LUT value: 0.00`; startup(); )(); </script> </body> </html>
input[type="range"] width: 160px; height: 4px; -webkit-appearance: none; background: #2c3e4e; border-radius: 5px; outline: none;
No products in the cart.