You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, aspectRatio = "2.39:1", saturation = 0.7, contrast = 1.3, tintColor = "rgba(255, 240, 210, 0.08)", grainAmount = 0.2, vignetteStrength = 0.4, vignetteSoftness = 0.6) {
// 1. Parse aspectRatio string (e.g., "2.39:1") into a numerical value
let targetAspectRatioValue;
const arParts = String(aspectRatio).split(':');
if (arParts.length === 2) {
const arW = parseFloat(arParts[0]);
const arH = parseFloat(arParts[1]);
if (!isNaN(arW) && !isNaN(arH) && arW > 0 && arH > 0) {
targetAspectRatioValue = arW / arH;
}
}
if (!targetAspectRatioValue) { // Fallback or if parsing failed
console.warn(`Invalid aspect ratio: "${aspectRatio}". Using default 2.39/1.`);
targetAspectRatioValue = 2.39 / 1;
}
// 2. Create canvas and set its dimensions based on the target aspect ratio
const canvas = document.createElement('canvas');
const baseWidth = originalImg.naturalWidth || originalImg.width;
canvas.width = Math.round(baseWidth);
canvas.height = Math.round(baseWidth / targetAspectRatioValue);
// Prevent extremely small or large canvases if calculated height is problematic
if (canvas.height <= 0 || !isFinite(canvas.height) || isNaN(canvas.height)) {
console.warn("Calculated canvas height is invalid. Defaulting to a sensible height relative to width.");
canvas.height = Math.round(canvas.width / (16/9)); // Default to 16:9 if aspect ratio somehow leads to 0 or Inf height
if (canvas.height <=0) canvas.height = Math.round(canvas.width * 0.75); // further fallback
}
if (canvas.width <=0) { // If original image width was 0 or invalid
console.warn("Canvas width is invalid. Setting to a default value.");
canvas.width = 640; // Default width
canvas.height = Math.round(canvas.width / targetAspectRatioValue); // Recalculate height
if (canvas.height <= 0 || !isFinite(canvas.height) || isNaN(canvas.height)) {
canvas.height = 360; // Absolute fallback height
}
}
const ctx = canvas.getContext('2d');
// 3. Fill canvas with black. This provides the background for letterboxing/pillarboxing.
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 4. Calculate drawing parameters for originalImg to fit and center within the canvas
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth <= 0 || imgHeight <= 0 || !isFinite(imgWidth) || !isFinite(imgHeight)) {
console.error("Original image has invalid dimensions or is not loaded.");
// Draw a placeholder error message on the canvas
ctx.fillStyle = "darkred";
ctx.fillRect(0,0, canvas.width, canvas.height);
ctx.font = "16px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("ErrorProcessing: Invalid Image Data", canvas.width / 2, canvas.height / 2);
return canvas;
}
const imgAr = imgWidth / imgHeight;
let drawWidth, drawHeight, dx, dy;
if (imgAr > targetAspectRatioValue) { // Image is wider or less tall than canvas target AR (needs pillarboxing)
drawWidth = canvas.width;
drawHeight = Math.round(drawWidth / imgAr);
dx = 0;
dy = Math.round((canvas.height - drawHeight) / 2);
} else { // Image is taller or less wide than canvas target AR (needs letterboxing)
drawHeight = canvas.height;
drawWidth = Math.round(drawHeight * imgAr);
dx = Math.round((canvas.width - drawWidth) / 2);
dy = 0;
}
// 5. Apply image filters (saturation, contrast) and draw the image
const currentSaturation = Math.max(0, Number(saturation));
const currentContrast = Math.max(0, Number(contrast));
ctx.filter = `saturate(${currentSaturation}) contrast(${currentContrast})`;
try {
ctx.drawImage(originalImg, dx, dy, drawWidth, drawHeight);
} catch (e) {
console.error("Error drawing image:", e);
// Fallback: clear filter and draw error message if drawImage failed
ctx.filter = 'none';
ctx.fillStyle = "darkred";
ctx.fillRect(0,0, canvas.width, canvas.height); // Clear previous attempts
ctx.font = "16px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("Error: Could not draw image", canvas.width / 2, canvas.height / 2);
return canvas;
}
ctx.filter = 'none'; // Reset filter for subsequent drawing operations
// 6. Apply tint overlay
if (tintColor && typeof tintColor === 'string' && tintColor.toLowerCase() !== "none" && tintColor.trim() !== "") {
// Basic validation for common color formats. Browsers are usually robust.
// This regex aims for common patterns like rgba(), rgb(), #hex, and color names
if (/^((rgba?)\([^)]+\)|#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|[a-zA-Z]+)$/i.test(tintColor.trim())) {
ctx.fillStyle = tintColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
console.warn(`Invalid tintColor format: "${tintColor}". Skipping tint.`);
}
}
// 7. Apply film grain using pixel manipulation
const currentGrainAmount = Math.max(0, Math.min(1, Number(grainAmount)));
if (currentGrainAmount > 0 && canvas.width > 0 && canvas.height > 0) {
const actualGrainIntensity = currentGrainAmount * 30; // Scale: 0-1 maps to noise intensity 0-30
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
const len = pixels.length;
for (let i = 0; i < len; i += 4) {
const noise = (Math.random() - 0.5) * actualGrainIntensity;
pixels[i] = Math.max(0, Math.min(255, pixels[i] + noise));
pixels[i + 1] = Math.max(0, Math.min(255, pixels[i + 1] + noise));
pixels[i + 2] = Math.max(0, Math.min(255, pixels[i + 2] + noise));
}
ctx.putImageData(imageData, 0, 0);
}
// 8. Apply vignette effect
const currentVignetteStrength = Math.max(0, Math.min(1, Number(vignetteStrength)));
const currentVignetteSoftness = Math.max(0, Math.min(1, Number(vignetteSoftness)));
if (currentVignetteStrength > 0 && canvas.width > 0 && canvas.height > 0) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
const r0Ratio = 0.9 - currentVignetteSoftness * 0.8; // Maps [0,1] softness to [0.9, 0.1] inner radius ratio
const innerRadius = outerRadius * r0Ratio;
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${currentVignetteStrength})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
return canvas;
}
Apply Changes