You can edit the below JavaScript code to customize the image tool.
async function processImage(
originalImg,
titleText = "REVOLUTION NOW!",
bodyText = "We stand at the precipice of monumental change. The archaic structures of the past can no longer contain the aspirations of a people awakened. A new era dawns, not by permission, but by the sheer force of collective will. It is time to dismantle the old, to build the new, and to forge a future where justice, equality, and freedom are not mere words, but the very bedrock of our society. The power is yours. The time is NOW.",
sloganText = "JOIN THE CAUSE!",
primaryColor = "#B71C1C", // A strong, somewhat dark red
secondaryColor = "#000000", // Black
backgroundColor = "#FFFDD0", // Cream
fontName = "Russo One", // Custom font, e.g., from Google Fonts
applyDuotone = 1, // 1 for true (default), 0 for false
duotoneDarkColor = "", // If empty, defaults to secondaryColor
duotoneLightColor = "" // If empty, defaults to primaryColor
) {
// Helper function to parse HEX color strings to RGB objects
function _hexToRgbInternal(hex) {
if (!hex || typeof hex !== 'string') return null;
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// Helper function for text wrapping and calculating its height
function _wrapTextAndMeasureHeightInternal(context, text, x, y, maxWidth, lineHeight, textAlign) {
const words = text.split(' ');
let line = '';
let currentY = y;
context.textAlign = textAlign; // Set alignment for each line
// context.textBaseline is already 'top' from global canvas settings
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = context.measureText(testLine); // Measures width
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
let lineX = x;
if (textAlign === 'center') {
lineX = x + maxWidth / 2;
} else if (textAlign === 'right') {
lineX = x + maxWidth;
}
context.fillText(line.trim(), lineX, currentY);
line = words[n] + ' ';
currentY += lineHeight;
} else {
line = testLine;
}
}
// Print the last line
let lastLineX = x;
if (textAlign === 'center') {
lastLineX = x + maxWidth / 2;
} else if (textAlign === 'right') {
lastLineX = x + maxWidth;
}
context.fillText(line.trim(), lastLineX, currentY);
// Return Y position *after* the last line of text (i.e., where the next element could start)
return currentY + lineHeight;
}
// 1. Define canvas dimensions
const canvasWidth = 800;
const canvasHeight = 1100;
// 2. Dynamically load specified font (e.g., "Russo One" from Google Fonts)
if (fontName === "Russo One") { // Example specific handling for this font
const fontStyleId = `dynamic-font-${fontName.replace(/\s+/g, '-')}`;
if (!document.getElementById(fontStyleId)) {
const style = document.createElement('style');
style.id = fontStyleId;
// Using @import for simplicity with document.fonts.load
style.textContent = `@import url('https://fonts.googleapis.com/css2?family=${fontName.replace(/\s+/g, '+')}:wght@400&display=swap');`;
document.head.appendChild(style);
}
try {
// Wait for the font to be loaded and ready.
// The size/style in load() doesn't strictly matter, just family name.
await document.fonts.load(`12px "${fontName}"`);
console.log(`Font "${fontName}" loaded or already available.`);
} catch (err) {
console.warn(`Could not load font "${fontName}". System fallback will be used. Error:`, err);
}
}
// 3. Create main canvas and context
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top'; // Consistent baseline for all text rendering
// 4. Fill background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 5. Draw Title
ctx.fillStyle = primaryColor;
ctx.textAlign = 'center';
let titleFontSize = 70;
// Fallback fonts added: Impact is good for bold headlines, then generic sans-serif
ctx.font = `bold ${titleFontSize}px "${fontName}", Impact, sans-serif`;
// Auto-adjust font size if title text is too wide for the canvas
const titlePadding = 40; // 20px margin on each side
while (ctx.measureText(titleText).width > canvasWidth - titlePadding && titleFontSize > 20) {
titleFontSize -= 5;
ctx.font = `bold ${titleFontSize}px "${fontName}", Impact, sans-serif`;
}
const titleY = 60; // Top margin for the title
ctx.fillText(titleText, canvasWidth / 2, titleY);
// Estimate bounding box of title for layout of next element
const titleBottomY = titleY + titleFontSize; // Since textBaseline is 'top', Y is top. Height is approx FontSize.
// 6. Process and Draw Image
// Use a temporary canvas for image manipulations like duotone
const tempImageCanvas = document.createElement('canvas');
const tempCtx = tempImageCanvas.getContext('2d');
tempImageCanvas.width = originalImg.naturalWidth || originalImg.width;
tempImageCanvas.height = originalImg.naturalHeight || originalImg.height;
tempCtx.drawImage(originalImg, 0, 0);
if (applyDuotone === 1) {
const actualDuotoneDarkStr = (duotoneDarkColor === "" || !duotoneDarkColor) ? secondaryColor : duotoneDarkColor;
const actualDuotoneLightStr = (duotoneLightColor === "" || !duotoneLightColor) ? primaryColor : duotoneLightColor;
const darkRgb = _hexToRgbInternal(actualDuotoneDarkStr) || { r: 0, g: 0, b: 0 }; // Fallback to black
const lightRgb = _hexToRgbInternal(actualDuotoneLightStr) || _hexToRgbInternal(primaryColor) || { r:183, g:28, b:28 }; // Fallback to primaryColor then hardcoded red
const imageData = tempCtx.getImageData(0, 0, tempImageCanvas.width, tempImageCanvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard luminance calculation for grayscale
const gray = 0.299 * r + 0.000 * g + 0.114 * b; // Corrected: 0.587 for G
// const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Correct luminance
const correctedGray = 0.2126 * r + 0.7152 * g + 0.0722 * b; // ITU-R BT.709 luminance (often preferred)
const normalizedGray = correctedGray / 255;
// Interpolate between dark and light colors based on grayscale value
data[i] = darkRgb.r * (1 - normalizedGray) + lightRgb.r * normalizedGray;
data[i + 1] = darkRgb.g * (1 - normalizedGray) + lightRgb.g * normalizedGray;
data[i + 2] = darkRgb.b * (1 - normalizedGray) + lightRgb.b * normalizedGray;
// Alpha channel (data[i+3]) is preserved
}
tempCtx.putImageData(imageData, 0, 0);
}
// Calculate dimensions and position for the (potentially modified) image
const maxImgWidth = canvasWidth * 0.75;
const maxImgHeight = canvasHeight * 0.4;
let imgWidth = tempImageCanvas.width;
let imgHeight = tempImageCanvas.height;
const aspectRatio = imgWidth / imgHeight;
// Scale image down to fit if it's too large, maintaining aspect ratio
if (imgWidth > maxImgWidth) {
imgWidth = maxImgWidth;
imgHeight = imgWidth / aspectRatio;
}
if (imgHeight > maxImgHeight) { // Check height again if width scaling wasn't enough or it was portrait
imgHeight = maxImgHeight;
imgWidth = imgHeight * aspectRatio;
}
const imgX = (canvasWidth - imgWidth) / 2; // Center the image
const imgY = titleBottomY + 40; // Position image 40px below the title area
ctx.drawImage(tempImageCanvas, imgX, imgY, imgWidth, imgHeight);
// Optional: Add a border around the image
ctx.strokeStyle = secondaryColor;
ctx.lineWidth = 2;
ctx.strokeRect(imgX, imgY, imgWidth, imgHeight);
// 7. Draw Body Text
ctx.fillStyle = secondaryColor;
let bodyFontSize = 22;
// Fallback fonts for body text: Arial is widely available
ctx.font = `${bodyFontSize}px "${fontName}", Arial, sans-serif`;
const bodyTextYStart = imgY + imgHeight + 50; // Position 50px below the image
const bodyTextMaxWidth = canvasWidth * 0.8; // Body text block width
const bodyTextX = (canvasWidth - bodyTextMaxWidth) / 2; // Center the text block
const bodyLineHeight = bodyFontSize * 1.5; // Line spacing
// Draw wrapped body text and get Y position of the content's bottom
const bodyTextEndY = _wrapTextAndMeasureHeightInternal(ctx, bodyText, bodyTextX, bodyTextYStart, bodyTextMaxWidth, bodyLineHeight, 'center');
// 8. Draw Slogan
ctx.fillStyle = primaryColor;
ctx.textAlign = 'center';
let sloganFontSize = 40;
ctx.font = `bold ${sloganFontSize}px "${fontName}", Impact, sans-serif`;
// Auto-adjust slogan font size if text is too wide
while (ctx.measureText(sloganText).width > canvasWidth - titlePadding && sloganFontSize > 15) {
sloganFontSize -= 2;
ctx.font = `bold ${sloganFontSize}px "${fontName}", Impact, sans-serif`;
}
// Calculate slogan Y position
// Place it 30px below body text, but ensure it fits above a 40px bottom margin
let sloganFinalY = bodyTextEndY + 30;
if (sloganFinalY + sloganFontSize > canvasHeight - 40) { // If it overflows
sloganFinalY = canvasHeight - sloganFontSize - 40; // Pin to bottom margin
}
// Ensure slogan is actually below body text if body text was very long / canvas short
sloganFinalY = Math.max(sloganFinalY, bodyTextEndY + 10);
// Only draw if it fits meaningfully
if (sloganFinalY >= bodyTextEndY && sloganFinalY + sloganFontSize <= canvasHeight - 20) {
ctx.fillText(sloganText, canvasWidth / 2, sloganFinalY);
} else {
console.warn("Slogan could not be placed appropriately due to space constraints.");
}
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Manifesto Page Creator is an online tool designed for creating visually compelling manifesto pages. It allows users to upload an image and dynamically generate a canvas that combines the image with customizable text elements. Users can specify a title, body content, and a slogan, as well as select color schemes and fonts to enhance the visual impact of their manifesto. The tool can apply a duotone effect to the uploaded image, giving it a unique stylistic look. This tool is particularly useful for activists, artists, and organizations looking to produce eye-catching digital flyers, social media graphics, or printed materials that convey powerful messages and inspire change.