<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Embedded Adversarial Noise Merger</title>
<style>
  body {
    font-family: Arial, sans-serif;
    margin: 20px;
    background: #f5f5f5;
  }
  h1 {
    text-align: center;
    margin-bottom: 20px;
  }
  .container {
    max-width: 700px;
    margin: 0 auto;
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px #ccc;
  }
  input[type="file"] {
    display: block;
    margin-bottom: 10px;
  }
  canvas {
    display: block;
    margin: 20px auto;
    max-width: 100%;
    border: 1px solid #ccc;
  }
  button {
    display: block;
    margin: 10px auto 10px auto;
    padding: 10px 20px;
    font-size: 16px;
    cursor: pointer;
  }
  #resultInfo {
    text-align: center;
    margin-top: 10px;
    font-size: 14px;
    color: #555;
  }
  label[for], .section-label {
    font-weight: bold;
    margin-top: 10px;
    display: block;
    text-align: center;
  }
  input[type="range"] {
    width: 100%;
    margin: 5px 0 10px 0;
  }
  .channel-checkboxes {
    text-align: center;
    margin: 5px 0 15px 0;
  }
  .channel-checkboxes label {
    margin: 0 10px;
    font-weight: normal;
  }
  select {
    width: 100%;
    margin: 5px 0 10px 0;
    padding: 5px;
  }
</style>
</head>
<body>
  <h1>Embedded Adversarial Noise Merger</h1>
  <div class="container">
    <label for="image1">Select Base Image:</label>
    <input type="file" id="image1" accept="image/*" />
    <label for="image2">Select Noise Image:</label>
    <input type="file" id="image2" accept="image/*" />

    <label for="perturbationScale">Perturbation Scale: <span id="scaleValue">0.10</span></label>
    <input type="range" id="perturbationScale" min="0" max="1" step="0.01" value="0.10" />

    <label class="section-label">Noise Color Channel Influence</label>
    <div class="channel-checkboxes">
      <label><input type="checkbox" id="channelR" checked /> R</label>
      <label><input type="checkbox" id="channelG" checked /> G</label>
      <label><input type="checkbox" id="channelB" checked /> B</label>
    </div>

    <label><input type="checkbox" id="invertNoise" /> Invert Noise Image</label>

    <label for="threshold">Perturbation Threshold / Clipping: <span id="thresholdValue">255</span></label>
    <input type="range" id="threshold" min="0" max="255" step="1" value="255" />

    <label for="contrast">Noise Contrast: <span id="contrastValue">1.00</span></label>
    <input type="range" id="contrast" min="0" max="3" step="0.01" value="1" />

    <label for="brightness">Noise Brightness: <span id="brightnessValue">0</span></label>
    <input type="range" id="brightness" min="-100" max="100" step="1" value="0" />

    <label for="noisePattern">Noise Pattern Type</label>
    <select id="noisePattern">
      <option value="uploaded">Uploaded Noise Image</option>
      <option value="gaussian">Gaussian Noise</option>
      <option value="saltpepper">Salt & Pepper Noise</option>
    </select>

    <label for="blurRadius">Blurring / Smoothing Noise (px radius): <span id="blurValue">0</span></label>
    <input type="range" id="blurRadius" min="0" max="10" step="1" value="0" />

    <label for="gamma">Dynamic Range Compression (Gamma): <span id="gammaValue">1.00</span></label>
    <input type="range" id="gamma" min="0.1" max="3" step="0.01" value="1" />

    <button id="mergeBtn" disabled>Merge Images</button>
    <canvas id="canvas"></canvas>
    <div id="resultInfo"></div>
  </div>
<script>
  const image1Input = document.getElementById('image1');
  const image2Input = document.getElementById('image2');
  const mergeBtn = document.getElementById('mergeBtn');
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const resultInfo = document.getElementById('resultInfo');

  const perturbationScaleInput = document.getElementById('perturbationScale');
  const scaleValueLabel = document.getElementById('scaleValue');

  const channelR = document.getElementById('channelR');
  const channelG = document.getElementById('channelG');
  const channelB = document.getElementById('channelB');

  const invertNoiseInput = document.getElementById('invertNoise');

  const thresholdInput = document.getElementById('threshold');
  const thresholdValueLabel = document.getElementById('thresholdValue');

  const contrastInput = document.getElementById('contrast');
  const contrastValueLabel = document.getElementById('contrastValue');

  const brightnessInput = document.getElementById('brightness');
  const brightnessValueLabel = document.getElementById('brightnessValue');

  const noisePatternSelect = document.getElementById('noisePattern');

  const blurRadiusInput = document.getElementById('blurRadius');
  const blurValueLabel = document.getElementById('blurValue');

  const gammaInput = document.getElementById('gamma');
  const gammaValueLabel = document.getElementById('gammaValue');

  let img1 = null;
  let img2 = null;
  let baseData = null;
  let noiseData = null;

  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  function enableMergeIfReady() {
    mergeBtn.disabled = !(img1 && (img2 || noisePatternSelect.value !== "uploaded"));
  }

  function loadImage(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = reader.result;
      };
      reader.onerror = reject;
      reader.readAsDataURL(file);
    });
  }

  image1Input.addEventListener('change', async (e) => {
    if(e.target.files.length === 0) return;
    try {
      img1 = await loadImage(e.target.files[0]);
      resetData();
      enableMergeIfReady();
      resultInfo.textContent = '';
      clearCanvas();
    } catch {
      alert('Failed to load base image.');
    }
  });

  image2Input.addEventListener('change', async (e) => {
    if(e.target.files.length === 0) return;
    try {
      img2 = await loadImage(e.target.files[0]);
      resetData();
      enableMergeIfReady();
      resultInfo.textContent = '';
      clearCanvas();
    } catch {
      alert('Failed to load noise image.');
    }
  });

  noisePatternSelect.addEventListener('change', () => {
    if (noisePatternSelect.value !== "uploaded") {
      img2 = null; // ignore uploaded noise if using generated noise
      resetData();
      enableMergeIfReady();
      resultInfo.textContent = '';
      clearCanvas();
    } else {
      enableMergeIfReady();
    }
  });

  function resetData() {
    baseData = null;
    noiseData = null;
  }

  function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }

  function prepareBaseData() {
    canvas.width = img1.width;
    canvas.height = img1.height;
    ctx.drawImage(img1, 0, 0);
    baseData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  }

  function generateNoisePattern(type, width, height) {
    const noiseImgData = ctx.createImageData(width, height);
    const data = noiseImgData.data;

    if (type === "gaussian") {
      // Gaussian noise with mean=128, std dev=50
      for (let i = 0; i < data.length; i += 4) {
        const val = clamp(128 + gaussianRandom() * 50, 0, 255);
        data[i] = val;
        data[i + 1] = val;
        data[i + 2] = val;
        data[i + 3] = 255;
      }
    } else if (type === "saltpepper") {
      for (let i = 0; i < data.length; i += 4) {
        const rnd = Math.random();
        let val;
        if (rnd < 0.05) val = 0; // pepper
        else if (rnd > 0.95) val = 255; // salt
        else val = 128;
        data[i] = val;
        data[i + 1] = val;
        data[i + 2] = val;
        data[i + 3] = 255;
      }
    }
    return noiseImgData;
  }

  function boxBlur(imgData, radius) {
    if (radius < 1) return imgData;
    const width = imgData.width;
    const height = imgData.height;
    const src = imgData.data;
    const dst = new Uint8ClampedArray(src.length);

    const w4 = width * 4;
    const r = radius;
    for (let y = 0; y < height; y++) {
      let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
      let count = 0;
      for (let x = -r; x <= r; x++) {
        const xx = clamp(x, 0, width -1);
        const i = y * w4 + xx * 4;
        sumR += src[i];
        sumG += src[i+1];
        sumB += src[i+2];
        sumA += src[i+3];
        count++;
      }
      for (let x = 0; x < width; x++) {
        const i = y * w4 + x *4;
        dst[i] = sumR / count;
        dst[i+1] = sumG / count;
        dst[i+2] = sumB / count;
        dst[i+3] = sumA / count;

        const iRemove = y * w4 + clamp(x - r, 0, width -1) *4;
        const iAdd = y * w4 + clamp(x + r + 1, 0, width -1) *4;
        sumR += src[iAdd] - src[iRemove];
        sumG += src[iAdd + 1] - src[iRemove + 1];
        sumB += src[iAdd + 2] - src[iRemove + 2];
        sumA += src[iAdd + 3] - src[iRemove + 3];
      }
    }

    const tmp = new Uint8ClampedArray(dst.length);
    for (let x = 0; x < width; x++) {
      let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
      let count = 0;
      for (let y = -r; y <= r; y++) {
        const yy = clamp(y, 0, height -1);
        const i = yy * w4 + x * 4;
        sumR += dst[i];
        sumG += dst[i+1];
        sumB += dst[i+2];
        sumA += dst[i+3];
        count++;
      }
      for (let y = 0; y < height; y++) {
        const i = y * w4 + x *4;
        tmp[i] = sumR / count;
        tmp[i+1] = sumG / count;
        tmp[i+2] = sumB / count;
        tmp[i+3] = sumA / count;

        const iRemove = clamp(y - r, 0, height -1) * w4 + x *4;
        const iAdd = clamp(y + r + 1, 0, height -1) * w4 + x *4;
        sumR += dst[iAdd] - dst[iRemove];
        sumG += dst[iAdd + 1] - dst[iRemove + 1];
        sumB += dst[iAdd + 2] - dst[iRemove + 2];
        sumA += dst[iAdd + 3] - dst[iRemove + 3];
      }
    }
    return new ImageData(tmp, width, height);
  }

  function gaussianRandom() {
    let u = 0, v = 0;
    while(u === 0) u = Math.random();
    while(v === 0) v = Math.random();
    return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
  }

  function adjustContrastBrightness(imgData, contrast, brightness) {
    const data = imgData.data;
    for (let i = 0; i < data.length; i +=4) {
      for(let c=0; c<3; c++) {
        let val = data[i + c];
        val = ((val - 128) * contrast) + 128 + brightness;
        data[i + c] = clamp(val, 0, 255);
      }
    }
  }

  function invertImageData(imgData) {
    const data = imgData.data;
    for(let i=0; i < data.length; i +=4){
      data[i] = 255 - data[i];
      data[i+1] = 255 - data[i+1];
      data[i+2] = 255 - data[i+2];
    }
  }

  function applyGammaCorrection(imgData, gamma) {
    const invGamma = 1 / gamma;
    const data = imgData.data;
    for(let i=0; i < data.length; i +=4){
      for(let c=0; c < 3; c++){
        let normalized = data[i+c] / 255;
        normalized = Math.pow(normalized, invGamma);
        data[i+c] = clamp(normalized * 255, 0, 255);
      }
    }
  }

  function prepareNoiseData() {
    if (noisePatternSelect.value === "uploaded" && img2) {
      if(img2.width !== canvas.width || img2.height !== canvas.height){
        const tmpCanvas = document.createElement('canvas');
        tmpCanvas.width = canvas.width;
        tmpCanvas.height = canvas.height;
        const tmpCtx = tmpCanvas.getContext('2d');
        tmpCtx.drawImage(img2, 0, 0, img2.width, img2.height, 0, 0, canvas.width, canvas.height);
        noiseData = tmpCtx.getImageData(0,0, canvas.width, canvas.height);
      } else {
        ctx.drawImage(img2, 0, 0);
        noiseData = ctx.getImageData(0,0, canvas.width, canvas.height);
      }
    } else {
      noiseData = generateNoisePattern(noisePatternSelect.value, canvas.width, canvas.height);
    }

    adjustContrastBrightness(noiseData, parseFloat(contrastInput.value), parseInt(brightnessInput.value));

    if (invertNoiseInput.checked) invertImageData(noiseData);

    const blurRadius = parseInt(blurRadiusInput.value);
    if (blurRadius > 0) noiseData = boxBlur(noiseData, blurRadius);
  }

  function mergeImages() {
    if (!img1 || (!img2 && noisePatternSelect.value === "uploaded")) return;
    prepareBaseData();
    prepareNoiseData();

    const scale = parseFloat(perturbationScaleInput.value);
    const threshold = parseInt(thresholdInput.value);
    const applyR = channelR.checked;
    const applyG = channelG.checked;
    const applyB = channelB.checked;

    const basePixels = baseData.data;
    const noisePixels = noiseData.data;

    for (let i = 0; i < basePixels.length; i += 4) {
      let noiseR = applyR ? noisePixels[i] : 0;
      let noiseG = applyG ? noisePixels[i + 1] : 0;
      let noiseB = applyB ? noisePixels[i + 2] : 0;

      let perturbR = clamp(noiseR * scale, 0, threshold);
      let perturbG = clamp(noiseG * scale, 0, threshold);
      let perturbB = clamp(noiseB * scale, 0, threshold);

      basePixels[i] = clamp(basePixels[i] + perturbR, 0, 255);
      basePixels[i + 1] = clamp(basePixels[i + 1] + perturbG, 0, 255);
      basePixels[i + 2] = clamp(basePixels[i + 2] + perturbB, 0, 255);
    }

    applyGammaCorrection(baseData, parseFloat(gammaInput.value));

    ctx.putImageData(baseData, 0, 0);
    resultInfo.textContent = 'Image merged with current settings.';
  }

  function updateLabels() {
    scaleValueLabel.textContent = perturbationScaleInput.value;
    thresholdValueLabel.textContent = thresholdInput.value;
    contrastValueLabel.textContent = parseFloat(contrastInput.value).toFixed(2);
    brightnessValueLabel.textContent = brightnessInput.value;
    blurValueLabel.textContent = blurRadiusInput.value;
    gammaValueLabel.textContent = parseFloat(gammaInput.value).toFixed(2);
  }

  [
    perturbationScaleInput,
    channelR,
    channelG,
    channelB,
    invertNoiseInput,
    thresholdInput,
    contrastInput,
    brightnessInput,
    noisePatternSelect,
    blurRadiusInput,
    gammaInput,
  ].forEach(el => {
    el.addEventListener('input', () => {
      updateLabels();
      mergeImages();
    });
  });

  mergeBtn.addEventListener('click', mergeImages);

  updateLabels();
  mergeBtn.disabled = true;
</script>
</body>
</html>
/* Add your styles here */

// Add your code here