Please bookmark this page to avoid losing your image tool!

Image Manifesto Page Creator

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
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!

Description

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Other Image Tools:

Image Pirate Ship’s Log Creator

Image Egyptian Hieroglyph Wall Creator

16-bit Game Character Portrait Creator Tool

Image Alchemist’s Formula Page Creator

Paranormal Investigation Image File Creator

Image Of Mayan Stone Tablet Creator

Intergalactic Criminal Database Image Creator

Image Secret Society Membership Card Creator

Image Top Secret Military File Creator

Image Retro Futuristic Patent Drawing Creator

Image Ancient Spell Book Page Creator

Image Aztec Codex Page Creator

Image Cave Painting Frame Creator

Image Cryptozoology Specimen Card Creator

Image Impressionist Painting Frame Creator

Image Paleontological Specimen Label Creator

Image Wizard’s Magical Tome Creator

Image Space Bounty Hunter Poster Creator

Image Government Classified Document Creator

Image Evidence File Creator for Ghost Hunters

Image Multiverse Passport Creator

Celtic Illuminated Manuscript Image Creator

Image Occult Grimoire Page Creator

Image Steampunk Invention Blueprint Creator

Image Cathedral Window Frame Creator

Image 8-Bit Pixel Art Frame Creator

Image Revolutionary Manifesto Page Creator

Mythical Kingdom Image Document Creator

Image Role-Playing Game Character Sheet Creator

Baroque Portrait Frame Creator Tool

Image Royal Decree Proclamation Creator

Image Paranormal Investigation File Creator

Victorian Séance Invitation Image Creator

Image Vintage Apothecary Label Creator

Image Archaeological Discovery Document Creator

Image Witch’s Spell Book Page Creator

See All →