You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numPhotos = "15", zoomLevel = "1.15") {
// Parse constraint parameters
let count = parseInt(numPhotos, 10);
if (isNaN(count) || count < 1) count = 15;
if (count > 50) count = 50; // Cap to avoid performance issues
let zoom = parseFloat(zoomLevel);
if (isNaN(zoom) || zoom < 1) zoom = 1.15;
if (zoom > 3) zoom = 3;
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
// Create the main output canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Optimization check: Use an offscreen canvas to pre-apply heavy filters
// This gives the polaroid inner images their "Photomania" pop!
const vibrantCanvas = document.createElement('canvas');
vibrantCanvas.width = width;
vibrantCanvas.height = height;
const vCtx = vibrantCanvas.getContext('2d');
// Fill background for transparent PNGs
vCtx.fillStyle = '#111111';
vCtx.fillRect(0, 0, width, height);
vCtx.filter = 'saturate(1.5) contrast(1.1) brightness(1.05)';
vCtx.drawImage(originalImg, 0, 0, width, height);
vCtx.filter = 'none';
// Build main background
ctx.fillStyle = '#111111';
ctx.fillRect(0, 0, width, height);
// Paint a blurred & darkened background of the original image
ctx.filter = `blur(${Math.max(width, height) * 0.015}px) brightness(0.6) sepia(0.3)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// Apply a radial vignette focus to center
const gradient = ctx.createRadialGradient(width/2, height/2, Math.min(width, height)*0.3, width/2, height/2, Math.max(width, height)*0.8);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.8)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Calculate grid logic to ensure the "polaroids" cover the image properly
const aspect = width / height;
const cols = Math.max(1, Math.round(Math.sqrt(count * aspect)));
const rows = Math.max(1, Math.ceil(count / cols));
const colW = width / cols;
const rowH = height / rows;
// Sizes of internal squares and polaroid borders
const pSize = Math.max(colW, rowH) * 1.25;
const innerWidth = pSize;
const innerHeight = pSize;
const pBorderTop = pSize * 0.05;
const pBorderSide = pSize * 0.05;
const pBorderBottom = pSize * 0.25;
const pw = innerWidth + 2 * pBorderSide;
const ph = innerHeight + pBorderTop + pBorderBottom;
// Generate grid positions with random offsets
const cells = [];
for (let i = 0; i < count; i++) {
const r = Math.floor(i / cols);
const c = i % cols;
const itemsInRow = (r === rows - 1) ? (count - r * cols) : cols;
const rowOffset = (cols - itemsInRow) * colW / 2;
const bx = rowOffset + c * colW + colW / 2;
const by = r * rowH + rowH / 2;
const cx = bx + (Math.random() - 0.5) * colW * 0.6;
const cy = by + (Math.random() - 0.5) * rowH * 0.6;
const angle = (Math.random() - 0.5) * 0.45; // roughly ±13 degrees
cells.push({ cx, cy, angle });
}
// Shuffle iteration order so Z-index depth is unpredictable
const indices = Array.from({ length: count }, (_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
// Render the scatter polaroid collage
for (const idx of indices) {
const { cx, cy, angle } = cells[idx];
ctx.save();
// --- 1. Polaroid Paper Setup & Shadow ---
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.shadowColor = 'rgba(0,0,0,0.7)';
ctx.shadowBlur = Math.max(width, height) * 0.012;
ctx.shadowOffsetX = ctx.shadowBlur * 0.3;
ctx.shadowOffsetY = ctx.shadowBlur * 0.4;
ctx.fillStyle = '#fafafa';
ctx.fillRect(-pw/2, -ph/2, pw, ph);
// Clear shadow for next steps
ctx.shadowColor = 'transparent';
// --- 2. Create the Window to the vibrant background ---
const ix = -innerWidth / 2;
const iy = -innerHeight / 2 + pBorderTop / 2 - pBorderBottom / 2;
ctx.beginPath();
ctx.rect(ix, iy, innerWidth, innerHeight);
// Save state to isolate the clipping mechanism
ctx.save();
ctx.clip();
// Apply zoom and inverse transform to map exact world coordinates
// so the image looks like it perfectly continues from polaroid to polaroid
ctx.scale(zoom, zoom);
ctx.rotate(-angle);
ctx.translate(-cx, -cy);
ctx.drawImage(vibrantCanvas, 0, 0, width, height);
// Drop clip region
ctx.restore();
// Apply subtle inset stroke (inner shadow border)
ctx.beginPath();
ctx.rect(ix, iy, innerWidth, innerHeight);
ctx.strokeStyle = 'rgba(0,0,0,0.12)';
ctx.lineWidth = Math.max(1, pSize * 0.005);
ctx.stroke();
ctx.restore(); // Restore completely out of main polaroid bounds
// --- 3. Draw masking tape ---
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(angle);
// Position tape at top-middle, spanning outer edge
ctx.translate(0, -ph/2 + pBorderTop * 0.7);
ctx.rotate((Math.random() - 0.5) * 0.4);
const tw = pw * 0.28;
const th = pBorderTop * 1.5;
ctx.fillStyle = 'rgba(235, 235, 220, 0.85)';
ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = Math.max(width, height) * 0.003;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 2;
ctx.fillRect(-tw/2, -th/2, tw, th);
// Minor tape wrinkle lines
ctx.strokeStyle = 'rgba(0,0,0,0.06)';
ctx.lineWidth = Math.max(1, pSize * 0.003);
const margin = th * 0.25;
ctx.beginPath();
ctx.moveTo(-tw/2 + margin, -th/2 + margin);
ctx.lineTo(tw/2 - margin, th/2 - margin);
ctx.stroke();
ctx.restore();
}
return canvas;
}
Apply Changes