You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, caption = "Шкатулка воспоминаний", vintageLevel = 1, showTrinkets = 1) {
const CW = 1000;
const CH = 800;
const canvas = document.createElement('canvas');
canvas.width = CW;
canvas.height = CH;
const ctx = canvas.getContext('2d');
// Parse parameters
const vLevel = Math.max(0, Math.min(1, parseFloat(vintageLevel) || 1));
const showAccessories = parseInt(showTrinkets) === 1;
// Draw Background Wood Texture
ctx.fillStyle = '#2a1a12';
ctx.fillRect(0, 0, CW, CH);
for (let x = 0; x < CW; x += 3) {
if (Math.random() > 0.3) {
ctx.fillStyle = Math.random() > 0.5 ? 'rgba(0,0,0,0.15)' : 'rgba(255,255,255,0.02)';
ctx.fillRect(x, 0, Math.random() * 3 + 1, CH);
}
// Wood knots
if (Math.random() < 0.003) {
ctx.save();
ctx.beginPath();
ctx.ellipse(x, Math.random() * CH, Math.random() * 6 + 4, Math.random() * 80 + 40, 0, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fill();
ctx.restore();
}
}
// Deep vignette/shadow for the box interior effect
const bgGrad = ctx.createRadialGradient(CW / 2, CH / 2, 200, CW / 2, CH / 2, 700);
bgGrad.addColorStop(0, 'rgba(0,0,0,0)');
bgGrad.addColorStop(1, 'rgba(0,0,0,0.85)');
ctx.fillStyle = bgGrad;
ctx.fillRect(0, 0, CW, CH);
// Box Inner Frame Edges
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, CW, 15);
ctx.fillRect(0, CH - 15, CW, 15);
ctx.fillRect(0, 0, 15, CH);
ctx.fillRect(CW - 15, 0, 15, CH);
// Helper Functions for Trinkets
function drawBgPhoto(ctx, x, y, rot) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rot);
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 8;
ctx.fillStyle = '#e8e4d6';
ctx.fillRect(-150, -180, 300, 360);
ctx.shadowColor = 'transparent';
ctx.fillStyle = '#bbb';
ctx.fillRect(-130, -160, 260, 260);
ctx.globalAlpha = 0.35;
// Square crop original image
const dim = Math.min(originalImg.width, originalImg.height);
const sx = (originalImg.width - dim) / 2;
const sy = (originalImg.height - dim) / 2;
ctx.drawImage(originalImg, sx, sy, dim, dim, -130, -160, 260, 260);
ctx.globalAlpha = 1.0;
const g = ctx.createRadialGradient(0, -30, 50, 0, -30, 180);
g.addColorStop(0, 'rgba(0,0,0,0)');
g.addColorStop(1, 'rgba(0,0,0,0.5)');
ctx.fillStyle = g;
ctx.fillRect(-130, -160, 260, 260);
ctx.restore();
}
function drawPocketWatch(ctx, x, y, r) {
ctx.save();
ctx.translate(x, y);
ctx.shadowColor = 'rgba(0,0,0,0.7)';
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 8;
const grad = ctx.createRadialGradient(-r / 3, -r / 3, r / 4, 0, 0, r);
grad.addColorStop(0, '#f9e8a2');
grad.addColorStop(0.5, '#cda135');
grad.addColorStop(1, '#6b4c10');
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
ctx.beginPath();
ctx.arc(0, -r - r * 0.15, r * 0.25, 0, Math.PI * 2);
ctx.strokeStyle = '#cda135';
ctx.lineWidth = r * 0.1;
ctx.stroke();
ctx.shadowColor = 'transparent';
ctx.beginPath();
ctx.arc(0, 0, r * 0.85, 0, Math.PI * 2);
ctx.fillStyle = '#f8f4e6';
ctx.fill();
const inShadow = ctx.createRadialGradient(0, 0, r * 0.6, 0, 0, r * 0.85);
inShadow.addColorStop(0, 'rgba(0,0,0,0)');
inShadow.addColorStop(1, 'rgba(0,0,0,0.5)');
ctx.fillStyle = inShadow;
ctx.fill();
ctx.strokeStyle = '#444';
ctx.lineWidth = r * 0.03;
for (let i = 0; i < 12; i++) {
ctx.save();
ctx.rotate((Math.PI * 2 / 12) * i);
ctx.beginPath();
ctx.moveTo(0, -r * 0.82);
ctx.lineTo(0, -r * 0.68);
ctx.stroke();
ctx.restore();
}
ctx.lineCap = 'round';
ctx.beginPath();
ctx.lineWidth = r * 0.06;
ctx.moveTo(0, 0);
ctx.lineTo(r * 0.3, -r * 0.4);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = r * 0.04;
ctx.moveTo(0, 0);
ctx.lineTo(-r * 0.5, r * 0.2);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, r * 0.06, 0, Math.PI * 2);
ctx.fillStyle = '#111';
ctx.fill();
ctx.restore();
}
function drawPetal(ctx, x, y, rot, s) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rot);
ctx.scale(s, s);
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 4;
const grad = ctx.createLinearGradient(0, 0, 0, -45);
grad.addColorStop(0, '#5a1515');
grad.addColorStop(1, '#8b2020');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(20, -15, 30, -35, 0, -45);
ctx.bezierCurveTo(-30, -35, -20, -15, 0, 0);
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -40);
ctx.stroke();
ctx.restore();
}
function drawTape(ctx, x, y, rot) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rot);
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 2;
ctx.fillStyle = 'rgba(235, 230, 205, 0.85)';
ctx.beginPath();
ctx.moveTo(-60, -15);
for (let i = 0; i < 5; i++) ctx.lineTo(-60 + Math.random() * 5, -15 + (30 / 5) * i);
ctx.lineTo(-60, 15);
ctx.lineTo(60, 15);
for (let i = 0; i < 5; i++) ctx.lineTo(60 - Math.random() * 5, 15 - (30 / 5) * i);
ctx.closePath();
ctx.fill();
const lg = ctx.createLinearGradient(-60, 0, 60, 0);
lg.addColorStop(0, 'rgba(0,0,0,0.03)');
lg.addColorStop(0.5, 'rgba(255,255,255,0.2)');
lg.addColorStop(1, 'rgba(0,0,0,0.03)');
ctx.fillStyle = lg;
ctx.fill();
ctx.restore();
}
// Render Trinkets
if (showAccessories) {
drawBgPhoto(ctx, 320, 480, -0.15);
drawBgPhoto(ctx, 720, 320, 0.2);
drawPocketWatch(ctx, 180, 220, 65);
const petals = [
{ x: 120, y: 550, r: 0.5, s: 1.0 },
{ x: 230, y: 680, r: 1.2, s: 0.8 },
{ x: 830, y: 150, r: -0.5, s: 0.9 },
{ x: 890, y: 620, r: 2.1, s: 1.1 },
{ x: 500, y: 130, r: -1.2, s: 0.7 },
{ x: 910, y: 380, r: 0.8, s: 0.6 }
];
petals.forEach(p => drawPetal(ctx, p.x, p.y, p.r, p.s));
}
// Main Polaroid Setup
const maxImgDim = 460;
const scale = Math.min(maxImgDim / originalImg.width, maxImgDim / originalImg.height);
const imgW = originalImg.width * scale;
const imgH = originalImg.height * scale;
const pPadX = 25;
const pPadTop = 25;
const pPadBot = 100;
const pW = imgW + pPadX * 2;
const pH = imgH + pPadTop + pPadBot;
// Slightly right-aligned
const cx = CW / 2 + 30;
const cy = CH / 2;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(0.06); // casual diagonal placement
// Polaroid Shadow
ctx.shadowColor = 'rgba(0,0,0,0.85)';
ctx.shadowBlur = 35;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 20;
// Polaroid Base
ctx.fillStyle = '#f9f6ef';
ctx.fillRect(-pW / 2, -pH / 2, pW, pH);
ctx.shadowColor = 'transparent';
ctx.strokeStyle = 'rgba(0,0,0,0.06)';
ctx.lineWidth = 1;
ctx.strokeRect(-pW / 2, -pH / 2, pW, pH);
// Filter and Process Original Image
const tempCan = document.createElement('canvas');
tempCan.width = imgW;
tempCan.height = imgH;
const tCtx = tempCan.getContext('2d');
tCtx.drawImage(originalImg, 0, 0, imgW, imgH);
if (vLevel > 0) {
const d = tCtx.getImageData(0, 0, imgW, imgH);
const data = d.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
const tr = (r * 0.393) + (g * 0.769) + (b * 0.189);
const tg = (r * 0.349) + (g * 0.686) + (b * 0.168);
const tb = (r * 0.272) + (g * 0.534) + (b * 0.131);
data[i] = r + (tr - r) * vLevel;
data[i+1] = g + (tg - g) * vLevel;
data[i+2] = b + (tb - b) * vLevel;
// Grain
const noise = (Math.random() - 0.5) * 20 * vLevel;
data[i] += noise;
data[i+1] += noise;
data[i+2] += noise;
}
tCtx.putImageData(d, 0, 0);
// Subtly darkened photo edges
const vigGrad = tCtx.createRadialGradient(imgW / 2, imgH / 2, Math.min(imgW, imgH) * 0.3, imgW / 2, imgH / 2, Math.max(imgW, imgH) * 0.75);
vigGrad.addColorStop(0, 'rgba(0,0,0,0)');
vigGrad.addColorStop(1, `rgba(0,0,0,${0.5 * vLevel})`);
tCtx.fillStyle = vigGrad;
tCtx.fillRect(0, 0, imgW, imgH);
}
// Draw Vintage Image onto Polaroid
ctx.drawImage(tempCan, -pW / 2 + pPadX, -pH / 2 + pPadTop);
ctx.strokeStyle = 'rgba(0,0,0,0.15)';
ctx.strokeRect(-pW / 2 + pPadX, -pH / 2 + pPadTop, imgW, imgH);
// Add Handwritten Text Label
ctx.fillStyle = '#222';
ctx.font = '40px "Segoe Script", "Caveat", "Comic Sans MS", cursive';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const maxTextWidth = pW - 50;
const words = String(caption).split(' ');
let line = '';
const lines = [];
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine);
if (metrics.width > maxTextWidth && n > 0) {
lines.push(line);
line = words[n] + ' ';
} else {
line = testLine;
}
}
lines.push(line);
const textCenterY = pH / 2 - pPadBot / 2;
const totalTextHeight = lines.length * 40;
const startY = textCenterY - (totalTextHeight / 2) + 20;
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i].trim(), 0, startY + (i * 40));
}
// Add Retro Timestamp Date
ctx.fillStyle = '#888';
ctx.font = '14px "Courier New", Courier, monospace';
ctx.textAlign = 'right';
ctx.fillText(new Date().toLocaleDateString(), pW / 2 - pPadX + 10, pH / 2 - 15);
// Apply Tape
if (showAccessories) {
drawTape(ctx, 0, -pH / 2 + 20, -0.04);
}
ctx.restore();
return canvas;
}
Apply Changes