You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, ditheringType = "bayer4x4", noiseAmount = 0.03, scanLineOpacity = 0.08, scanRowInterval = 3, preBlurRadius = 0.3) {
// Bayer matrices (normalized for direct comparison with intensity 0-1)
// These are defined inside the function to keep it self-contained.
const BAYER_MATRIX_2X2_NORMALIZED = [
[0/4, 2/4],
[3/4, 1/4]
];
const BAYER_MATRIX_4X4_NORMALIZED = [
[ 0/16, 8/16, 2/16, 10/16],
[12/16, 4/16, 14/16, 6/16],
[ 3/16, 11/16, 1/16, 9/16],
[15/16, 7/16, 13/16, 5/16]
];
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization hint for frequent getImageData/putImageData
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (!width || !height) {
console.error("Image has zero dimensions. Cannot process.");
// Return an empty or minimal canvas to avoid breaking caller
canvas.width = 1;
canvas.height = 1;
ctx.clearRect(0, 0, 1, 1); // Ensure it's blank
return canvas;
}
canvas.width = width;
canvas.height = height;
// 1. Optional Pre-blur to simulate slight focus imperfection or ink bleed
if (preBlurRadius > 0) {
// Apply blur to the original image drawing
ctx.filter = `blur(${preBlurRadius}px)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none'; // Reset filter before getImageData
} else {
// Draw image without blur
ctx.drawImage(originalImg, 0, 0, width, height);
}
// 2. Get image data for pixel manipulation
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Could not get image data. Tainted canvas?", e);
// If canvas is tainted (e.g., cross-origin image without CORS), return the drawn (possibly blurred) image.
return canvas;
}
const data = imageData.data;
// 3. Process pixels: Grayscale, Dithering, Noise
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4; // Index for the red component of the pixel
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Convert to grayscale (luminosity method for perceptually accurate brightness)
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
const normalizedGray = gray / 255.0; // Normalize to 0-1 range
let finalColorVal = 0; // 0 for black, 255 for white
// Apply dithering
if (ditheringType === "none") {
// Simple thresholding
finalColorVal = gray < 128 ? 0 : 255;
} else if (ditheringType === "bayer2x2") {
const matX = x % 2;
const matY = y % 2;
const threshold = BAYER_MATRIX_2X2_NORMALIZED[matY][matX];
finalColorVal = normalizedGray > threshold ? 255 : 0;
} else { // Default to "bayer4x4" or any other unrecognized type
const matX = x % 4;
const matY = y % 4;
const threshold = BAYER_MATRIX_4X4_NORMALIZED[matY][matX];
finalColorVal = normalizedGray > threshold ? 255 : 0;
}
// Apply noise (salt-and-pepper style on the binarized image)
if (noiseAmount > 0 && Math.random() < noiseAmount) {
finalColorVal = Math.random() < 0.5 ? 0 : 255;
}
data[i] = finalColorVal; // Red
data[i + 1] = finalColorVal; // Green
data[i + 2] = finalColorVal; // Blue
// Alpha (data[i + 3]) remains unchanged
}
}
// 4. Put the modified pixel data back onto the canvas
ctx.putImageData(imageData, 0, 0);
// 5. Draw Scan Lines on top of the processed image
if (scanLineOpacity > 0 && scanRowInterval > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanLineOpacity})`;
for (let y = 0; y < height; y += Math.max(1, Math.floor(scanRowInterval))) { // Ensure interval is at least 1
ctx.fillRect(0, y, width, 1); // Draw a 1px high scan line
}
}
return canvas;
}
Apply Changes