You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, title = "A Journey Through Time", subtitle = "Chapter I") {
// Determine canvas dimensions
const width = 800;
const height = 600;
// Create canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Draw Wooden Desk Background
ctx.fillStyle = '#1e110a';
ctx.fillRect(0, 0, width, height);
// Add faux wood grain stripes
ctx.lineWidth = 4;
for(let i = 0; i < 200; i++) {
ctx.strokeStyle = `rgba(0, 0, 0, ${Math.random() * 0.4})`;
let x = Math.random() * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x + (Math.random() * 10 - 5), height);
ctx.stroke();
}
// Smooth radial lighting over the wood
let deskLighting = ctx.createRadialGradient(width/2, height/2, 100, width/2, height/2, 600);
deskLighting.addColorStop(0, 'rgba(255,255,255,0.05)');
deskLighting.addColorStop(1, 'rgba(0,0,0,0.85)');
ctx.fillStyle = deskLighting;
ctx.fillRect(0, 0, width, height);
// 2. Draw Book Cover Outline (Dark Leather)
ctx.save();
ctx.shadowColor = 'rgba(0, 0, 0, 0.8)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 10;
ctx.fillStyle = '#2b160e'; // Very dark reddish brown
// Draw rounded rect manually for broad compatibility
ctx.beginPath();
ctx.moveTo(60, 50);
ctx.lineTo(740, 50);
ctx.quadraticCurveTo(760, 50, 760, 70);
ctx.lineTo(760, 550);
ctx.quadraticCurveTo(760, 570, 740, 570);
ctx.lineTo(60, 570);
ctx.quadraticCurveTo(40, 570, 40, 550);
ctx.lineTo(40, 70);
ctx.quadraticCurveTo(40, 50, 60, 50);
ctx.closePath();
ctx.fill();
ctx.restore();
// The protruding book spine (leather bump)
ctx.fillStyle = '#110804';
ctx.beginPath();
ctx.moveTo(375, 45);
ctx.quadraticCurveTo(400, 55, 425, 45);
ctx.lineTo(425, 575);
ctx.quadraticCurveTo(400, 565, 375, 575);
ctx.closePath();
ctx.fill();
// 3. Draw Book Pages (Stacking them up to create 3D thickness)
for (let i = 12; i >= 0; i--) {
if (i === 12) { // Add drop shadow to bottom-most pages
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
} else {
ctx.shadowColor = 'transparent';
}
ctx.beginPath();
// Left Page geometry
ctx.moveTo(70 - i, 80 + i * 0.5);
ctx.quadraticCurveTo(235 - i * 0.5, 70 + i * 0.5, 400, 90 + i * 0.5); // Top edge
ctx.lineTo(400, 510 + i * 0.5); // Center Spine
ctx.quadraticCurveTo(235 - i * 0.5, 530 + i * 0.5, 70 - i, 520 + i * 0.5); // Bottom edge
ctx.closePath();
// Right Page geometry
ctx.moveTo(400, 90 + i * 0.5);
ctx.quadraticCurveTo(565 + i * 0.5, 70 + i * 0.5, 730 + i, 80 + i * 0.5); // Top edge
ctx.lineTo(730 + i, 520 + i * 0.5); // Outer right edge
ctx.quadraticCurveTo(565 + i * 0.5, 530 + i * 0.5, 400, 510 + i * 0.5); // Bottom edge
ctx.closePath();
ctx.fillStyle = (i % 2 === 0) ? '#d9d1b0' : '#e6deb8'; // Alternating shadow color for pages
if (i === 0) {
// Fill top-most page with realistic spine-shadow gradient
// Left page overlay
let gradLeft = ctx.createLinearGradient(70, 0, 400, 0);
gradLeft.addColorStop(0, '#e8e0c0');
gradLeft.addColorStop(0.8, '#f5efd3');
gradLeft.addColorStop(1, '#a89f83');
ctx.fillStyle = gradLeft;
ctx.beginPath();
ctx.moveTo(70, 80);
ctx.quadraticCurveTo(235, 70, 400, 90);
ctx.lineTo(400, 510);
ctx.quadraticCurveTo(235, 530, 70, 520);
ctx.closePath();
ctx.fill();
// Right page overlay
let gradRight = ctx.createLinearGradient(400, 0, 730, 0);
gradRight.addColorStop(0, '#a89f83');
gradRight.addColorStop(0.2, '#f5efd3');
gradRight.addColorStop(1, '#e8e0c0');
ctx.fillStyle = gradRight;
ctx.beginPath();
ctx.moveTo(400, 90);
ctx.quadraticCurveTo(565, 70, 730, 80);
ctx.lineTo(730, 520);
ctx.quadraticCurveTo(565, 530, 400, 510);
ctx.closePath();
ctx.fill();
} else {
ctx.fill();
}
}
// Center spine dark shadow fold
let spineShadow = ctx.createLinearGradient(380, 0, 420, 0);
spineShadow.addColorStop(0, 'rgba(0,0,0,0)');
spineShadow.addColorStop(0.3, 'rgba(0,0,0,0.1)');
spineShadow.addColorStop(0.5, 'rgba(0,0,0,0.4)'); // Deep fold
spineShadow.addColorStop(0.7, 'rgba(0,0,0,0.1)');
spineShadow.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = spineShadow;
ctx.fillRect(380, 80, 40, 440);
// Define top page shape for clipping bounds
const definePageBounds = () => {
ctx.beginPath();
// Left outline
ctx.moveTo(70, 80);
ctx.quadraticCurveTo(235, 70, 400, 90);
ctx.lineTo(400, 510);
ctx.quadraticCurveTo(235, 530, 70, 520);
// Right outline
ctx.lineTo(400, 510);
ctx.quadraticCurveTo(565, 530, 730, 520);
ctx.lineTo(730, 80);
ctx.quadraticCurveTo(565, 70, 400, 90);
ctx.closePath();
};
// 4. Add "Old Paper" texture (dirt / spots)
ctx.save();
definePageBounds();
ctx.clip(); // Ensure texture does not spill off pages
for (let j = 0; j < 300; j++) {
let x = 70 + Math.random() * 660;
let y = 70 + Math.random() * 460;
ctx.fillStyle = `rgba(100, 70, 40, ${Math.random() * 0.08})`;
ctx.beginPath();
ctx.arc(x, y, Math.random() * 3, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
// 5. Draw Faded Antique Text on Left Page
ctx.save();
ctx.fillStyle = 'rgba(60, 40, 25, 0.7)'; // Faded sepia ink
ctx.textAlign = 'center';
// Headers
ctx.font = 'italic 26px "Georgia", "Times New Roman", serif';
ctx.fillText(subtitle, 235, 120);
ctx.font = '18px "Georgia", "Times New Roman", serif';
ctx.fillText(title, 235, 150);
// Decorative line
ctx.strokeStyle = 'rgba(60, 40, 25, 0.4)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(150, 170);
ctx.lineTo(320, 170);
ctx.stroke();
// Left page lore text
ctx.textAlign = 'left';
ctx.font = '14px "Georgia", "Times New Roman", serif';
let text = "There are memories that time simply cannot erase. Woven into the worn fibers of this bound artifact, the moments captured within tell a poignant story of origins, of paths once wandered, and lives lived to their absolute fullest.\n\nLike echoes from yesteryear, these faces gaze outward through the veil of time, frozen securely in sepia and faded ink. We watch as the days fade in tandem with the setting sun, but rest assured, the impression left upon the paper and soul remains everlasting...\n\nBehold the visage of past eras, serving forever as a timeless testament to a fleeting, yet profoundly beautiful existence.";
let textY = 210;
let paragraphs = text.split('\n');
paragraphs.forEach(p => {
let words = p.split(' ');
let line = '';
for (let k = 0; k < words.length; k++) {
let testLine = line + words[k] + ' ';
let metrics = ctx.measureText(testLine);
if (metrics.width > 220 && k > 0) {
ctx.fillText(line, 125, textY);
line = words[k] + ' ';
textY += 21;
} else {
line = testLine;
}
}
ctx.fillText(line, 125, textY);
textY += 22; // Paragraph bottom spacing
});
ctx.restore();
// 6. Center and Vintage-Filter Original Image onto Right Page
let boxW = 220;
let boxH = 320;
let scaleX = boxW / originalImg.width;
let scaleY = boxH / originalImg.height;
let scale = Math.min(scaleX, scaleY);
let dw = originalImg.width * scale;
let dh = originalImg.height * scale;
// Right page center
let dx = 565;
let dy = 300;
ctx.save();
ctx.translate(dx, dy);
// Add slight playful rotation for scrap-book feel
ctx.rotate((Math.PI / 180) * 1.5);
// Drop shadow behind pasted photo
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
// Apply old photo filters (Sepia, Lower Brightness, Higher Contrast)
ctx.filter = 'sepia(0.8) contrast(1.15) brightness(0.9)';
ctx.drawImage(originalImg, -dw/2, -dh/2, dw, dh);
ctx.filter = 'none'; // reset filter
// Vintage thick white photo border
ctx.strokeStyle = 'rgba(255, 250, 240, 0.9)';
ctx.lineWidth = 6;
ctx.strokeRect(-dw/2, -dh/2, dw, dh);
ctx.shadowColor = 'transparent'; // reset shadow
// Little scrap-book black photograph corner-holders
ctx.fillStyle = '#1a1a1a';
let c = 15; // corner bracket size
// Top-Left corner
ctx.beginPath();
ctx.moveTo(-dw/2, -dh/2);
ctx.lineTo(-dw/2 + c, -dh/2);
ctx.lineTo(-dw/2, -dh/2 + c);
ctx.fill();
// Top-Right corner
ctx.beginPath();
ctx.moveTo(dw/2, -dh/2);
ctx.lineTo(dw/2 - c, -dh/2);
ctx.lineTo(dw/2, -dh/2 + c);
ctx.fill();
// Bottom-Left corner
ctx.beginPath();
ctx.moveTo(-dw/2, dh/2);
ctx.lineTo(-dw/2 + c, dh/2);
ctx.lineTo(-dw/2, dh/2 - c);
ctx.fill();
// Bottom-Right corner
ctx.beginPath();
ctx.moveTo(dw/2, dh/2);
ctx.lineTo(dw/2 - c, dh/2);
ctx.lineTo(dw/2, dh/2 - c);
ctx.fill();
ctx.restore();
// 7. General atmospheric lighting/sepia tint over the entire canvas
ctx.fillStyle = 'rgba(120, 80, 40, 0.08)'; // Yellowish/brown warm hue
ctx.fillRect(0, 0, width, height);
return canvas;
}
Apply Changes