You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, overallStrength = 0.6, highlightCoolness = 0.15, vignetteAmount = 0.3) {
// Validate originalImg: must be a loaded HTMLImageElement with positive dimensions
if (!(originalImg instanceof HTMLImageElement) || !originalImg.complete ||
originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
console.error("processImage: Original image is not a valid, fully loaded HTMLImageElement with dimensions.");
// Create a small canvas with an error message
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 250;
errorCanvas.height = 60;
const errCtx = errorCanvas.getContext('2d');
if (errCtx) {
errCtx.fillStyle = 'rgb(250, 230, 230)'; // Light error background
errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errCtx.font = 'bold 12px Arial';
errCtx.fillStyle = 'red';
errCtx.textAlign = 'center';
errCtx.fillText('Error: Invalid Image Input.', errorCanvas.width / 2, errorCanvas.height / 2 - 5);
errCtx.font = '10px Arial';
errCtx.fillText('Ensure image is loaded and valid.', errorCanvas.width / 2, errorCanvas.height / 2 + 10);
}
return errorCanvas;
}
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
// Sanitize and clamp parameters (they can be string or number, Number() handles casting)
// If parsing results in NaN (e.g., from an invalid string), use the default value from signature.
// Then clamp to the expected 0-1 range.
let s_overallStrengthNum = Number(overallStrength);
s_overallStrengthNum = isNaN(s_overallStrengthNum) ? 0.6 : s_overallStrengthNum;
const finalOverallStrength = Math.max(0, Math.min(1, s_overallStrengthNum));
let s_highlightCoolnessNum = Number(highlightCoolness);
s_highlightCoolnessNum = isNaN(s_highlightCoolnessNum) ? 0.15 : s_highlightCoolnessNum;
const finalHighlightCoolness = Math.max(0, Math.min(1, s_highlightCoolnessNum));
let s_vignetteAmountNum = Number(vignetteAmount);
s_vignetteAmountNum = isNaN(s_vignetteAmountNum) ? 0.3 : s_vignetteAmountNum;
const finalVignetteAmount = Math.max(0, Math.min(1, s_vignetteAmountNum));
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("processImage: Could not get 2D context from canvas.");
// Return the canvas (it will be blank, but correctly sized)
return canvas;
}
// 1. Apply base contrast and saturation filters
// These values are chosen to emulate the clarity and richness of T* coating.
const contrastValue = 1 + 0.5 * finalOverallStrength; // Max 1.5x contrast
const saturationValue = 1 + 0.4 * finalOverallStrength; // Max 1.4x saturation
ctx.filter = `contrast(${contrastValue}) saturate(${saturationValue})`;
ctx.drawImage(originalImg, 0, 0, w, h);
ctx.filter = 'none'; // Reset filter for subsequent operations
// 2. Apply cool highlight effect (characteristic of some lens coatings/flare)
if (finalHighlightCoolness > 0) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
// Pixels brighter than this normalized luminance will be affected
const LUMINANCE_THRESHOLD_NORM = 190 / 255; // Approx 0.745 (bright midtones to highlights)
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
// Calculate pixel luminance (normalized 0-1)
const lum_norm = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
if (lum_norm > LUMINANCE_THRESHOLD_NORM) {
// gradFactor ranges from 0 (at threshold) to 1 (at max luminance)
let gradFactor = (lum_norm - LUMINANCE_THRESHOLD_NORM) / (1.0 - LUMINANCE_THRESHOLD_NORM);
// Power curve to make the effect stronger in the brightest parts of highlights
gradFactor = Math.pow(gradFactor, 1.5);
const boost = finalHighlightCoolness * gradFactor;
// Apply a cool (cyan/blue) tint by boosting blue and slightly reducing red/green
data[i+2] = Math.min(255, b * (1 + boost * 0.6)); // Boost Blue channel
data[i] = Math.max(0, r * (1 - boost * 0.3)); // Reduce Red channel
data[i+1] = Math.max(0, g * (1 - boost * 0.15)); // Reduce Green channel less
}
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Apply vignette effect
if (finalVignetteAmount > 0) {
const cx = w / 2;
const cy = h / 2;
// Vignette outer radius should cover the corners of the image
const outerRadius = Math.sqrt(cx * cx + cy * cy);
// Vignette starts to fade in from this ratio of the outerRadius
// e.g., 0.3 means the central 30% (by radius) is unaffected, then gradient begins.
const innerRadiusRatio = 0.3;
const gradient = ctx.createRadialGradient(cx, cy, outerRadius * innerRadiusRatio, cx, cy, outerRadius);
// Vignette color for multiply: 0 = black (full vignette), 255 = white (no vignette)
const vignetteColorVal = Math.round(255 * (1 - finalVignetteAmount));
// Color stop at the inner radius: white (no darkening)
gradient.addColorStop(0, 'white');
// Color stop at the outer radius: calculated vignette color
gradient.addColorStop(1, `rgb(${vignetteColorVal}, ${vignetteColorVal}, ${vignetteColorVal})`);
ctx.globalCompositeOperation = 'multiply'; // Darkens the image where gradient is not white
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation to default
}
return canvas;
}
Apply Changes