You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
threshold1 = 60,
threshold2 = 120,
threshold3 = 180,
color1Str = "30,0,60", // Dark Violet/Indigo for shadows
color2Str = "0,200,255", // Cyan for mid-darks
color3Str = "255,0,200", // Magenta for mid-lights
color4Str = "255,255,100", // Light Yellow for highlights
numLines = 30,
lineColorStr = "255,255,255,0.5", // Semi-transparent White for lines
lineThickness = 2
) {
// 1. Setup Canvas
const canvas = document.createElement('canvas');
// Add willReadFrequently for potential performance optimization with getImageData/putImageData
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image has zero dimensions. Cannot process.");
// Return a tiny placeholder canvas
canvas.width = 1;
canvas.height = 1;
ctx.fillRect(0,0,1,1); // Make it black
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Helper function to parse color strings like "r,g,b" or "r,g,b,a"
// Returns {r, g, b, a} object with values 0-255 for RGB, 0-1 for A.
function parseColor(colorStr, defaultAlphaForRGB = 1.0) {
const parts = colorStr.split(',').map(s => parseFloat(s.trim()));
let r = 0, g = 0, b = 0, a = defaultAlphaForRGB;
if (parts.length >= 3) {
if (!isNaN(parts[0])) r = parts[0];
if (!isNaN(parts[1])) g = parts[1];
if (!isNaN(parts[2])) b = parts[2];
if (parts.length === 4 && !isNaN(parts[3])) {
a = parts[3]; // Use provided alpha if 4 parts
} else if (parts.length === 3) {
a = defaultAlphaForRGB; // Use defaultAlpha if only RGB provided
}
} else {
console.warn(`Invalid color string: "${colorStr}". Using default (black or transparent black).`);
// r,g,b are already 0. Alpha is defaultAlphaForRGB.
}
// Clamp values to valid ranges
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
a = Math.max(0, Math.min(1, a)); // Alpha clamped to 0-1
return { r, g, b, a };
}
// Parse posterization colors. These are intended to be fully opaque.
const color1 = parseColor(color1Str, 1.0);
const color2 = parseColor(color2Str, 1.0);
const color3 = parseColor(color3Str, 1.0);
const color4 = parseColor(color4Str, 1.0);
// 2. Pixel Manipulation (Color Transformation for Futurist Posterization)
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Could not getImageData (e.g., cross-origin issue if image source is tainted):", e);
// Return the canvas with the original image drawn if processing fails here
return canvas;
}
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r_orig = data[i];
const g_orig = data[i + 1];
const b_orig = data[i + 2];
const a_orig = data[i + 3];
// Skip fully transparent pixels to preserve them
if (a_orig === 0) {
continue;
}
// Calculate luminance (standard formula for perceived brightness)
const luminance = 0.299 * r_orig + 0.587 * g_orig + 0.114 * b_orig;
let targetColor;
if (luminance < threshold1) {
targetColor = color1;
} else if (luminance < threshold2) {
targetColor = color2;
} else if (luminance < threshold3) {
targetColor = color3;
} else {
targetColor = color4;
}
data[i] = targetColor.r;
data[i + 1] = targetColor.g;
data[i + 2] = targetColor.b;
// Set alpha for the posterized color. Since targetColor.a is 1.0 for color1-4,
// this makes non-fully-transparent pixels opaque with the new color.
// If original pixel had some transparency, it becomes opaque.
data[i + 3] = targetColor.a * 255;
}
ctx.putImageData(imageData, 0, 0);
// 3. Dynamic Lines Overlay
if (numLines > 0 && lineThickness > 0) {
// Parse line color (can include its own alpha)
const parsedLineColor = parseColor(lineColorStr);
ctx.strokeStyle = `rgba(${parsedLineColor.r}, ${parsedLineColor.g}, ${parsedLineColor.b}, ${parsedLineColor.a})`;
ctx.lineWidth = Math.max(0.1, lineThickness); // Ensure lineWidth is positive
ctx.beginPath(); //Optimize: begin path once for all lines with same style
for (let i = 0; i < numLines; i++) {
const x1 = Math.random() * canvas.width;
const y1 = Math.random() * canvas.height;
const angle = Math.random() * Math.PI * 2; // Random direction
// Calculate a dynamic length for lines, relative to canvas size
const canvasDiagonal = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height);
// Make lines reasonably long, e.g., 10% to 60% of canvas diagonal
const length = (Math.random() * 0.5 + 0.1) * canvasDiagonal;
const x2 = x1 + Math.cos(angle) * length;
const y2 = y1 + Math.sin(angle) * length;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
}
ctx.stroke(); // Draw all lines accumulated in the path
}
return canvas;
}
Apply Changes