<!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