You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, panoramicRatio = 2.39, vignetteStrength = 0.6, grainAmount = 0.08, desaturation = 0.15, warmTone = 0.1, coolTone = 0.0, contrast = 1.05) {
if (!originalImg || typeof originalImg.width === 'undefined' || (originalImg.width === 0 && originalImg.naturalWidth === 0) || (originalImg.height === 0 && originalImg.naturalHeight === 0)) {
console.error("Original image is not valid or not loaded.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 300;
errorCanvas.height = 100;
const errCtx = errorCanvas.getContext('2d');
errCtx.fillStyle = '#DDDDDD';
errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errCtx.fillStyle = 'red';
errCtx.font = '16px Arial';
errCtx.textAlign = 'center';
errCtx.textBaseline = 'middle';
errCtx.fillText('Error: Invalid input image.', errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const originalWidth = originalImg.naturalWidth || originalImg.width;
const originalHeight = originalImg.naturalHeight || originalImg.height;
// Sanitize parameters
panoramicRatio = Math.max(0.5, Math.min(5, Number(panoramicRatio))); // e.g. 2.39:1 or 1:2 (0.5)
vignetteStrength = Math.max(0, Math.min(1, Number(vignetteStrength)));
grainAmount = Math.max(0, Math.min(1, Number(grainAmount)));
desaturation = Math.max(0, Math.min(1, Number(desaturation)));
warmTone = Math.max(0, Math.min(1, Number(warmTone)));
coolTone = Math.max(0, Math.min(1, Number(coolTone)));
contrast = Math.max(0.1, Math.min(3, Number(contrast))); // Contrast factor
// Determine panoramic dimensions for the output canvas
// The canvas will adopt the panoramic ratio.
// We'll use originalWidth as the basis for the canvas width.
const canvasWidth = originalWidth;
const canvasHeight = Math.round(originalWidth / panoramicRatio);
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Calculate source image region to crop/fit into the panoramic aspect ratio
let sx = 0, sy = 0, sWidth = originalWidth, sHeight = originalHeight;
const originalAspectRatio = originalWidth / originalHeight;
if (originalAspectRatio > panoramicRatio) {
// Original image is wider (more panoramic) than the target ratio; crop sides.
sWidth = originalHeight * panoramicRatio;
sx = (originalWidth - sWidth) / 2;
} else if (originalAspectRatio < panoramicRatio) {
// Original image is taller (less panoramic) than the target ratio; crop top/bottom.
sHeight = originalWidth / panoramicRatio;
sy = (originalHeight - sHeight) / 2;
}
// If aspect ratios match (or are very close), sx,sy remain 0, sWidth/sHeight remain original.
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const len = data.length;
for (let i = 0; i < len; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Alpha data[i+3] is preserved
// 1. Desaturation
if (desaturation > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = r * (1 - desaturation) + gray * desaturation;
g = g * (1 - desaturation) + gray * desaturation;
b = b * (1 - desaturation) + gray * desaturation;
}
// 2. Warm/Cool Tones
if (warmTone > 0) {
r *= (1 + 0.35 * warmTone); // Increase red
g *= (1 + 0.20 * warmTone); // Increase green less
// b *= (1 - 0.1 * warmTone); // Optionally decrease blue for more warmth
}
if (coolTone > 0) {
b *= (1 + 0.35 * coolTone); // Increase blue
g *= (1 + 0.15 * coolTone); // Increase green slightly for cyan tint
// r *= (1 - 0.1 * coolTone); // Optionally decrease red
}
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 3. Contrast
if (contrast !== 1.0) {
// (value/255 - 0.5) * contrast + 0.5 ensures midpoint (0.5) is unchanged
r = (r / 255 - 0.5) * contrast + 0.5;
g = (g / 255 - 0.5) * contrast + 0.5;
b = (b / 255 - 0.5) * contrast + 0.5;
r *= 255;
g *= 255;
b *= 255;
}
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 4. Film Grain
if (grainAmount > 0) {
const grainIntensity = 40; // Max pixel intensity change for grain
const grain = (Math.random() - 0.5) * grainIntensity * grainAmount;
r += grain;
g += grain;
b += grain;
}
// Final Clamp
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));
}
ctx.putImageData(imageData, 0, 0);
// 5. Vignette (applied on top of all pixel processing)
if (vignetteStrength > 0) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// innerRadius determines the size of the clear (unvignetted) area
// vignetteStrength = 1 => innerRadius is very small (strong vignette)
// vignetteStrength = 0 => innerRadius = outerRadius (no vignette, though this case is caught by if)
const innerRadiusFactor = 1 - Math.min(0.99, vignetteStrength * 1.2); // Tuned factor
const innerRadius = outerRadius * innerRadiusFactor;
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
// Adjust mid-point based on strength for a softer or harder falloff
const midPointColorOpacity = vignetteStrength * 0.4;
const midPointStop = Math.max(0.5, 0.3 + innerRadiusFactor * 0.6); // Ensure mid is after inner
gradient.addColorStop(midPointStop, `rgba(0,0,0,${midPointColorOpacity})`);
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(0.9, vignetteStrength * 1.1)})`); // Max opacity of vignette
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
return canvas;
}
Apply Changes