You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, sepiaIntensity = "0.85", drawGridLines = "1") {
const intensity = Math.min(1, Math.max(0, parseFloat(sepiaIntensity)));
const addGrid = parseInt(drawGridLines) === 1;
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Draw original image
ctx.drawImage(originalImg, 0, 0);
// 2. Apply Sepia & Antique tone to the image
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
let r = data[i], g = data[i + 1], b = data[i + 2];
// Standard Sepia formula
let tr = r * 0.393 + g * 0.769 + b * 0.189;
let tg = r * 0.349 + g * 0.686 + b * 0.168;
let tb = r * 0.272 + g * 0.534 + b * 0.131;
// Enhance for parchment warmth
tr *= 1.15;
tg *= 1.05;
tb *= 0.90;
// Blend with original
data[i] = Math.min(255, r + (tr - r) * intensity);
data[i + 1] = Math.min(255, g + (tg - g) * intensity);
data[i + 2] = Math.min(255, b + (tb - b) * intensity);
}
ctx.putImageData(imgData, 0, 0);
// 3. Create "Terra Incognita" Fog surrounding the center
ctx.save();
// Cap blur to prevent massive lag on 4K+ images
const blurAmount = Math.min(60, Math.max(width, height) * 0.04);
ctx.filter = `blur(${blurAmount}px)`;
// Outer frame path mapping the unexplored area
ctx.fillStyle = '#cdb492'; // Classic map parchment tone
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(width, 0);
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.lineTo(0, 0);
// Cut out an organic hole in the middle mapping explored lands
const cx = width / 2;
const cy = height / 2;
const baseRadius = Math.min(width, height) * 0.38;
const segments = 150;
for (let i = 0; i <= segments; i++) {
const angle = -i * (Math.PI * 2) / segments; // reverse map for inner cutout
// Complex sine combinations for organic, hand-drawn edges
const rOffset = Math.sin(i * 12) * (baseRadius * 0.06) +
Math.sin(i * 5) * (baseRadius * 0.09) +
Math.sin(i * 27) * (baseRadius * 0.03) +
Math.random() * (baseRadius * 0.03);
const r = baseRadius + rOffset;
const px = cx + Math.cos(angle) * r;
const py = cy + Math.sin(angle) * r;
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.fill();
ctx.restore();
// 4. Map Grids (Latitude / Longitude emulation)
if (addGrid) {
ctx.strokeStyle = `rgba(101, 67, 33, 0.25)`;
ctx.lineWidth = Math.max(1, width * 0.002);
let gridSize = Math.max(width, height) / 12;
ctx.beginPath();
for (let x = cx % gridSize; x < width; x += gridSize) {
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
}
for (let y = cy % gridSize; y < height; y += gridSize) {
ctx.moveTo(0, y);
ctx.lineTo(width, y);
}
ctx.stroke();
}
// 5. Paper texture/grain via lower-resolution canvas upscaling
const nWidth = Math.max(1, Math.floor(width / 3));
const nHeight = Math.max(1, Math.floor(height / 3));
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = nWidth;
noiseCanvas.height = nHeight;
const nCtx = noiseCanvas.getContext('2d');
const nData = nCtx.createImageData(nWidth, nHeight);
for(let i = 0; i < nData.data.length; i += 4) {
let val = Math.random() * 255;
nData.data[i] = val; // R
nData.data[i+1] = val * 0.9; // G
nData.data[i+2] = val * 0.8; // B
nData.data[i+3] = 40; // Soft opacity
}
nCtx.putImageData(nData, 0, 0);
ctx.globalCompositeOperation = 'multiply';
ctx.drawImage(noiseCanvas, 0, 0, nWidth, nHeight, 0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
// 6. Draw Vignette Gradient Overlay
const radGrad = ctx.createRadialGradient(cx, cy, baseRadius * 0.5, cx, cy, Math.max(width, height) * 0.8);
radGrad.addColorStop(0, 'rgba(0, 0, 0, 0)');
radGrad.addColorStop(1, 'rgba(70, 40, 10, 0.55)');
ctx.fillStyle = radGrad;
ctx.fillRect(0, 0, width, height);
// 7. Draw Stylized Compass Rose (Procedural)
const compassRadius = Math.min(width, height) * 0.08;
const padding = compassRadius * 1.5;
if (width > padding * 2.5 && height > padding * 2.5) {
drawCompass(ctx, padding, height - padding, compassRadius);
}
// 8. Add "Terra Incognita" text marker
ctx.fillStyle = 'rgba(60, 25, 5, 0.85)';
const fontSize = Math.max(20, Math.min(width, height) * 0.045);
ctx.font = `italic 700 ${fontSize}px "Times New Roman", Times, serif`;
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
// Add text shadow to help it stand out
ctx.shadowColor = "rgba(220, 200, 170, 0.8)";
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
ctx.shadowBlur = 0;
ctx.fillText("Terra Incognita", width - (padding * 0.5), height - (padding * 0.5));
ctx.shadowColor = "transparent";
return canvas;
// Helper block explicitly for procedural compass rose rendering
function drawCompass(c, x, y, r) {
c.save();
c.translate(x, y);
// Compass borders
c.strokeStyle = 'rgba(70, 30, 10, 0.7)';
c.lineWidth = Math.max(1, r * 0.03);
c.beginPath(); c.arc(0, 0, r, 0, Math.PI * 2); c.stroke();
c.beginPath(); c.arc(0, 0, r * 0.85, 0, Math.PI * 2); c.stroke();
const colorLight = 'rgba(210, 180, 140, 0.95)';
const colorDark = 'rgba(80, 35, 10, 0.95)';
const drawDiamondSide = (angle, len, wide, color) => {
c.save();
c.rotate(angle);
c.fillStyle = color;
c.beginPath();
c.moveTo(0, -len);
c.lineTo(wide, 0);
c.lineTo(0, 0);
c.fill();
c.restore();
};
// Draw compass star points
for(let i = 0; i < 8; i++) {
let angle = i * Math.PI / 4;
let length = (i % 2 === 0) ? r * 0.95 : r * 0.6; // Alternating lengths
let widthSpan = r * 0.15;
// Light side
drawDiamondSide(angle, length, widthSpan, colorLight);
// Dark side
drawDiamondSide(angle, length, -widthSpan, colorDark);
}
// Inner compass pip
c.beginPath();
c.arc(0, 0, r * 0.1, 0, Math.PI * 2);
c.fillStyle = colorDark;
c.fill();
c.restore();
}
}
Apply Changes