terst

Autochrome Overlay Tool (GPU Blend, CPU Clump)

Autochrome Overlay Tool




Clumping may take time. Please wait for it to finish.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Autochrome Overlay Tool (GPU Blend, CPU Clump)</title>
  <style>
    body { font-family: sans-serif; background: #111; color: #fff; text-align: center; padding: 20px; }
    canvas { margin-top: 10px; border: 1px solid #555; image-rendering: pixelated; }
    #info { margin-top: 10px; font-size: 14px; color: #ccc; white-space: pre-line; }
    #warning { font-size: 13px; color: #f88; }
    input, button, label { margin: 8px; padding: 8px; font-size: 14px; }
  </style>
</head>
<body>
  <h1>Autochrome Overlay Tool</h1>
  <label>Base Image: <input type="file" id="baseImgInput" accept="image/*"></label><br>
  <label>Color 1: <input type="color" id="color1" value="#ff9600"></label>
  <label>Color 2: <input type="color" id="color2" value="#64c832"></label>
  <label>Color 3: <input type="color" id="color3" value="#9600ff"></label><br>
  <label>Effect Strength: <input type="range" id="effectSlider" min="0" max="100" value="100"></label><br>
  <button id="newScreenBtn" disabled>New Screen</button>
  <button id="moreBtn" disabled>Clump</button>
  <div id="warning">Clumping may take time. Please wait for it to finish.</div>
  <button id="saveBtn" disabled>Download</button>
  <div id="info"></div>
  <canvas id="canvas"></canvas>

  <script>
    const canvas = document.getElementById('canvas');
    const gl = canvas.getContext('webgl2', { preserveDrawingBuffer: true });
    if (!gl) alert('WebGL2 not supported');

    const vertexSrc = `#version 300 es
      in vec2 a_position;
      out vec2 v_uv;
      void main() {
        v_uv = a_position * 0.5 + 0.5;
        gl_Position = vec4(a_position, 0, 1);
      }`;

    const fragmentSrc = `#version 300 es
      precision highp float;
      in vec2 v_uv;
      uniform sampler2D u_base;
      uniform sampler2D u_screen;
      uniform float u_strength;
      out vec4 outColor;
      void main() {
        vec2 flippedUV = vec2(v_uv.x, 1.0 - v_uv.y);
        vec4 base = texture(u_base, flippedUV);
        vec4 screen = texture(u_screen, flippedUV);
        outColor.rgb = mix(base.rgb, base.rgb * screen.rgb, u_strength);
        outColor.a = 1.0;
      }`;

    function compileShader(type, source) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(shader));
        return null;
      }
      return shader;
    }

    function createProgram(vsSrc, fsSrc) {
      const vs = compileShader(gl.VERTEX_SHADER, vsSrc);
      const fs = compileShader(gl.FRAGMENT_SHADER, fsSrc);
      const prog = gl.createProgram();
      gl.attachShader(prog, vs);
      gl.attachShader(prog, fs);
      gl.linkProgram(prog);
      if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(prog));
        return null;
      }
      return prog;
    }

    const program = createProgram(vertexSrc, fragmentSrc);
    gl.useProgram(program);

    const positionLoc = gl.getAttribLocation(program, 'a_position');
    const strengthLoc = gl.getUniformLocation(program, 'u_strength');
    const baseLoc = gl.getUniformLocation(program, 'u_base');
    const screenLoc = gl.getUniformLocation(program, 'u_screen');

    const quad = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
    const vao = gl.createVertexArray();
    gl.bindVertexArray(vao);
    const vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(positionLoc);
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);

    function createTexture(image) {
      const tex = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, tex);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      return tex;
    }

    const baseImgInput = document.getElementById('baseImgInput');
    const newScreenBtn = document.getElementById('newScreenBtn');
    const moreBtn = document.getElementById('moreBtn');
    const saveBtn = document.getElementById('saveBtn');
    const info = document.getElementById('info');
    const color1 = document.getElementById('color1');
    const color2 = document.getElementById('color2');
    const color3 = document.getElementById('color3');
    const effectSlider = document.getElementById('effectSlider');

    let baseImage = null;
    let screenCanvas = null;
    let screenCtx = null;
    let clumpIterations = 0;
    let totalSwaps = 0;
    let targetSwaps = 0;
    let baseTex = null, screenTex = null;

    function hexToRGB(hex) {
      const bigint = parseInt(hex.slice(1), 16);
      return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
    }

    function loadImage(file, callback) {
      const img = new Image();
      img.onload = () => callback(img);
      img.src = URL.createObjectURL(file);
    }

    function generateRandomScreen(width, height, colors) {
      const sc = document.createElement('canvas');
      sc.width = width;
      sc.height = height;
      const sctx = sc.getContext('2d');
      const imgData = sctx.createImageData(width, height);
      for (let i = 0; i < imgData.data.length; i += 4) {
        const color = colors[Math.floor(Math.random() * colors.length)];
        imgData.data[i] = color[0];
        imgData.data[i + 1] = color[1];
        imgData.data[i + 2] = color[2];
        imgData.data[i + 3] = 255;
      }
      sctx.putImageData(imgData, 0, 0);
      return sc;
    }

    async function clumpScreen(passes) {
      const width = screenCanvas.width;
      const height = screenCanvas.height;
      const imgData = screenCtx.getImageData(0, 0, width, height);
      const data = imgData.data;
      let swaps = 0;

      for (let i = 0; i < passes; i++) {
        const x = Math.floor(Math.random() * width);
        const y = Math.floor(Math.random() * height);
        const cIdx = (y * width + x) * 4;
        const cr = data[cIdx], cg = data[cIdx + 1], cb = data[cIdx + 2];

        for (let dy = -2; dy <= 2; dy++) {
          for (let dx = -2; dx <= 2; dx++) {
            const nx = (x + dx + width) % width;
            const ny = (y + dy + height) % height;
            const nIdx = (ny * width + nx) * 4;
            if (data[nIdx] === cr && data[nIdx + 1] === cg && data[nIdx + 2] === cb) {
              const sx = (x + Math.sign(dx) + width) % width;
              const sy = (y + Math.sign(dy) + height) % height;
              const sIdx = (sy * width + sx) * 4;
              for (let j = 0; j < 4; j++) {
                const tmp = data[sIdx + j];
                data[sIdx + j] = data[nIdx + j];
                data[nIdx + j] = tmp;
              }
              swaps++;
            }
          }
        }
      }

      screenCtx.putImageData(imgData, 0, 0);
      screenTex = createTexture(screenCanvas);
      return swaps;
    }

    function renderOverlay() {
      gl.viewport(0, 0, canvas.width, canvas.height);
      gl.useProgram(program);
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, baseTex);
      gl.uniform1i(baseLoc, 0);

      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_2D, screenTex);
      gl.uniform1i(screenLoc, 1);

      gl.uniform1f(strengthLoc, effectSlider.value / 100);
      gl.bindVertexArray(vao);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
    }

    function generateNewScreen() {
      const colors = [hexToRGB(color1.value), hexToRGB(color2.value), hexToRGB(color3.value)];
      screenCanvas = generateRandomScreen(canvas.width, canvas.height, colors);
      screenCtx = screenCanvas.getContext('2d');
      screenTex = createTexture(screenCanvas);
      clumpIterations = 0;
      totalSwaps = 0;
      targetSwaps = canvas.width * canvas.height * 20;
      renderOverlay();
      updateInfo();
    }

    function updateInfo(currentPassSwaps = 0, totalThisRun = targetSwaps) {
      const percent = Math.min((totalSwaps / totalThisRun) * 100, 100).toFixed(1);
      info.textContent = `Hi!`;
    }

    baseImgInput.addEventListener('change', () => {
      const file = baseImgInput.files[0];
      if (file) {
        loadImage(file, img => {
          baseImage = img;
          canvas.width = img.width;
          canvas.height = img.height;
          baseTex = createTexture(img);
          generateNewScreen();
          newScreenBtn.disabled = false;
          moreBtn.disabled = false;
          saveBtn.disabled = false;
        });
      }
    });

    newScreenBtn.addEventListener('click', () => {
      generateNewScreen();
    });

    
moreBtn.addEventListener('click', async () => {
  totalSwaps = 0;
  targetSwaps = canvas.width * canvas.height * 20;

  moreBtn.disabled = true;
  const totalPasses = Math.floor(canvas.width * canvas.height * 0.01);
  let donePasses = 0;
  let passBatch = 5000;
  let passSwaps = 0;

  while (donePasses < totalPasses) {
    const batchSize = Math.min(passBatch, totalPasses - donePasses);
    passSwaps = await clumpScreen(batchSize);
    donePasses += batchSize;
    totalSwaps += passSwaps;
    clumpIterations++;
    updateInfo(passSwaps, totalPasses);
    renderOverlay();
    const percent = Math.floor((donePasses / totalPasses) * 100);
    moreBtn.textContent = `Clump (${percent}%)`;
    await new Promise(resolve => setTimeout(resolve, 20));
  }

  moreBtn.textContent = "Clump (100%)";
  moreBtn.disabled = false;
});


    effectSlider.addEventListener('input', renderOverlay);

    saveBtn.addEventListener('click', () => {
      const width = canvas.width;
      const height = canvas.height;
      const pixels = new Uint8Array(width * height * 4);
      gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

      const outputCanvas = document.createElement('canvas');
      outputCanvas.width = width;
      outputCanvas.height = height;
      const ctx = outputCanvas.getContext('2d');
      const imageData = ctx.createImageData(width, height);

      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          const i = (y * width + x) * 4;
          const j = ((height - y - 1) * width + x) * 4;
          imageData.data[i] = pixels[j];
          imageData.data[i + 1] = pixels[j + 1];
          imageData.data[i + 2] = pixels[j + 2];
          imageData.data[i + 3] = pixels[j + 3];
        }
      }

      ctx.putImageData(imageData, 0, 0);
      const link = document.createElement('a');
      link.download = 'autochrome_output.png';
      link.href = outputCanvas.toDataURL('image/png');
      link.click();
    });

    color1.addEventListener('input', generateNewScreen);
    color2.addEventListener('input', generateNewScreen);
    color3.addEventListener('input', generateNewScreen);

function updateColorIndicators() {
  color1.style.backgroundColor = color1.value;
  color2.style.backgroundColor = color2.value;
  color3.style.backgroundColor = color3.value;
}

color1.addEventListener('input', () => {
  updateColorIndicators();
  generateNewScreen();
});
color2.addEventListener('input', () => {
  updateColorIndicators();
  generateNewScreen();
});
color3.addEventListener('input', () => {
  updateColorIndicators();
  generateNewScreen();
});

updateColorIndicators();

  </script>
</body>
</html>