You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, frameThickness = 50, baseParchmentColor = "rgb(240,225,190)", burnEdgeColor = "rgb(100,60,20)", cornerSymbol = "skull", decorationColor = "rgb(60,40,20)") {
const ft = Math.max(20, frameThickness); // Ensure a minimum thickness
const canvas = document.createElement('canvas');
canvas.width = originalImg.width + 2 * ft;
canvas.height = originalImg.height + 2 * ft;
const ctx = canvas.getContext('2d');
// Helper: Random number in range
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// Helper function to draw a jagged line segment for the parchment path
function addJaggedLineSegment(ctx, p1, p2, jMagnitude, numSegments) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const segLen = Math.sqrt(dx * dx + dy * dy);
if (segLen === 0) {
ctx.lineTo(p2.x, p2.y);
return;
}
const unitDx = dx / segLen;
const unitDy = dy / segLen;
const perpX = -unitDy; // Perpendicular vector component
const perpY = unitDx; // Perpendicular vector component
for (let i = 1; i < numSegments; i++) {
const t = i / numSegments;
const currentX = p1.x + t * dx;
const currentY = p1.y + t * dy;
let displacement = (Math.random() - 0.5) * 2 * jMagnitude;
// Reduce jaggedness near endpoints for smoother transitions if needed, but full randomness is fine for torn paper
if (i < numSegments * 0.1 || i > numSegments * 0.9) {
displacement *= 0.5; // Optional: taper near ends
}
ctx.lineTo(currentX + displacement * perpX, currentY + displacement * perpY);
}
ctx.lineTo(p2.x, p2.y); // Ensure it ends at the exact corner
}
// 1. Draw Burnt Background
ctx.fillStyle = burnEdgeColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Draw Jagged Parchment Layer
const parchmentPath = new Path2D();
const nominalBorder = ft * 0.05; // Nominal small border of "burn" visible
const pX = nominalBorder;
const pY = nominalBorder;
const pW = canvas.width - 2 * nominalBorder;
const pH = canvas.height - 2 * nominalBorder;
const jAmount = ft * 0.2; // Jaggedness magnitude
const pointsPerSide = 15;
// Define perturbed corners for the parchment
const cTL = { x: pX + rand(-jAmount * 0.3, jAmount * 0.3), y: pY + rand(-jAmount * 0.3, jAmount * 0.3) };
const cTR = { x: pX + pW + rand(-jAmount * 0.3, jAmount * 0.3), y: pY + rand(-jAmount * 0.3, jAmount * 0.3) };
const cBR = { x: pX + pW + rand(-jAmount * 0.3, jAmount * 0.3), y: pY + pH + rand(-jAmount * 0.3, jAmount * 0.3) };
const cBL = { x: pX + rand(-jAmount * 0.3, jAmount * 0.3), y: pY + pH + rand(-jAmount * 0.3, jAmount * 0.3) };
parchmentPath.moveTo(cTL.x, cTL.y);
addJaggedLineSegment(parchmentPath, cTL, cTR, jAmount, pointsPerSide);
addJaggedLineSegment(parchmentPath, cTR, cBR, jAmount, pointsPerSide);
addJaggedLineSegment(parchmentPath, cBR, cBL, jAmount, pointsPerSide);
addJaggedLineSegment(parchmentPath, cBL, cTL, jAmount, pointsPerSide);
parchmentPath.closePath();
ctx.fillStyle = baseParchmentColor;
ctx.fill(parchmentPath);
// 3. Add subtle speckle texture to parchment
ctx.save();
ctx.clip(parchmentPath);
const numSpeckles = (canvas.width * canvas.height) / 70; // Adjust density
for (let i = 0; i < numSpeckles; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
// Use a slightly darker, semi-transparent black for speckles
ctx.fillStyle = `rgba(0, 0, 0, ${Math.random() * 0.08 + 0.02})`;
ctx.beginPath();
ctx.arc(x, y, Math.random() * 1.5 + 0.5, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore(); // Remove clipping
// 4. Draw the Original Image
ctx.drawImage(originalImg, ft, ft, originalImg.width, originalImg.height);
// 5. Draw Inner Shadow/Border for Image (subtle)
ctx.strokeStyle = 'rgba(0,0,0,0.25)';
ctx.lineWidth = 1;
ctx.strokeRect(ft - 0.5, ft - 0.5, originalImg.width + 1, originalImg.height + 1);
ctx.strokeStyle = 'rgba(0,0,0,0.15)';
ctx.lineWidth = 2;
ctx.strokeRect(ft - 1.5, ft - 1.5, originalImg.width + 3, originalImg.height + 3);
// --- Corner Decoration Functions ---
function drawSkull(ctx, cx, cy, size, color) {
ctx.fillStyle = color;
const headRadiusX = size * 0.25;
const headRadiusY = size * 0.3;
const eyeRadius = size * 0.07;
const eyeOffsetX = size * 0.11;
const eyeOffsetY = -size * 0.08;
const noseSize = size * 0.05;
// Head shape
ctx.beginPath();
ctx.ellipse(cx, cy, headRadiusX, headRadiusY, 0, 0, Math.PI * 2);
ctx.fill();
// Eyes (simple dark circles)
const eyeColor = "black";
ctx.fillStyle = eyeColor;
ctx.beginPath();
ctx.arc(cx - eyeOffsetX, cy + eyeOffsetY, eyeRadius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + eyeOffsetX, cy + eyeOffsetY, eyeRadius, 0, Math.PI * 2);
ctx.fill();
// Nose (simple triangle)
ctx.beginPath();
ctx.moveTo(cx, cy + size * 0.04);
ctx.lineTo(cx - noseSize, cy + size * 0.13);
ctx.lineTo(cx + noseSize, cy + size * 0.13);
ctx.closePath();
ctx.fill();
}
function drawCrossbones(ctx, cx, cy, size, color) {
ctx.fillStyle = color;
ctx.strokeStyle = color; // If outlining parts not filled
ctx.lineWidth = size * 0.03;
const boneLength = size * 0.7;
const boneWidth = size * 0.12;
const endRadius = boneWidth * 0.6;
ctx.save();
ctx.translate(cx, cy);
for (let i = 0; i < 2; i++) {
ctx.rotate(i === 0 ? Math.PI / 4 : Math.PI / 2); // Rotate for first, then additional 90 for second
// Bone shaft
ctx.beginPath();
ctx.rect(-boneLength / 2, -boneWidth / 2, boneLength, boneWidth);
ctx.fill();
// Bone ends (simplified as circles)
ctx.beginPath();
ctx.arc(-boneLength / 2 - endRadius*0.1, 0, endRadius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(boneLength / 2 + endRadius*0.1, 0, endRadius, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawXMark(ctx, cx, cy, size, color) {
ctx.strokeStyle = color;
ctx.lineWidth = Math.max(2, size * 0.15); // Ensure visible line
ctx.lineCap = 'round';
const arm = size * 0.35;
ctx.beginPath();
ctx.moveTo(cx - arm, cy - arm);
ctx.lineTo(cx + arm, cy + arm);
ctx.moveTo(cx + arm, cy - arm);
ctx.lineTo(cx - arm, cy + arm);
ctx.stroke();
}
// 6. Draw Corner Decorations
const decSymbol = cornerSymbol.toLowerCase();
if (decSymbol !== "none") {
const decSize = ft * 0.65; // Size of the decoration
const decAnchorOffset = ft * 0.5; // Center of decoration from edge of canvas
const corners = [
{ x: decAnchorOffset, y: decAnchorOffset }, // TL
{ x: canvas.width - decAnchorOffset, y: decAnchorOffset }, // TR
{ x: decAnchorOffset, y: canvas.height - decAnchorOffset }, // BL
{ x: canvas.width - decAnchorOffset, y: canvas.height - decAnchorOffset } // BR
];
corners.forEach(corner => {
if (decSymbol === "skull") {
drawSkull(ctx, corner.x, corner.y, decSize, decorationColor);
} else if (decSymbol === "crossbones") {
drawCrossbones(ctx, corner.x, corner.y, decSize, decorationColor);
} else if (decSymbol === "x") {
drawXMark(ctx, corner.x, corner.y, decSize, decorationColor);
}
});
}
return canvas;
}
Apply Changes