You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, mode = "document_scan", val1 = "15", val2 = "5") {
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0);
mode = mode.toString().toLowerCase();
if (mode === "ela" || mode === "examine") {
// Mode 1: Error Level Analysis (Image Examine Tool / Forensics)
// Helps to detect manipulation in images like cheated exam proof/documents
return new Promise((resolve, reject) => {
const quality = (parseFloat(val2) || 90) / 100;
const multiplier = parseFloat(val1) || 15;
const jpegDataUrl = canvas.toDataURL('image/jpeg', quality);
const tempImg = new Image();
tempImg.onload = () => {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.drawImage(tempImg, 0, 0);
const origData = ctx.getImageData(0, 0, width, height);
const tempData = tempCtx.getImageData(0, 0, width, height);
const outData = ctx.createImageData(width, height);
for (let i = 0; i < origData.data.length; i += 4) {
outData.data[i] = Math.abs(origData.data[i] - tempData.data[i]) * multiplier; // R
outData.data[i+1] = Math.abs(origData.data[i+1] - tempData.data[i+1]) * multiplier; // G
outData.data[i+2] = Math.abs(origData.data[i+2] - tempData.data[i+2]) * multiplier; // B
outData.data[i+3] = 255; // A
}
ctx.putImageData(outData, 0, 0);
resolve(canvas);
};
tempImg.onerror = reject;
tempImg.src = jpegDataUrl;
});
}
else if (mode === "watermark") {
// Mode 2: Literally stamp "ЭКЗАМЕН" (Exam) across the image
const text = (val1 === "15" || !val1) ? "ЭКЗАМЕН" : val1.toString();
const opacity = parseFloat(val2) || 0.6;
ctx.fillStyle = `rgba(200, 30, 30, ${opacity})`;
ctx.font = `bold ${Math.max(width, height) / 8}px 'Courier New', Courier, monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 6);
const textMetrics = ctx.measureText(text);
const padX = textMetrics.width * 0.15;
const ascent = textMetrics.actualBoundingBoxAscent || (parseInt(ctx.font) * 0.7);
const descent = textMetrics.actualBoundingBoxDescent || (parseInt(ctx.font) * 0.2);
const padY = ascent * 0.4;
ctx.lineWidth = Math.max(width, height) / 80;
ctx.strokeStyle = `rgba(200, 30, 30, ${opacity})`;
ctx.strokeRect(
-(textMetrics.width / 2) - padX,
-ascent - padY,
textMetrics.width + (padX * 2),
(ascent + descent) + (padY * 2)
);
ctx.fillText(text, 0, 0);
return canvas;
}
else {
// Mode 3: Document Scanner / Outline tool (Default Mode)
// Extremely useful for analyzing/printing text from exam note photos (Adaptive Binarization)
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Generate Integral Image for fast local thresholding computation (Bradley-Roth algorithm)
const integral = new Int32Array(width * height);
for (let y = 0; y < height; y++) {
let sum = 0;
for (let x = 0; x < width; x++) {
let idx = y * width + x;
let i4 = idx * 4;
// Grayscale equivalent
let gray = data[i4] * 0.299 + data[i4 + 1] * 0.587 + data[i4 + 2] * 0.114;
sum += gray;
integral[idx] = (y === 0) ? sum : integral[idx - width] + sum;
}
}
const t = parseFloat(val1) || 15; // Threshold control
const windowPercent = parseFloat(val2) || 5;
const s = Math.max(2, Math.floor(width * (windowPercent / 100)));
const sHalf = Math.floor(s / 2);
const outData = ctx.createImageData(width, height);
const outBase = outData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let idx = y * width + x;
let i4 = idx * 4;
let x1 = Math.max(x - sHalf, 0);
let y1 = Math.max(y - sHalf, 0);
let x2 = Math.min(x + sHalf, width - 1);
let y2 = Math.min(y + sHalf, height - 1);
let count = (x2 - x1 + 1) * (y2 - y1 + 1);
let valX1_Y2 = (x1 > 0) ? integral[y2 * width + (x1 - 1)] : 0;
let valX2_Y1 = (y1 > 0) ? integral[(y1 - 1) * width + x2] : 0;
let valX1_Y1 = (x1 > 0 && y1 > 0) ? integral[(y1 - 1) * width + (x1 - 1)] : 0;
let windowSum = integral[y2 * width + x2] - valX1_Y2 - valX2_Y1 + valX1_Y1;
let gray = data[i4] * 0.299 + data[i4 + 1] * 0.587 + data[i4 + 2] * 0.114;
if (gray * count < windowSum * (1.0 - (t / 100.0))) {
outBase[i4] = 0; // Black
outBase[i4+1] = 0;
outBase[i4+2] = 0;
outBase[i4+3] = 255;
} else {
outBase[i4] = 255; // White
outBase[i4+1] = 255;
outBase[i4+2] = 255;
outBase[i4+3] = 255;
}
}
}
ctx.putImageData(outData, 0, 0);
return canvas;
}
}
Apply Changes