You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, contrast = 50, blueBoost = 30, greenBoost = 10, vignetteStrength = 0.7, vignetteStart = 0.3) {
// Ensure image is loaded and valid
if (!originalImg || typeof originalImg.naturalWidth === 'undefined' || !originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
console.error("Image not loaded or invalid. Cannot process.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200;
errorCanvas.height = 50;
const errorCtx = errorCanvas.getContext('2d');
if (errorCtx) {
errorCtx.fillStyle = '#fdd'; // Light red background
errorCtx.fillRect(0,0,errorCanvas.width,errorCanvas.height);
errorCtx.font = "bold 14px Arial";
errorCtx.fillStyle = "#D8000C"; // Dark red text
errorCtx.textAlign = "center";
errorCtx.textBaseline = "middle";
errorCtx.fillText("Error: Invalid Image", errorCanvas.width/2, errorCanvas.height/2);
}
return errorCanvas;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Check if context was successfully created
if (!ctx) {
console.error("Canvas 2D context is not supported.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200;
errorCanvas.height = 50;
const errorCtxFallback = errorCanvas.getContext('2d');
if (errorCtxFallback) {
errorCtxFallback.fillStyle = '#fdd';
errorCtxFallback.fillRect(0,0,errorCanvas.width,errorCanvas.height);
errorCtxFallback.font = "bold 14px Arial";
errorCtxFallback.fillStyle = "#D8000C";
errorCtxFallback.textAlign = "center";
errorCtxFallback.textBaseline = "middle";
errorCtxFallback.fillText("Error: Canvas Not Supported", errorCanvas.width/2, errorCanvas.height/2);
}
return errorCanvas;
}
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Get image data for pixel manipulation
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// This can happen due to tainted canvas (CORS issues with image source)
console.error("Could not getImageData (e.g., CORS issue): ", e.message);
// Return canvas with original image drawn, and add a warning message
ctx.font = "bold 16px Arial";
ctx.fillStyle = "rgba(216, 0, 12, 0.8)"; // Dark red, semi-transparent
ctx.textAlign = "center";
ctx.textBaseline = "top";
// Simple drop shadow for text readability
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillText("Original shown. Processing failed (CORS issue?).", canvas.width / 2 + 1, 20 + 1);
ctx.fillStyle = "rgba(255, 200, 200, 0.9)";
ctx.fillText("Original shown. Processing failed (CORS issue?).", canvas.width / 2, 20);
return canvas;
}
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
// --- Pixel manipulation: Grayscale, Tint, Contrast ---
// Clamp contrast parameter. The contrast formula is sensitive.
// C needs to be in range like (-259, 259). We use a slightly tighter range for C.
let C = Math.max(-255, Math.min(255, contrast));
if (C === 259 || C === -259) C = 258 * Math.sign(C); // Ensure no division by zero in extreme case
const contrastFactor = (259 * (C + 255)) / (255 * (259 - C));
for (let i = 0; i < data.length; i += 4) {
let rInitial = data[i];
let gInitial = data[i+1];
let bInitial = data[i+2];
// Convert to grayscale
let gray = 0.299 * rInitial + 0.587 * gInitial + 0.114 * bInitial;
// Apply Daguerreotype tint (typically cool, slightly blue/green)
// Tinting is done on the grayscale value.
let tintedR = gray;
let tintedG = gray + greenBoost;
let tintedB = gray + blueBoost;
// Clamp initial tinted values to [0, 255] before applying contrast
tintedR = Math.max(0, Math.min(255, tintedR));
tintedG = Math.max(0, Math.min(255, tintedG));
tintedB = Math.max(0, Math.min(255, tintedB));
// Apply contrast to the tinted grayscale values
let r = contrastFactor * (tintedR - 128) + 128;
let g = contrastFactor * (tintedG - 128) + 128;
let b = contrastFactor * (tintedB - 128) + 128;
// Clamp final RGB values to [0, 255]
data[i] = Math.max(0, Math.min(255, r));
data[i+1] = Math.max(0, Math.min(255, g));
data[i+2] = Math.max(0, Math.min(255, b));
// Alpha channel (data[i+3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
// --- Vignette Pass ---
const centerX = width / 2;
const centerY = height / 2;
// Calculate the furthest distance from center to a corner for outer radius
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// vignetteStart (0-1) determines the relative size of the transparent inner circle.
// Clamp vignetteStart to [0, 1] to prevent invalid gradient radiuses.
const clampedVignetteStart = Math.max(0, Math.min(1, vignetteStart));
const innerRadius = outerRadius * clampedVignetteStart;
// Clamp vignetteStrength (opacity) to [0, 1]
const clampedVignetteStrength = Math.max(0, Math.min(1, vignetteStrength));
// Create a radial gradient for the vignette effect
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Center of gradient (at innerRadius distance) is transparent
gradient.addColorStop(1, `rgba(0,0,0,${clampedVignetteStrength})`); // Edge of gradient (at outerRadius distance) is dark
// Apply the vignette gradient as an overlay
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
return canvas;
}
Apply Changes