You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, xOffset = 0.5, yOffset = 0.6, scale = 1.0, opacity = 0.85, vintageEffect = 1, monsterColor = "#151515") {
// Ensure parameters are properly cast in case they come as strings
xOffset = Number(xOffset);
yOffset = Number(yOffset);
scale = Number(scale);
opacity = Number(opacity);
vintageEffect = Number(vintageEffect);
const canvas = document.createElement('canvas');
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Draw original image with or without vintage base filter
if (vintageEffect === 1) {
ctx.filter = 'grayscale(100%) sepia(30%) contrast(1.25) brightness(0.85)';
} else {
ctx.filter = 'none';
}
ctx.drawImage(originalImg, 0, 0, w, h);
ctx.filter = 'none';
// Calculate monster position and size dynamically based on image width
const baseSize = w / 500;
const drawScale = scale * baseSize;
const posX = w * xOffset;
const posY = h * yOffset;
ctx.save();
// Move to requested coordinates and size
ctx.translate(posX, posY);
ctx.scale(drawScale, drawScale);
// Minor blur on the monster to blend it gracefully into the image depth
ctx.filter = vintageEffect === 1 ? 'blur(1.5px)' : 'blur(0.5px)';
ctx.globalAlpha = opacity;
// 1. Water shadow beneath the monster
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.beginPath();
ctx.ellipse(-65, 0, 75, 4, 0, 0, 2 * Math.PI);
ctx.fill();
// 2. Draw distinctive Loch Ness Monster silhouette
// Coordinates assume Nessie is facing right, moving out from the left.
ctx.fillStyle = monsterColor;
ctx.beginPath();
ctx.moveTo(-140, 0);
// Hump 2 (Back)
ctx.bezierCurveTo(-135, -20, -105, -20, -100, 0);
ctx.lineTo(-90, 0);
// Hump 1 (Front)
ctx.bezierCurveTo(-80, -40, -40, -40, -30, 0);
ctx.lineTo(-20, 0);
// Nape (Back of neck curving upwards)
ctx.bezierCurveTo(-10, -60, 10, -100, 20, -130);
// Top of head and snout over-arching forward
ctx.bezierCurveTo(25, -145, 50, -140, 55, -125);
// Under the jaw
ctx.bezierCurveTo(55, -115, 45, -115, 40, -120);
// Throat (Front of neck swooping dramatically down to water base)
ctx.bezierCurveTo(25, -95, 5, -60, 0, 0);
// Return and close base completely
ctx.lineTo(-140, 0);
ctx.closePath();
ctx.fill();
// 3. Water surface ripples / wake
ctx.globalAlpha = Math.min(1, opacity + 0.1);
ctx.strokeStyle = vintageEffect === 1 ? 'rgba(255, 255, 255, 0.3)' : 'rgba(200, 230, 255, 0.4)';
ctx.lineWidth = 1.5 / Math.max(0.1, drawScale);
ctx.beginPath();
// Front wake near neck entry point
ctx.moveTo(0, 1); ctx.quadraticCurveTo(20, 5, 40, 2);
ctx.moveTo(-20, 3); ctx.quadraticCurveTo(5, 8, 30, 4);
// Side wakes along the humps
ctx.moveTo(-150, 1); ctx.lineTo(10, 1);
ctx.moveTo(-140, 3); ctx.lineTo(-10, 3);
ctx.moveTo(-120, 5); ctx.lineTo(-30, 5);
ctx.stroke();
ctx.restore();
// Apply film grain & heavy shadow vignette for that retro "Surgeon's photo" vibe
if (vintageEffect === 1) {
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
// Fast random noise buffer
const noiseGen = new Float32Array(4096);
for(let i = 0; i < 4096; i++) {
noiseGen[i] = (Math.random() - 0.5) * 35;
}
let nIdx = 0;
// Injecting monochromatic noise to emulate old camera grain
for (let i = 0; i < data.length; i += 4) {
const noise = noiseGen[nIdx++];
if(nIdx > 4095) nIdx = 0;
data[i] = Math.min(255, Math.max(0, data[i] + noise));
data[i+1] = Math.min(255, Math.max(0, data[i+1] + noise));
data[i+2] = Math.min(255, Math.max(0, data[i+2] + noise));
}
ctx.putImageData(imgData, 0, 0);
// Apply ominous vignette
const rx = w / 2;
const ry = h / 2;
const gradient = ctx.createRadialGradient(rx, ry, Math.min(rx, ry) * 0.4, rx, ry, Math.max(w, h) * 0.8);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.65)');
ctx.fillStyle = gradient;
ctx.globalCompositeOperation = 'multiply';
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
}
return canvas;
}
Apply Changes