You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg, // JavaScript Image object, assumed to be loaded
blurAmount = 0.5, // Blur radius in pixels. 0 to disable.
noiseIntensity = 0.15, // Noise intensity (0-1). 0 to disable.
tintColor = "rgba(0, 40, 50, 0.15)", // CSS color string for tint. Empty string or "none" to disable.
scanLineOpacity = 0.07, // Opacity of scan lines (0-1). 0 to disable.
scanLineThickness = 1, // Thickness of each scan line in pixels.
scanLineSpacing = 3, // Space between scan lines in pixels.
vignetteStrength = 0.6 // Strength of vignette (0-1). 0 for no vignette, 1 for fully opaque black edges.
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: true }); // Ensure canvas supports alpha
// Use naturalWidth/Height for intrinsic dimensions, fallback to width/height if natural are 0
let imgWidth = originalImg.naturalWidth || originalImg.width;
let imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Image has zero width or height. Using 1x1 canvas.");
canvas.width = 1; // Avoid errors with zero-dimension canvas
canvas.height = 1;
// Optionally draw a placeholder or just return the tiny canvas
ctx.fillStyle = 'grey';
ctx.fillRect(0,0,1,1);
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
// 1. Apply optional blur when drawing the original image
if (typeof blurAmount === 'number' && blurAmount > 0) {
ctx.filter = `blur(${blurAmount}px)`;
}
try {
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error drawing image:", e);
// Clear canvas and return if drawing fails
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.filter = 'none'; // Reset filter in case it was set
return canvas;
}
if (typeof blurAmount === 'number' && blurAmount > 0) {
ctx.filter = 'none'; // Reset filter
}
// 2. Grayscale and Noise (pixel manipulation)
// This operation can fail if the canvas is tainted (e.g., CORS image without proper headers)
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error getting image data (possibly CORS issue):", e);
// If we can't get image data, we can't do grayscale/noise.
// We could return the blurred image, or try to proceed with other effects
// For now, let's return the canvas as is (potentially blurred image)
return canvas;
}
const data = imageData.data;
const noiseFa = (typeof noiseIntensity === 'number' && noiseIntensity > 0) ? noiseIntensity * 255 : 0;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Luminance-preserving grayscale
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Add noise
if (noiseFa > 0) {
const randomNoise = (Math.random() - 0.5) * noiseFa;
gray += randomNoise;
}
// Clamp
gray = Math.max(0, Math.min(255, gray));
data[i] = gray;
data[i + 1] = gray;
data[i + 2] = gray;
// Alpha (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
// 3. Apply Tint Layer
if (tintColor && typeof tintColor === 'string' && tintColor.trim() !== "" && tintColor.toLowerCase().trim() !== "none") {
ctx.fillStyle = tintColor;
ctx.globalCompositeOperation = 'source-over'; // Standard alpha blending for the tint layer
ctx.fillRect(0, 0, imgWidth, imgHeight);
}
// Reset to default composite operation (important if other effects use different modes)
ctx.globalCompositeOperation = 'source-over';
// 4. Draw Scan Lines
if (typeof scanLineOpacity === 'number' && scanLineOpacity > 0 &&
typeof scanLineThickness === 'number' && scanLineThickness > 0 &&
typeof scanLineSpacing === 'number' && (scanLineThickness + scanLineSpacing) > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${Math.min(1, Math.max(0, scanLineOpacity))})`; // Clamp opacity
for (let y = 0; y < imgHeight; y += (scanLineThickness + scanLineSpacing)) {
ctx.fillRect(0, y, imgWidth, scanLineThickness);
}
}
// 5. Draw Vignette
if (typeof vignetteStrength === 'number' && vignetteStrength > 0) {
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY); // Radius to canvas corner
// Inner radius for the gradient. Adjust this to control the size of the clear center.
// e.g., 30% of the smaller dimension's half.
const innerGradientRadius = Math.min(centerX, centerY) * 0.3;
const gradient = ctx.createRadialGradient(
centerX, centerY, innerGradientRadius, // Inner circle (center, radius)
centerX, centerY, outerRadius // Outer circle (center, radius)
);
gradient.addColorStop(0, "rgba(0,0,0,0)"); // Center transparent
// VignetteStrength directly controls opacity at edges, clamped to [0,1]
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, Math.max(0, vignetteStrength))})`);
ctx.fillStyle = gradient;
ctx.globalCompositeOperation = 'source-over'; // Ensure it overlays correctly
ctx.fillRect(0, 0, imgWidth, imgHeight);
}
// Ensure globalCompositeOperation is reset to default if it was changed by any step.
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes