You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
photoSize = 300,
borderColor = "#F0F0F0",
vintageLevel = 0.35, // 0 (none) to 1 (strong)
vignetteStrength = 0.4, // 0 (none) to 1 (strong)
shadowBlur = 20,
shadowOffsetX = 7,
shadowOffsetY = 7,
shadowColor = "rgba(0,0,0,0.25)"
) {
// Ensure numeric parameters are numbers and provide fallbacks if NaN
const numPhotoSize = Number(photoSize) || 300;
let numVintageLevel = Number(vintageLevel);
if (isNaN(numVintageLevel)) numVintageLevel = 0.35;
let numVignetteStrength = Number(vignetteStrength);
if (isNaN(numVignetteStrength)) numVignetteStrength = 0.4;
const numShadowBlur = Number(shadowBlur) || 0;
const numShadowOffsetX = Number(shadowOffsetX) || 0;
const numShadowOffsetY = Number(shadowOffsetY) || 0;
// Validate and clamp strength/level parameters
numVintageLevel = Math.max(0, Math.min(1, numVintageLevel));
numVignetteStrength = Math.max(0, Math.min(1, numVignetteStrength));
// 1. Calculate dimensions for the Polaroid frame and photo area
const imageToDrawSize = numPhotoSize; // Photo area is square
// Classic Polaroid-like border proportions (relative to photo size)
const sideBorder = imageToDrawSize * 0.07;
const topBorder = imageToDrawSize * 0.07;
const bottomBorder = imageToDrawSize * 0.30; // Larger bottom border
const frameWidth = imageToDrawSize + 2 * sideBorder;
const frameHeight = imageToDrawSize + topBorder + bottomBorder;
// Canvas size needs to accommodate the frame and its shadow
const absShadowBlur = Math.abs(numShadowBlur);
const canvasWidth = frameWidth + 2 * absShadowBlur + Math.abs(numShadowOffsetX);
const canvasHeight = frameHeight + 2 * absShadowBlur + Math.abs(numShadowOffsetY);
// 2. Create Canvas
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
// 3. Determine Source Image Cropping (center crop to square)
// Use naturalWidth/Height for intrinsic image dimensions
const imgNaturalWidth = originalImg.naturalWidth || originalImg.width;
const imgNaturalHeight = originalImg.naturalHeight || originalImg.height;
if (imgNaturalWidth === 0 || imgNaturalHeight === 0) {
// Handle invalid image dimensions gracefully (e.g., draw empty Polaroid)
console.error("Original image has zero width or height.");
// Optionally draw just the frame or return an empty canvas
}
let sx = 0, sy = 0, sWidth = imgNaturalWidth, sHeight = imgNaturalHeight;
if (imgNaturalWidth > 0 && imgNaturalHeight > 0) { // Proceed with cropping only if dimensions are valid
if (imgNaturalWidth / imgNaturalHeight > 1) { // Landscape image
sHeight = imgNaturalHeight;
sWidth = imgNaturalHeight; // Crop to square based on height
sx = (imgNaturalWidth - sWidth) / 2;
sy = 0;
} else { // Portrait or square image
sWidth = imgNaturalWidth;
sHeight = imgNaturalWidth; // Crop to square based on width
sx = 0;
sy = (imgNaturalHeight - sHeight) / 2;
}
}
// 4. Draw Polaroid Frame (with shadow)
// Calculate top-left position of the frame, allowing space for shadow
const frameRectX = absShadowBlur + (numShadowOffsetX < 0 ? Math.abs(numShadowOffsetX) : 0);
const frameRectY = absShadowBlur + (numShadowOffsetY < 0 ? Math.abs(numShadowOffsetY) : 0);
if (absShadowBlur > 0 || numShadowOffsetX !== 0 || numShadowOffsetY !== 0) {
ctx.shadowColor = shadowColor;
ctx.shadowBlur = absShadowBlur;
ctx.shadowOffsetX = numShadowOffsetX;
ctx.shadowOffsetY = numShadowOffsetY;
}
ctx.fillStyle = borderColor;
ctx.fillRect(frameRectX, frameRectY, frameWidth, frameHeight);
// Reset shadow for subsequent drawing operations (image, vignette)
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// 5. Draw Image with Vintage Filters
const imgDestX = frameRectX + sideBorder;
const imgDestY = frameRectY + topBorder;
if (numVintageLevel > 0) {
const sepiaVal = numVintageLevel * 0.6; // Max 60% sepia
const saturateVal = 1 - numVintageLevel * 0.5; // Reduce saturation up to 50%
const brightnessVal = 1 + numVintageLevel * 0.05; // Slight brightness increase
const contrastVal = 1 - numVintageLevel * 0.1; // Slight contrast decrease (soften)
ctx.filter = `sepia(${sepiaVal.toFixed(2)}) saturate(${saturateVal.toFixed(2)}) brightness(${brightnessVal.toFixed(2)}) contrast(${contrastVal.toFixed(2)})`;
}
if (sWidth > 0 && sHeight > 0) { // Only draw if source dimensions are valid
ctx.drawImage(
originalImg,
sx, sy, sWidth, sHeight, // Source rect (cropped square from original image)
imgDestX, imgDestY, imageToDrawSize, imageToDrawSize // Destination rect (photo area on canvas)
);
}
// Reset filter after drawing image, so vignette is not affected by image filters
ctx.filter = 'none';
// 6. Apply Vignette (Overlay on top of the image)
if (numVignetteStrength > 0 && imageToDrawSize > 0) {
const vignetteCenterX = imgDestX + imageToDrawSize / 2;
const vignetteCenterY = imgDestY + imageToDrawSize / 2;
const outerRadius = (imageToDrawSize / 2) * Math.sqrt(2); // Distance from center to corner of square photo area
// Inner radius calculation determines how far the vignette effect spreads:
// vignetteStrength = 0 -> innerRadiusRatio = 1 (innerRadius = outerRadius, no visible effect)
// vignetteStrength = 1 -> innerRadiusRatio = 0.3 (strong vignette, transparent center is 30% of outerRadius)
const innerRadiusRatio = 1 - numVignetteStrength * 0.7;
const innerRadius = outerRadius * innerRadiusRatio;
const gradient = ctx.createRadialGradient(
vignetteCenterX, vignetteCenterY, innerRadius,
vignetteCenterX, vignetteCenterY, outerRadius
);
// Vignette color is typically dark (black)
gradient.addColorStop(0, `rgba(0,0,0,0)`); // Center of vignette is transparent
// Opacity of vignette edges controlled by numVignetteStrength. Max 0.8 alpha for a less harsh effect.
gradient.addColorStop(1, `rgba(0,0,0,${(numVignetteStrength * 0.8).toFixed(2)})`);
ctx.fillStyle = gradient;
ctx.fillRect(imgDestX, imgDestY, imageToDrawSize, imageToDrawSize); // Apply gradient over the photo area
}
// 7. Return Canvas
return canvas;
}
Apply Changes