You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
theme = "dark",
intensity = 0.8,
stampText = "ДЕЛО О СБОРЩИКЕ ДУШ - СЕКРЕТНО",
soulCount = 7
) {
// Ensure numeric values for intensity and soulCount
const params = {
theme: typeof theme === 'string' ? theme.toLowerCase() : "dark",
intensity: parseFloat(intensity) || 0.8,
stampText: String(stampText),
soulCount: parseInt(soulCount, 10) || 7
};
// Create main canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Match dimensions to original image
canvas.width = originalImg.width;
canvas.height = originalImg.height;
// Apply thematic CSS filters to the drawing context for the base image
if (params.theme === "sepia") {
// Old, degraded detective photo look
ctx.filter = "sepia(100%) contrast(140%) brightness(60%) hue-rotate(-10deg)";
} else {
// Dark, noir, eerie look
ctx.filter = "grayscale(100%) contrast(150%) brightness(40%)";
}
// Draw base image
ctx.drawImage(originalImg, 0, 0);
ctx.filter = "none"; // Reset filter for subsequent drawings
// 1. Draw Vignette (Heavy dark edges concentrating vision to the center)
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const maxRadius = Math.max(canvas.width, canvas.height) * 0.8;
const vignetteOffset = 0.3 - (params.intensity * 0.1); // Dynamic inner radius based on intensity
const vignetteGrad = ctx.createRadialGradient(cx, cy, maxRadius * Math.max(0, vignetteOffset), cx, cy, maxRadius);
vignetteGrad.addColorStop(0, "rgba(0, 0, 0, 0)");
vignetteGrad.addColorStop(1, `rgba(0, 0, 0, ${Math.min(1, params.intensity + 0.1)})`);
ctx.fillStyle = vignetteGrad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Add film grain/noise (to make it look like an old file or supernatural interference)
const noiseSize = 150;
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = noiseSize;
noiseCanvas.height = noiseSize;
const nCtx = noiseCanvas.getContext('2d');
const nImgData = nCtx.createImageData(noiseSize, noiseSize);
for (let i = 0; i < nImgData.data.length; i += 4) {
const val = Math.random() * 255;
nImgData.data[i] = val; // R
nImgData.data[i + 1] = val; // G
nImgData.data[i + 2] = val; // B
nImgData.data[i + 3] = 255 * (0.05 + 0.1 * params.intensity); // A
}
nCtx.putImageData(nImgData, 0, 0);
ctx.globalCompositeOperation = "overlay";
ctx.fillStyle = ctx.createPattern(noiseCanvas, "repeat");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over"; // Reset composite operation
// 3. Draw "Souls" (Ethereal glowing orbs floating in the image)
ctx.globalCompositeOperation = "screen";
for (let i = 0; i < params.soulCount; i++) {
// Randomize positions leaning slightly towards the center or bottom
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
// Radius scales with image width
const r = (Math.random() * 0.1 + 0.05) * canvas.width;
const soulGrad = ctx.createRadialGradient(x, y, 0, x, y, r);
soulGrad.addColorStop(0, "rgba(100, 220, 255, 0.7)"); // Bright cyan core
soulGrad.addColorStop(0.2, "rgba(60, 150, 200, 0.4)");
soulGrad.addColorStop(0.6, "rgba(20, 80, 120, 0.1)");
soulGrad.addColorStop(1, "rgba(0, 0, 0, 0)"); // Fade to transparency
ctx.fillStyle = soulGrad;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalCompositeOperation = "source-over"; // Reset composite operation
// 4. Draw Case File Stamp
if (params.stampText) {
ctx.save();
// Calculate font size responsive to image dimensions
const stampSize = Math.max(16, canvas.width * 0.04);
ctx.font = `bold ${stampSize}px "Courier New", Courier, monospace`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// Position at the bottom right/center ish
const px = canvas.width * 0.7;
const py = canvas.height * 0.85;
ctx.translate(px, py);
// Apply slight random rotation for authenticity of a physical stamp
const rotation = -0.05 - (Math.random() * 0.08);
ctx.rotate(rotation);
const lines = params.stampText.split('-'); // Allow splitting into multiple lines with hyphen
const padding = stampSize * 0.6;
// Find widest text line
let maxTextWidth = 0;
lines.forEach(line => {
const w = ctx.measureText(line.trim()).width;
if (w > maxTextWidth) maxTextWidth = w;
});
const boxWidth = maxTextWidth + padding * 2;
const boxHeight = (lines.length * stampSize) + padding * 2;
// Stamp style (faded/distressed bloody red color)
ctx.strokeStyle = "rgba(180, 30, 30, 0.85)";
ctx.fillStyle = "rgba(180, 30, 30, 0.85)";
ctx.lineWidth = Math.max(3, stampSize * 0.15);
// Draw the border rectangle
const startX = -boxWidth / 2;
const startY = -boxHeight / 2;
ctx.strokeRect(startX, startY, boxWidth, boxHeight);
// Double-border effect for classic stamp
const innerPadding = ctx.lineWidth * 2;
ctx.lineWidth = Math.max(1, stampSize * 0.05);
ctx.strokeRect(startX + innerPadding, startY + innerPadding, boxWidth - innerPadding * 2, boxHeight - innerPadding * 2);
// Fill text lines
lines.forEach((line, index) => {
const lineY = startY + padding + (stampSize / 2) + (index * stampSize);
ctx.fillText(line.trim(), 0, lineY);
});
ctx.restore();
}
return canvas;
}
Apply Changes