You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, numLevels = 3, ditherStrength = 1.0, addTextStr = "true", numSnippets = 5, snippetFontSize = 14, snippetFontColor = "30,30,30", snippetOpacity = 0.4, snippetFontFamily = "sans-serif") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Opt-in for frequent readbacks
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const bayerMatrix4x4 = [
[0, 8, 2, 10],
[12, 4, 14, 6],
[3, 11, 1, 9],
[15, 7, 13, 5]
];
const bayerSize = 4;
// Ensure numLevels is at least 2 for meaningful quantization.
// 1 level would just be a single color, not really a newspaper effect.
const effectiveNumLevels = Math.max(2, Math.floor(numLevels));
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const i = (y * canvas.width + x) * 4;
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Convert to grayscale using luminosity method
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
const normalizedGray = gray / 255.0; // Normalize to 0-1 range
// Get Bayer matrix value, normalized (0 to almost 1)
const bayerValue = bayerMatrix4x4[y % bayerSize][x % bayerSize];
const normalizedBayer = bayerValue / (bayerSize * bayerSize); // e.g., for 4x4, divide by 16
// Dithering logic (inspired by ordered dithering for multiple levels)
// The dither component shifts the gray value before quantization.
// (normalizedBayer - 0.5) creates a value roughly in [-0.5, 0.5]
const ditherComponent = ditherStrength * (normalizedBayer - 0.5);
// Scale normalizedGray to the range of level indices [0, effectiveNumLevels-1]
// Then add dither component.
// Example: M levels. Input I (0-1). Output is round((M-1)*I + dither_comp) / (M-1)
let valueToQuantize = (effectiveNumLevels - 1) * normalizedGray + ditherComponent;
let levelIndex = Math.round(valueToQuantize);
// Clamp levelIndex to be within [0, effectiveNumLevels-1]
levelIndex = Math.max(0, Math.min(effectiveNumLevels - 1, levelIndex));
let finalGrayNorm;
if (effectiveNumLevels <= 1) { // Should be prevented by Math.max(2,...)
finalGrayNorm = (normalizedGray > 0.5) ? 1.0 : 0.0;
} else {
finalGrayNorm = levelIndex / (effectiveNumLevels - 1.0);
}
// Ensure final value is clamped (should be redundant if logic is correct)
finalGrayNorm = Math.max(0.0, Math.min(1.0, finalGrayNorm));
const finalValue = Math.round(finalGrayNorm * 255);
data[i] = finalValue;
data[i + 1] = finalValue;
data[i + 2] = finalValue;
// Alpha (data[i+3]) remains unchanged
}
}
ctx.putImageData(imageData, 0, 0);
// Add text snippets if requested
const shouldAddText = String(addTextStr).toLowerCase() === 'true';
if (shouldAddText && numSnippets > 0) {
const snippetTexts = [
"NEWS TODAY", "HEADLINES", "EXTRA EXTRA", "READ ALL ABOUT IT",
"LOREM IPSUM", "DOLOR SIT AMET", "CONSECTETUR", "ADIPISCING ELIT",
"BREAKING NEWS", "TOP STORY", "EXCLUSIVE", "REPORT", "UPDATE",
"ANALYSIS", "OPINION", "LOCAL NEWS", "WORLD EVENTS"
];
const parsedColors = String(snippetFontColor).split(',').map(s => parseInt(s.trim(), 10));
const textR = Number.isFinite(parsedColors[0]) ? parsedColors[0] : 30;
const textG = Number.isFinite(parsedColors[1]) ? parsedColors[1] : 30;
const textB = Number.isFinite(parsedColors[2]) ? parsedColors[2] : 30;
ctx.fillStyle = `rgba(${textR}, ${textG}, ${textB}, ${Math.max(0, Math.min(1, snippetOpacity))})`;
ctx.font = `${Math.max(1, snippetFontSize)}px ${snippetFontFamily}`; // Ensure font size is positive
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (let i = 0; i < numSnippets; i++) {
const text = snippetTexts[Math.floor(Math.random() * snippetTexts.length)];
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
// Random angle, e.g., up to +/- 30 degrees (PI/6 radians)
const angle = (Math.random() - 0.5) * (Math.PI / 6);
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillText(text.toUpperCase(), 0, 0); // Using toUpperCase for a blocky, headline feel
ctx.restore();
}
}
return canvas;
}
Apply Changes