You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, titleText = 'STRANGER THINGS', subtitleText = 'CHAPTER FIVE', characterCount = 4, particleCount = 300) {
/**
* Dynamically loads the 'Benguiat' font from Google Fonts.
* This is the iconic font used for the Stranger Things title.
*/
const loadStrangerFont = async () => {
const fontName = 'Benguiat';
const styleId = 'google-font-stranger-things';
if (!document.getElementById(styleId)) {
const fontUrl = `https://fonts.googleapis.com/css2?family=${fontName}:wght@700&display=swap`;
const style = document.createElement('style');
style.id = styleId;
style.innerHTML = `@import url('${fontUrl}');`;
document.head.appendChild(style);
}
try {
await document.fonts.load(`700 10px "${fontName}"`);
} catch (e) {
console.error("Font could not be loaded.", e);
// The function will continue with a default font if Benguiat fails to load.
}
return fontName;
};
const fontName = await loadStrangerFont();
// Setup canvas with a 16:9 aspect ratio
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
const rand = (min, max) => Math.random() * (max - min) + min;
// 1. Draw Background and Atmosphere
const drawBackground = () => {
// Dark red/black gradient for an ominous sky
const bgGradient = ctx.createRadialGradient(canvas.width / 2, canvas.height, 0, canvas.width / 2, canvas.height, canvas.width * 0.8);
bgGradient.addColorStop(0, '#1a0000');
bgGradient.addColorStop(1, '#000000');
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
// 2. Draw Mind Flayer (procedurally)
const drawMindFlayer = () => {
ctx.save();
ctx.strokeStyle = 'rgba(20, 0, 0, 0.8)';
ctx.fillStyle = 'rgba(10, 0, 0, 0.7)';
ctx.lineWidth = 2;
const centerX = canvas.width / 2;
const centerY = canvas.height * 0.2;
// Draw multiple legs/tentacles
for (let i = 0; i < 7; i++) {
ctx.beginPath();
ctx.moveTo(centerX + rand(-30, 30), centerY + rand(-30, 30));
const endX = rand(0, canvas.width);
const endY = rand(canvas.height * 0.6, canvas.height * 1.2);
const cp1x = centerX + rand(-250, 250);
const cp1y = centerY + rand(100, 300);
const cp2x = (centerX + endX) / 2 + rand(-200, 200);
const cp2y = (centerY + endY) / 2 + rand(-100, 200);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
ctx.stroke();
// Draw smaller branches off the main tentacles
for (let j = 0; j < 2; j++) {
const branchStartT = rand(0.3, 0.7);
const t = branchStartT; const u = 1 - t;
const tu = u*u; const tt = t*t;
const pX = u*tu*centerX + 3*u*t*cp1x + 3*u*tt*cp2x + t*tt*endX;
const pY = u*tu*centerY + 3*u*t*cp1y + 3*u*tt*cp2y + t*tt*endY;
ctx.moveTo(pX, pY);
ctx.quadraticCurveTo(
pX + rand(-100, 100), pY + rand(50, 100),
pX + rand(-150, 150), pY + rand(100, 200)
);
ctx.stroke();
}
}
ctx.restore();
};
// 3. Draw floating ember-like particles from the Upside Down
const drawParticles = () => {
for (let i = 0; i < particleCount; i++) {
ctx.beginPath();
const x = rand(0, canvas.width);
const y = rand(0, canvas.height);
const size = rand(1, 3);
const opacity = rand(0.1, 0.7);
ctx.fillStyle = `rgba(255, 30, 30, ${opacity})`;
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
};
// 4. Draw character silhouettes
const drawSilhouette = (x, y, scale = 1) => {
const headR = 8 * scale;
const bodyW = 18 * scale;
const bodyH = 25 * scale;
const legH = 22 * scale;
const legSpread = 15 * scale;
const neckY = y - legH - bodyH;
const hipY = y - legH;
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(x, neckY - headR, headR, 0, Math.PI * 2);
ctx.fill();
ctx.fillRect(x - bodyW / 2, neckY, bodyW, bodyH);
ctx.beginPath();
ctx.moveTo(x, hipY);
ctx.lineTo(x - legSpread / 2, y);
ctx.moveTo(x, hipY);
ctx.lineTo(x + legSpread / 2, y);
ctx.strokeStyle = '#000';
ctx.lineWidth = 6 * scale;
ctx.stroke();
};
// 5. Draw the user's image and blend it into the scene
const drawUserImage = () => {
const imgMaxHeight = canvas.height * 0.6;
const imgMaxWidth = canvas.width * 0.4;
let imgHeight = originalImg.height;
let imgWidth = originalImg.width;
const ratio = imgWidth / imgHeight;
if (imgHeight > imgMaxHeight) {
imgHeight = imgMaxHeight;
imgWidth = imgHeight * ratio;
}
if (imgWidth > imgMaxWidth) {
imgWidth = imgMaxWidth;
imgHeight = imgWidth / ratio;
}
const imgX = (canvas.width - imgWidth) / 2;
const imgY = canvas.height - imgHeight;
ctx.drawImage(originalImg, imgX, imgY, imgWidth, imgHeight);
// Apply a red tint to make the image fit the environment
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = 'rgba(180, 20, 20, 0.7)';
ctx.fillRect(imgX, imgY, imgWidth, imgHeight);
ctx.globalCompositeOperation = 'source-over';
return { imgX, imgWidth };
};
// 6. Draw the iconic text
const drawText = () => {
const primaryRed = '#e62e25';
ctx.fillStyle = primaryRed;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Add the signature red glow
ctx.shadowColor = primaryRed;
ctx.shadowBlur = 25;
// Draw title with larger first and last letters
const y = canvas.height * 0.18;
const baseFontSize = canvas.width / 13;
const smallFontSize = baseFontSize * 0.85;
const firstChar = titleText.substring(0, 1).toUpperCase();
const lastChar = titleText.substring(titleText.length - 1).toUpperCase();
const middleText = titleText.substring(1, titleText.length - 1).toUpperCase();
ctx.font = `700 ${baseFontSize}px "${fontName}"`;
const firstCharWidth = ctx.measureText(firstChar).width;
const lastCharWidth = ctx.measureText(lastChar).width;
ctx.font = `700 ${smallFontSize}px "${fontName}"`;
const middleTextWidth = ctx.measureText(middleText).width;
const totalWidth = firstCharWidth + middleTextWidth + lastCharWidth;
let currentX = (canvas.width - totalWidth) / 2;
ctx.font = `700 ${baseFontSize}px "${fontName}"`;
ctx.fillText(firstChar, currentX + firstCharWidth / 2, y);
currentX += firstCharWidth;
ctx.font = `700 ${smallFontSize}px "${fontName}"`;
ctx.fillText(middleText, currentX + middleTextWidth / 2, y);
currentX += middleTextWidth;
ctx.font = `700 ${baseFontSize}px "${fontName}"`;
ctx.fillText(lastChar, currentX + lastCharWidth / 2, y);
// Draw subtitle
const subtitleFontSize = canvas.width / 40;
ctx.font = `700 ${subtitleFontSize}px "${fontName}"`;
const subtitleY = y + baseFontSize * 0.6;
ctx.fillText(subtitleText.toUpperCase(), canvas.width / 2, subtitleY);
// Reset shadow for subsequent drawings
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
};
// --- Main Drawing Execution ---
drawBackground();
drawMindFlayer();
drawParticles();
// Draw a black ground plane
ctx.fillStyle = '#000';
ctx.fillRect(0, canvas.height * 0.95, canvas.width, canvas.height * 0.05);
const { imgX, imgWidth } = drawUserImage();
// Place character silhouettes around the user image
const groundY = canvas.height - (canvas.height * 0.05);
const leftCount = Math.ceil(characterCount / 2);
const rightCount = Math.floor(characterCount / 2);
for (let i = 0; i < leftCount; i++) {
const x = rand(canvas.width * 0.05, imgX - 50);
const scale = rand(0.7, 1.1);
drawSilhouette(x, groundY, scale);
}
for (let i = 0; i < rightCount; i++) {
const x = rand(imgX + imgWidth + 50, canvas.width * 0.95);
const scale = rand(0.7, 1.1);
drawSilhouette(x, groundY, scale);
}
drawText();
return canvas;
}
Apply Changes