You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, effectMode = "cross-stitch", parameterValue = "12") {
// This utility provides three interpretations of an "Image of a Grandmother" (Бабушка) filter:
// 1. "cross-stitch" (default): Converts the image into a grandmother's cross-stitch artwork.
// 2. "babushka": Overlays a traditional red floral babushka headscarf framing the main subject.
// 3. "vintage": Applies an old photograph sepia filter with a heavy vignette and film noise.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const mode = effectMode.toLowerCase();
if (mode === "vintage") {
// Appy an old vintage photograph style
ctx.drawImage(originalImg, 0, 0, width, height);
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Sepia & Noise adjustments
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// Sepia Matrix calculations
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;
// Reduce contrast & add random aged film noise
let noise = Math.random() * 20 - 10;
data[i] = Math.min(255, tr * 0.95 + noise);
data[i+1] = Math.min(255, tg * 0.95 + noise);
data[i+2] = Math.min(255, tb * 0.95 + noise);
}
ctx.putImageData(imgData, 0, 0);
// Add typical Grandmother Photo Frame vignette overlay
const grad = ctx.createRadialGradient(
width/2, height/2, Math.min(width, height) * 0.4,
width/2, height/2, Math.max(width, height) * 0.75
);
grad.addColorStop(0, 'rgba(0, 0, 0, 0)');
grad.addColorStop(1, 'rgba(50, 20, 0, 0.85)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
return canvas;
}
else if (mode === "babushka") {
// Wraps the main target inside a traditional Babushka headscarf overlay
ctx.drawImage(originalImg, 0, 0, width, height);
const size = Math.min(width, height);
// Generate a classic red polka-dot/floral pattern programmatically
const patCanvas = document.createElement('canvas');
patCanvas.width = 40;
patCanvas.height = 40;
const pCtx = patCanvas.getContext('2d');
pCtx.fillStyle = '#b01020';
pCtx.fillRect(0, 0, 40, 40);
pCtx.fillStyle = '#ffffff';
pCtx.beginPath();
pCtx.arc(10, 10, 6, 0, Math.PI*2);
pCtx.arc(30, 30, 6, 0, Math.PI*2);
pCtx.fill();
pCtx.fillStyle = '#ffcc00';
pCtx.beginPath();
pCtx.arc(10, 10, 2.5, 0, Math.PI*2);
pCtx.arc(30, 30, 2.5, 0, Math.PI*2);
pCtx.fill();
const scarfPat = ctx.createPattern(patCanvas, 'repeat');
// Target face/subject area
const cx = width / 2;
const cy = height / 2.3;
const rx = size * 0.28;
const ry = size * 0.36;
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = size * 0.05;
ctx.shadowOffsetY = size * 0.02;
// Draw scarf plane with inverse face cutout
ctx.beginPath();
ctx.rect(-width, -height, width * 3, height * 3);
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2, true);
ctx.fillStyle = scarfPat;
ctx.fill();
// Draw the bottom knot of the headscarf
const knotY = cy + ry + size * 0.03;
ctx.beginPath();
ctx.arc(cx, knotY, size * 0.07, 0, Math.PI * 2);
ctx.fill();
// Scarf tails falling off the knot
ctx.beginPath();
ctx.moveTo(cx, knotY);
ctx.lineTo(cx - size * 0.12, knotY + size * 0.2);
ctx.lineTo(cx - size * 0.03, knotY + size * 0.25);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx, knotY);
ctx.lineTo(cx + size * 0.12, knotY + size * 0.2);
ctx.lineTo(cx + size * 0.03, knotY + size * 0.25);
ctx.closePath();
ctx.fill();
ctx.restore();
return canvas;
}
else {
// Cross-Stitch Mode (Default Image Converter operation)
// Convert the image into a Grandmother's Needlepoint / Cross-Stitch Artwork
const stitchSize = Math.max(4, parseInt(parameterValue) || 12);
// Render Aida cloth fabric background
ctx.fillStyle = "#f4f1ea";
ctx.fillRect(0, 0, width, height);
// Draw Aida fabric punch holes
ctx.fillStyle = "#e0dbce";
for (let y = 0; y <= height; y += stitchSize) {
for (let x = 0; x <= width; x += stitchSize) {
ctx.beginPath();
ctx.arc(x, y, stitchSize * 0.12, 0, Math.PI * 2);
ctx.fill();
}
}
const cols = Math.floor(width / stitchSize);
const rows = Math.floor(height / stitchSize);
// Downsample image for cross-stitch color extraction
const offCanvas = document.createElement('canvas');
offCanvas.width = cols;
offCanvas.height = rows;
const offCtx = offCanvas.getContext('2d');
offCtx.drawImage(originalImg, 0, 0, cols, rows);
const imgData = offCtx.getImageData(0, 0, cols, rows).data;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Draw individual cross stitches
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const i = (y * cols + x) * 4;
const r = imgData[i];
const g = imgData[i+1];
const b = imgData[i+2];
const a = imgData[i+3];
if (a < 50) continue;
// Skip stark white pieces to let fabric naturally act as negative space
if (r > 240 && g > 240 && b > 240) continue;
const cx = x * stitchSize + stitchSize / 2;
const cy = y * stitchSize + stitchSize / 2;
const offset = stitchSize * 0.35;
// Stitch dropped shadow
ctx.strokeStyle = `rgba(0, 0, 0, 0.25)`;
ctx.lineWidth = Math.max(1, stitchSize * 0.4);
ctx.beginPath();
ctx.moveTo(cx - offset, cy - offset + 1.5);
ctx.lineTo(cx + offset, cy + offset + 1.5);
ctx.stroke();
const color = `rgb(${r}, ${g}, ${b})`;
// Thread passing beneath (bottom stitch)
ctx.strokeStyle = color;
ctx.lineWidth = Math.max(1, stitchSize * 0.35);
ctx.beginPath();
ctx.moveTo(cx - offset, cy + offset);
ctx.lineTo(cx + offset, cy - offset);
ctx.stroke();
// Thread passing over (top stitch)
ctx.beginPath();
ctx.moveTo(cx - offset, cy - offset);
ctx.lineTo(cx + offset, cy + offset);
ctx.stroke();
// Outer texture highlight on the crest of the top thread
ctx.strokeStyle = `rgba(255, 255, 255, 0.2)`;
ctx.lineWidth = Math.max(1, stitchSize * 0.1);
ctx.beginPath();
ctx.moveTo(cx - offset * 0.6, cy - offset * 0.6);
ctx.lineTo(cx + offset * 0.6, cy + offset * 0.6);
ctx.stroke();
}
}
return canvas;
}
}
Apply Changes