You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
polaroidPhotoSize = 300, // The width/height of the square photo area in pixels
sepiaStrength = 0.25, // 0 to 1 for sepia effect
desaturation = 0.15, // 0 to 1 (0 = original saturation, 1 = grayscale)
contrastValue = 0.95, // Multiplier, e.g., 1 is normal
brightnessValue = 1.05, // Multiplier, e.g., 1 is normal
blurRadius = 0.3, // In pixels, for subtle softness
borderColor = '#f3f3ea', // Color of the Polaroid border (off-white/light beige)
vignetteIntensity = 0.35,// 0 to 1, strength of the dark corners
grainAmount = 0.05, // 0 to 1, strength of the film grain
imageCornerRadiusRatio = 0.015 // Ratio of polaroidPhotoSize for rounded corners of the image
) {
// Basic validation for the input image
if (!originalImg || typeof originalImg.width === 'undefined' || originalImg.width === 0 || originalImg.height === 0) {
console.error("Original image is invalid or not loaded. Ensure it's an Image object with dimensions.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = Math.max(1, polaroidPhotoSize); // Ensure canvas has some size
errorCanvas.height = Math.max(1, polaroidPhotoSize);
const errCtx = errorCanvas.getContext('2d');
if (errCtx) {
errCtx.fillStyle = 'red';
errCtx.fillRect(0,0, errorCanvas.width, errorCanvas.height);
errCtx.fillStyle = 'white';
errCtx.font = '12px Arial';
errCtx.textAlign = 'center';
errCtx.fillText('Invalid Image', errorCanvas.width / 2, errorCanvas.height / 2);
}
return errorCanvas;
}
// Calculate border padding based on typical Polaroid proportions
const paddingSideRatio = 0.06;
const paddingTopRatio = 0.06;
const paddingBottomRatio = 0.30; // Larger bottom "chin" characteristic of Polaroids
const paddingSide = paddingSideRatio * polaroidPhotoSize;
const paddingTop = paddingTopRatio * polaroidPhotoSize;
const paddingBottom = paddingBottomRatio * polaroidPhotoSize;
const canvasWidth = polaroidPhotoSize + 2 * paddingSide;
const canvasHeight = polaroidPhotoSize + paddingTop + paddingBottom;
const finalCanvas = document.createElement('canvas');
finalCanvas.width = canvasWidth;
finalCanvas.height = canvasHeight;
const ctx = finalCanvas.getContext('2d');
// 1. Draw the Polaroid border
ctx.fillStyle = borderColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Create an intermediate canvas for image processing (the photo part)
const imgCanvas = document.createElement('canvas');
imgCanvas.width = polaroidPhotoSize;
imgCanvas.height = polaroidPhotoSize;
const imgCtx = imgCanvas.getContext('2d');
// Helper function for creating a rounded rectangle path
const roundedRectPath = (context, x, y, w, h, r) => {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
if (r < 0) r = 0;
context.beginPath();
context.moveTo(x + r, y);
context.arcTo(x + w, y, x + w, y + h, r);
context.arcTo(x + w, y + h, x, y + h, r);
context.arcTo(x, y + h, x, y, r);
context.arcTo(x, y, x + w, y, r);
context.closePath();
};
// 2. Apply rounded corners to the photo area if specified
const cornerRadius = imageCornerRadiusRatio * polaroidPhotoSize;
if (cornerRadius > 0) {
imgCtx.save();
roundedRectPath(imgCtx, 0, 0, polaroidPhotoSize, polaroidPhotoSize, cornerRadius);
imgCtx.clip();
}
// 3. Draw the original image, cropped and scaled to fit the square photo area
// This maintains aspect ratio by cropping either width or height.
const G_imgAspectRatio = originalImg.width / originalImg.height;
const P_photoAspectRatio = 1; // Target aspect ratio is square
let sx, sy, sWidth, sHeight;
if (G_imgAspectRatio > P_photoAspectRatio) { // Image is wider than target square
sHeight = originalImg.height;
sWidth = sHeight * P_photoAspectRatio; // sWidth will be originalImg.height
sx = (originalImg.width - sWidth) / 2;
sy = 0;
} else { // Image is taller or same aspect as target square
sWidth = originalImg.width;
sHeight = sWidth / P_photoAspectRatio; // sHeight will be originalImg.width
sy = (originalImg.height - sHeight) / 2;
sx = 0;
}
imgCtx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, polaroidPhotoSize, polaroidPhotoSize);
if (cornerRadius > 0) {
imgCtx.restore(); // Remove clipping path after drawing, so subsequent operations are not clipped
}
// 4. Apply filters (sepia, desaturation, contrast, brightness, blur)
// Use a temporary canvas to apply filters to avoid issues with `ctx.filter` on self-drawing.
const tempFilterCanvas = document.createElement('canvas');
tempFilterCanvas.width = polaroidPhotoSize;
tempFilterCanvas.height = polaroidPhotoSize;
const tempFilterCtx = tempFilterCanvas.getContext('2d');
let filterString = '';
if (desaturation > 0) filterString += `saturate(${1 - Math.min(1, Math.max(0, desaturation))}) `;
if (sepiaStrength > 0) filterString += `sepia(${Math.min(1, Math.max(0, sepiaStrength))}) `;
// Ensure contrast and brightness are non-negative
filterString += `contrast(${Math.max(0, contrastValue)}) `;
filterString += `brightness(${Math.max(0, brightnessValue)}) `;
if (blurRadius > 0) filterString += `blur(${Math.max(0, blurRadius)}px) `;
if (filterString.trim() !== '') {
tempFilterCtx.filter = filterString.trim();
}
tempFilterCtx.drawImage(imgCanvas, 0, 0); // Draw current imgCanvas (possibly clipped image) to tempFilterCanvas with filters
// Copy filtered image back to imgCanvas
imgCtx.clearRect(0, 0, polaroidPhotoSize, polaroidPhotoSize);
imgCtx.drawImage(tempFilterCanvas, 0, 0);
// 5. Apply film grain
if (grainAmount > 0 && grainAmount <= 1) {
const imageData = imgCtx.getImageData(0, 0, polaroidPhotoSize, polaroidPhotoSize);
const pixels = imageData.data;
// Adjust grain intensity: grainAmount=1 -> max noise +/-25.
const grainIntensity = grainAmount * 25;
for (let i = 0; i < pixels.length; i += 4) {
// Only apply grain to non-transparent pixels (respecting rounded corners)
if (pixels[i + 3] > 0) {
const noise = (Math.random() - 0.5) * grainIntensity; // Monochromatic noise
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));
}
}
imgCtx.putImageData(imageData, 0, 0);
}
// 6. Apply vignette
if (vignetteIntensity > 0 && vignetteIntensity <= 1) {
imgCtx.save();
const centerX = polaroidPhotoSize / 2;
const centerY = polaroidPhotoSize / 2;
// Outer radius slightly larger than half the diagonal to cover corners
const outerRadius = Math.sqrt(centerX*centerX + centerY*centerY) * 1.1; // polaroidPhotoSize * 0.707 * 1.1
// Inner radius defines transparent center. As intensity increases, transparent center shrinks.
const baseInnerRadiusPercent = 0.4; // Percentage of photoSize
const innerRadius = polaroidPhotoSize * baseInnerRadiusPercent * (1 - vignetteIntensity * 0.9);
const gradient = imgCtx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, Math.max(0,vignetteIntensity))})`);
imgCtx.fillStyle = gradient;
imgCtx.globalCompositeOperation = 'source-over';
imgCtx.fillRect(0, 0, polaroidPhotoSize, polaroidPhotoSize);
imgCtx.restore();
}
// 7. Draw the processed image (from imgCanvas) onto the final Polaroid canvas
const photoX = paddingSide;
const photoY = paddingTop;
ctx.drawImage(imgCanvas, photoX, photoY, polaroidPhotoSize, polaroidPhotoSize);
// 8. Optional: add a very subtle inner shadow/line to distinguish photo from border
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
ctx.lineWidth = 0.5; // Keep it very thin
// Offset by 0.5 for sharper lines if lineWidth is integer, but with 0.5 width not strictly needed.
ctx.strokeRect(photoX - ctx.lineWidth, photoY - ctx.lineWidth, polaroidPhotoSize + ctx.lineWidth*2, polaroidPhotoSize + ctx.lineWidth*2);
return finalCanvas;
}
Apply Changes