You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, scratches = 2, beastColor = 'rgba(200, 40, 0, 0.4)', fleshColor = '#3a0000') {
const width = originalImg.width;
const height = originalImg.height;
// 1. Base Layer (The exposed under-layer representing flesh/blood)
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Fill base layer with dark flesh color
ctx.fillStyle = fleshColor;
ctx.fillRect(0, 0, width, height);
// Add some crude organic texture/streaks to the flesh background
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
for (let i = 0; i < 150; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const w = (Math.random() * 60) + 10;
const h = (Math.random() * 3) + 1;
ctx.fillRect(x, y, w, h);
}
// 2. Image Top Layer (The original image that will be torn)
const imgCanvas = document.createElement('canvas');
imgCanvas.width = width;
imgCanvas.height = height;
const imgCtx = imgCanvas.getContext('2d');
// Increase contrast and saturation for an intense, "wild" appearance
imgCtx.filter = 'contrast(1.2) saturate(1.4)';
imgCtx.drawImage(originalImg, 0, 0);
imgCtx.filter = 'none';
// Apply predator-like tint overlay
imgCtx.globalCompositeOperation = 'multiply';
imgCtx.fillStyle = beastColor;
imgCtx.fillRect(0, 0, width, height);
// Add a dark vignette around the edges
const maxDist = Math.max(width, height) * 0.7;
const gradient = imgCtx.createRadialGradient(width / 2, height / 2, maxDist * 0.3, width / 2, height / 2, maxDist);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.8)');
imgCtx.fillStyle = gradient;
imgCtx.fillRect(0, 0, width, height);
// 3. Scratch out cuts (Destination Out rips holes through the image layer)
imgCtx.globalCompositeOperation = 'destination-out';
// Helper function to carve a single sharp claw mark
function drawClaw(startX, startY, length, angle, maxThickness) {
imgCtx.save();
imgCtx.translate(startX, startY);
imgCtx.rotate(angle);
imgCtx.beginPath();
imgCtx.moveTo(0, 0); // Start point (sharp)
// Top edge of the cut
imgCtx.bezierCurveTo(
length * 0.15, -maxThickness * 0.8,
length * 0.4, -maxThickness,
length, 0 // End point (sharp)
);
// Bottom edge of the cut
imgCtx.bezierCurveTo(
length * 0.6, maxThickness * 0.6,
length * 0.2, maxThickness * 0.4,
0, 0
);
// Fill punches the hole
imgCtx.fill();
// Add a bit of rough tearing along the middle
for (let i = 0; i < 5; i++) {
const jtX = length * (0.2 + Math.random() * 0.6);
const jtY = (Math.random() - 0.5) * maxThickness;
imgCtx.beginPath();
imgCtx.arc(jtX, jtY, maxThickness * 0.25, 0, Math.PI * 2);
imgCtx.fill();
}
imgCtx.restore();
}
// Number of overall swipe gestures
const swipeCount = typeof scratches === 'number' ? scratches : parseInt(scratches, 10) || 2;
for (let i = 0; i < swipeCount; i++) {
// Randomize the general placement and angle of the swipe
const swipeX = width * (0.15 + Math.random() * 0.7);
const swipeY = height * (0.15 + Math.random() * 0.7);
const swipeAngle = (Math.PI / 4) + (Math.random() - 0.5) * 1.5; // Roughly diagonal
const swipeLength = maxDist * (0.5 + Math.random() * 0.6);
const maxThickness = swipeLength * (0.015 + Math.random() * 0.01);
// A wild beast usually has 3 or 4 claws
const numClaws = Math.random() > 0.4 ? 4 : 3;
const spread = maxThickness * 3.8;
// Draw the parallel marks made by the claws
for (let c = 0; c < numClaws; c++) {
// Offset logic so marks are spaced apart perpendicular to the swipe angle
const offset = (c - (numClaws - 1) / 2) * spread;
const dx = Math.cos(swipeAngle + Math.PI / 2) * offset;
const dy = Math.sin(swipeAngle + Math.PI / 2) * offset;
// Stagger the starting points slightly to simulate organic hand motion
const stagger = Math.random() * swipeLength * 0.15;
// Translate the central swipe coordinates to the actual claw starting coordinates
const sx = swipeX + dx + Math.cos(swipeAngle) * stagger - (swipeLength / 2 * Math.cos(swipeAngle));
const sy = swipeY + dy + Math.sin(swipeAngle) * stagger - (swipeLength / 2 * Math.sin(swipeAngle));
drawClaw(sx, sy, swipeLength * (0.8 + Math.random() * 0.3), swipeAngle, maxThickness);
}
}
// 4. Composite Layer Assembly
// Draw the torn image with a drop shadow to give the illusion of physical depth exposing the flesh
ctx.shadowColor = 'rgba(0, 0, 0, 0.9)';
ctx.shadowBlur = 12;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.drawImage(imgCanvas, 0, 0);
// Reset shadow properties for further drawing
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Optional: Add ambient blood splatter (drawn overlapping the top layer)
ctx.fillStyle = '#6a0000'; // Thicker, slightly brighter blood tone
for (let i = 0; i < swipeCount * 30; i++) {
const splatX = width * Math.random();
const splatY = height * Math.random();
// Keep radius fairly small relative to the canvas
const radius = Math.random() * (Math.max(width, height) * 0.005);
ctx.beginPath();
ctx.arc(splatX, splatY, radius, 0, Math.PI * 2);
ctx.fill();
}
return canvas;
}
Apply Changes