Please bookmark this page to avoid losing your image tool!

Image Revolutionary 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 = "MANIFESTO",
    bodyText = "People of the World, Citizens of the Digital Age!\n\nA spectre haunts our modern society – the spectre of conformity and passive consumption. For too long have our thoughts been shaped by unseen hands, our creativity stifled by mundane demands. We've been lulled into a digital slumber, mistaking endless scrolling for freedom.\n\nNO MORE! The hour of awakening is upon us! We, inheritors of the revolutionary spirit, declare our unwavering resolve to shatter the chains of indifference and reclaim our minds.\n\nLET IT BE KNOWN:\n\n1. That THOUGHT is an unalienable right, its free expression vital. We resist homogenized ideas and censorship.\n\n2. That CREATIVITY is progress. We foster environments for imagination, free from algorithmic tyranny.\n\n3. That KNOWLEDGE must be liberated. We tear down barriers to information, championing open learning.\n\n4. That ACTION, from conviction, drives change. We are active participants, not mere spectators, in forging an equitable future.\n\nThis is not a plea, but a PROCLAMATION. This is not a dream, but a CALL TO ACTION. The ink of this manifesto is our collective will. Let these words resonate, inspiring a new generation to rise, question, build, and dare.\n\nTHE REVOLUTION BEGINS NOW! WITH YOU!",
    pageFontName = "IM Fell English SC",
    pageFontUrl = "https://fonts.gstatic.com/s/imfellenglishsc/v17/aKl_ lymphomasnovaRz1z-3_A0sQ43vezDns0n.woff2",
    fallbackPageFont = "serif",
    pageFontSizeStr = "20",
    titleFontFamily = "Impact, sans-serif",
    titleFontSizeStr = "60",
    mainTextColor = "rgb(40, 20, 20)",
    pageBackgroundColor = "rgb(240, 225, 200)",
    imagePlacement = "top", // "top", "background", "none"
    imageScaleFactor = 0.3,
    imageOpacity = 0.2,
    addSpeckleEffectStr = "true",
    stampText = "APPROVED",
    stampColor = "rgb(160, 0, 0)"
) {

    // Helper function to draw aged specks (optional visual enhancement)
    function _drawAgedSpecks(ctx, width, height, amount = 5000) {
        ctx.save();
        for (let i = 0; i < amount; i++) {
            const x = Math.random() * width;
            const y = Math.random() * height;
            const radius = Math.random() * 1.5;
            const alpha = Math.random() * 0.15 + 0.03;
            ctx.fillStyle = `rgba(0, 0, 0, ${alpha})`;
            ctx.beginPath();
            ctx.arc(x, y, radius, 0, Math.PI * 2);
            ctx.fill();
        }
        ctx.restore();
    }

    // Helper function to load a font dynamically
    async function _loadCustomFont(fontFamily, woff2Url) {
        if (!fontFamily || !woff2Url) return false;
        if (typeof FontFace !== 'function') return false; // FontFace API not supported

        // Check if already available
        // Defensive check for document.fonts and its methods
        if (document.fonts && typeof document.fonts.check === 'function' && document.fonts.check(`12px "${fontFamily}"`)) {
            return true;
        }
        
        const fontFace = new FontFace(fontFamily, `url(${woff2Url}) format('woff2')`);
        try {
            await fontFace.load();
            if (document.fonts && typeof document.fonts.add === 'function') {
                document.fonts.add(fontFace);
            } else { // Fallback or log if document.fonts.add is not available (highly unlikely in modern browsers)
                console.warn("document.fonts.add is not available. Font may not be applied correctly.");
            }
            return true;
        } catch (e) {
            console.error(`Font "${fontFamily}" failed to load from ${woff2Url}:`, e);
            return false;
        }
    }

    // Helper function to wrap text and draw it
    function _wrapTextAndDraw(context, text, x, y, maxWidth, lineHeight) {
        const words = text.split(' ');
        let line = '';
        let currentLineY = y;

        for (let n = 0; n < words.length; n++) {
            const word = words[n];
            const testLine = line === '' ? word : line + ' ' + word;
            const metrics = context.measureText(testLine);

            if (metrics.width > maxWidth && line !== '') {
                context.fillText(line, x, currentLineY);
                line = word;
                currentLineY += lineHeight;
            } else {
                line = testLine;
            }
        }
        context.fillText(line, x, currentLineY);
        return currentLineY + lineHeight; // Return Y for the start of the next line
    }


    // Parse string parameters to their intended types
    const pageFontSize = parseInt(pageFontSizeStr) || 20;
    const titleFontSize = parseInt(titleFontSizeStr) || 60;
    const useSpeckleEffect = addSpeckleEffectStr.toLowerCase() === "true";

    const canvas = document.createElement('canvas');
    canvas.width = 800;
    canvas.height = 1100;
    const ctx = canvas.getContext('2d');

    let actualPageFont = fallbackPageFont;
    if (pageFontName) { // If a custom font name is provided
        let useCustomFont = true;
        if (pageFontUrl) { // If a URL is also provided, try to load it
            useCustomFont = await _loadCustomFont(pageFontName, pageFontUrl);
        }
        // If font loaded successfully or no URL (assume system/previously loaded), construct font string
        if (useCustomFont) {
             // Check if font is loadable/available before using with quotes
            if (document.fonts && typeof document.fonts.check === 'function' && document.fonts.check(`12px "${pageFontName}"`)) {
                 actualPageFont = `"${pageFontName}", ${fallbackPageFont}`;
            } else if (!pageFontUrl) { // No URL provided, but check failed - might be system font not needing quotes or complex name
                 actualPageFont = `${pageFontName}, ${fallbackPageFont}`;
            } else { // Failed to load from URL
                 actualPageFont = fallbackPageFont;
            }
        } else { // Loading explicitly failed or font name wasn't valid to attempt custom
            actualPageFont = fallbackPageFont;
        }
    }


    // 1. Draw Background Color
    ctx.fillStyle = pageBackgroundColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. Add Speckle Effect (if enabled)
    if (useSpeckleEffect) {
        _drawAgedSpecks(ctx, canvas.width, canvas.height);
    }

    // 3. Draw Background Image (if specified)
    if (imagePlacement === "background" && originalImg && originalImg.width > 0 && originalImg.height > 0) {
        ctx.save();
        ctx.globalAlpha = imageOpacity;
        const imgAspect = originalImg.width / originalImg.height;
        const canvasAspect = canvas.width / canvas.height;
        let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;

        if (imgAspect > canvasAspect) { // Image is wider than canvas aspect ratio (letterboxed)
            sWidth = originalImg.height * canvasAspect; // Crop width
            sx = (originalImg.width - sWidth) / 2;
        } else { // Image is taller than canvas aspect ratio (pillarboxed)
            sHeight = originalImg.width / canvasAspect; // Crop height
            sy = (originalImg.height - sHeight) / 2;
        }
        ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
        ctx.restore();
    }

    // Layout variables
    const pageMargin = 60;
    let currentY = pageMargin;

    // 4. Draw Title
    ctx.fillStyle = mainTextColor;
    ctx.font = `bold ${titleFontSize}px ${titleFontFamily}`;
    ctx.textAlign = 'center';
    currentY += titleFontSize * 0.8; // Adjust for text baseline and desired top space
    ctx.fillText(titleText.toUpperCase(), canvas.width / 2, currentY);
    currentY += titleFontSize * 0.5 + 30; // Space after title

    // 5. Draw Image (if placement is "top")
    if (imagePlacement === "top" && originalImg && originalImg.width > 0 && originalImg.height > 0) {
        const maxImgWidth = canvas.width - 2 * pageMargin;
        const maxImgHeight = canvas.height * imageScaleFactor;

        let imgDispWidth = originalImg.width;
        let imgDispHeight = originalImg.height;
        const aspect = imgDispWidth / imgDispHeight;

        // Scale image to fit within maxImgWidth and maxImgHeight while maintaining aspect ratio
        if (imgDispWidth > maxImgWidth) {
            imgDispWidth = maxImgWidth;
            imgDispHeight = imgDispWidth / aspect;
        }
        if (imgDispHeight > maxImgHeight) {
            imgDispHeight = maxImgHeight;
            imgDispWidth = imgDispHeight * aspect;
        }
         // Final check if width became too large due to height restriction
        if (imgDispWidth > maxImgWidth) {
            imgDispWidth = maxImgWidth;
            imgDispHeight = imgDispWidth / aspect;
        }

        const imgX = (canvas.width - imgDispWidth) / 2;
        
        ctx.strokeStyle = mainTextColor;
        ctx.lineWidth = 1.5; // Thinner border
        ctx.strokeRect(imgX - 3, currentY - 3, imgDispWidth + 6, imgDispHeight + 6);
        
        ctx.drawImage(originalImg, imgX, currentY, imgDispWidth, imgDispHeight);
        currentY += imgDispHeight + (imgDispHeight > 0 ? 25 : 0); // Space after image, only if image has height
    }
    
    // 6. Draw Body Text
    ctx.fillStyle = mainTextColor;
    ctx.font = `${pageFontSize}px ${actualPageFont}`;
    ctx.textAlign = 'left';
    
    const bodyLineHeight = pageFontSize * 1.4;
    const textMaxWidth = canvas.width - 2 * pageMargin;
    
    const stampVisualSize = (stampText && stampText.trim() !== "") ? 100 : 0;
    const stampAreaPadding = (stampText && stampText.trim() !== "") ? 20 : 0;
    const maxTextY = canvas.height - pageMargin - (stampVisualSize + stampAreaPadding);

    const paragraphs = bodyText.split('\n');
    for (const paragraph of paragraphs) {
        if (currentY + bodyLineHeight > maxTextY && paragraph.trim() !== "") {
            if (currentY <= maxTextY) { // Check if there's space for ellipsis
                 ctx.fillText("...", pageMargin, currentY);
            }
            break; 
        }

        if (paragraph.trim() === '') {
            if (currentY + bodyLineHeight <= maxTextY) {
                 currentY += bodyLineHeight;
            } else break;
        } else {
            // Call _wrapTextAndDraw. It returns Y for the next line.
            // If this new Y (for NEXT line) is already beyond maxTextY, the current paragraph might have overflowed.
            let paragraphEndY = _wrapTextAndDraw(ctx, paragraph, pageMargin, currentY, textMaxWidth, bodyLineHeight);
            
            // If the content of the paragraph itself extended beyond maxTextY
            if (paragraphEndY - bodyLineHeight > maxTextY) { // Check end of last drawn line
                 // The paragraph overflowed. Further paragraphs will not be drawn.
                 // currentY is set to the start of where the next line would have been (even if it overflowed)
                 currentY = paragraphEndY;
                 break; 
            }
            currentY = paragraphEndY;
            currentY += bodyLineHeight * 0.3; // Small gap after paragraph
        }
    }
    if (paragraphs.length > 0 && bodyText.trim() !== "") currentY -= bodyLineHeight * 0.3; // Remove last extra gap

    // 7. Draw Stamp
    if (stampText && stampText.trim() !== "") {
        ctx.save();
        // Ensure stampVisualSize used here matches the one used for maxTextY calculation
        const actualStampSize = 100; 
        const stampX = canvas.width - pageMargin - actualStampSize;
        const stampY = canvas.height - pageMargin - actualStampSize;
        
        ctx.translate(stampX + actualStampSize / 2, stampY + actualStampSize / 2);
        ctx.rotate(-Math.PI / 18); 

        ctx.strokeStyle = stampColor;
        ctx.lineWidth = 3;
        ctx.strokeRect(-actualStampSize / 2, -actualStampSize / 2, actualStampSize, actualStampSize);

        ctx.fillStyle = stampColor;
        const stampFontSize = Math.max(10, Math.floor(actualStampSize / Math.max(5, stampText.length/2.5))); // Adjust font size dynamically
        ctx.font = `bold ${stampFontSize}px Arial, sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(stampText.toUpperCase(), 0, 0);
        
        ctx.restore();
    }

    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 Revolutionary Manifesto Page Creator is a web-based tool designed to transform your image and text into a visually compelling manifesto page. Users can input an image, a title, and body text to create a formatted page with customizable fonts, colors, and effects. This tool is ideal for activists, designers, and educators looking to create impactful visual statements, posters, or presentations. With features such as an optional speckle effect and the ability to stamp the page, this tool allows for creative expression while conveying powerful messages effectively.

Leave a Reply

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