You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
text = "GRAFFITI",
textColor = "#ff0044",
stencilColor = "#111111",
wallColor = "#e6e4e0",
addSplatters = "true"
) {
// 1. Load graffiti font dynamically using FontFace API
try {
const font = new FontFace(
'Permanent Marker',
'url(https://fonts.gstatic.com/s/permanentmarker/v16/FhPTLj_xSlGUuBH62peS3sH1_A.woff2)'
);
await font.load();
document.fonts.add(font);
} catch (err) {
console.warn("Failed to load graffiti font, using fallback.", err);
}
// 2. Setup canvas dimensions based on original image
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
// Apply a slight blur helps reduce noise in the image for a smoother stencil look
ctx.filter = 'blur(1px)';
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// 3. Helper to parse ANY css color into RGB using an offscreen canvas
const colorToRGB = (colorStr) => {
const tempCtx = document.createElement('canvas').getContext('2d');
tempCtx.fillStyle = '#000000';
tempCtx.fillStyle = colorStr;
const c = tempCtx.fillStyle;
if (/^#[0-9A-Fa-f]{6}$/.test(c)) {
return {
r: parseInt(c.substring(1, 3), 16),
g: parseInt(c.substring(3, 5), 16),
b: parseInt(c.substring(5, 7), 16)
};
} else if (c.startsWith('rgba') || c.startsWith('rgb')) {
const arr = c.match(/\d+/g);
return {
r: parseInt(arr[0] || 0, 10),
g: parseInt(arr[1] || 0, 10),
b: parseInt(arr[2] || 0, 10)
};
}
return { r: 0, g: 0, b: 0 };
};
const cDark = colorToRGB(stencilColor);
const cWall = colorToRGB(wallColor);
const cMid = {
r: Math.floor((cDark.r + cWall.r) / 2),
g: Math.floor((cDark.g + cWall.g) / 2),
b: Math.floor((cDark.b + cWall.b) / 2)
};
// 4. Extract pixel data and apply Stencil (Banksy) + Wall Texture effect
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const lum = 0.299 * r + 0.587 * g + 0.114 * b;
if (lum < 100) {
// Dark regions -> solid stencil paint
data[i] = cDark.r;
data[i + 1] = cDark.g;
data[i + 2] = cDark.b;
} else if (lum < 160) {
// Midtone regions -> mix of paint and wall
data[i] = cMid.r;
data[i + 1] = cMid.g;
data[i + 2] = cMid.b;
} else {
// Light regions -> wall background with concrete-like noise texture
const noise = (Math.random() - 0.5) * 20;
data[i] = Math.min(255, Math.max(0, cWall.r + noise));
data[i + 1] = Math.min(255, Math.max(0, cWall.g + noise));
data[i + 2] = Math.min(255, Math.max(0, cWall.b + noise));
}
data[i + 3] = 255; // Set fully opaque
}
ctx.putImageData(imgData, 0, 0);
// 5. Add spray paint splatters and dripping effects
if (String(addSplatters).toLowerCase() === "true") {
const splatCount = Math.floor((width * height) / 12000);
for (let i = 0; i < splatCount; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const radius = Math.random() * (width * 0.005) + 1;
ctx.fillStyle = Math.random() > 0.4 ? stencilColor : textColor;
ctx.globalAlpha = Math.random() * 0.7 + 0.3;
// Paint dot
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
// Dripping effect on some dots
if (Math.random() > 0.8) {
const dripLen = Math.random() * (height * 0.05) + 5;
ctx.beginPath();
ctx.arc(x, y + dripLen, radius * 0.6, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(x - radius * 0.8, y);
ctx.lineTo(x + radius * 0.8, y);
ctx.lineTo(x + radius * 0.5, y + dripLen);
ctx.lineTo(x - radius * 0.5, y + dripLen);
ctx.fill();
}
}
ctx.globalAlpha = 1.0; // Reset alpha
}
// 6. Overlay graffiti stylish text
if (text && text.trim().length > 0) {
ctx.save();
let fontSize = Math.floor(width / text.length * 1.5);
if (fontSize > height * 0.3) fontSize = height * 0.3;
if (fontSize < 30) fontSize = 30;
ctx.font = `${fontSize}px "Permanent Marker", "Brush Script MT", Impact, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Shrink font size if it exceeds canvas width boundaries
while (ctx.measureText(text).width > width * 0.9 && fontSize > 20) {
fontSize -= 2;
ctx.font = `${fontSize}px "Permanent Marker", "Brush Script MT", Impact, sans-serif`;
}
const xPos = width / 2;
const yPos = height * 0.85;
// Shadow / 3D Extrusion Effect
ctx.fillStyle = stencilColor;
const shadowOffset = Math.max(2, Math.floor(fontSize * 0.05));
ctx.fillText(text, xPos + shadowOffset, yPos + shadowOffset);
ctx.fillText(text, xPos + shadowOffset * 1.5, yPos + shadowOffset * 1.5);
// White pop outline
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = Math.max(2, Math.floor(fontSize * 0.05));
ctx.strokeText(text, xPos, yPos);
// Fill color
ctx.fillStyle = textColor;
ctx.fillText(text, xPos, yPos);
ctx.restore();
}
return canvas;
}
Apply Changes