Please bookmark this page to avoid losing your image tool!

Image Space Colony Propaganda 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, topText = "JOIN THE COLONY!", bottomText = "YOUR FUTURE AWAITS!", colonyName = "MARS PRIME", primaryColor = "#D92323", secondaryColor = "#0A2F51", textColor = "#F0E6D2", imageFilter = "duotone", overlayPattern = "stars", fontFamily = "Impact, Arial, sans-serif") {

    // Helper to parse hex color string to RGB object
    function hexToRgb(hex) {
        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)
        } : { r: 0, g: 0, b: 0 }; // Default to black if parse fails
    }

    // Helper to draw stroked text
    function drawStrokedText(ctx, text, x, y, fontSize, font, fillColor, strokeColor, strokeWidth) {
        ctx.font = `${fontSize}px ${font}`;
        ctx.fillStyle = fillColor;
        ctx.strokeStyle = strokeColor;
        ctx.lineWidth = strokeWidth;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        if (strokeWidth > 0 && strokeColor && strokeColor !== 'transparent') {
             ctx.strokeText(text, x, y);
        }
        ctx.fillText(text, x, y);
    }
    
    // Helper functions for patterns (designed to be subtle)
    function drawStars(ctx, width, height, count, color) {
        const originalFillStyle = ctx.fillStyle;
        const originalAlpha = ctx.globalAlpha;
        ctx.fillStyle = color;
        ctx.globalAlpha *= 0.6; // Make stars somewhat transparent

        for (let i = 0; i < count; i++) {
            const x = Math.random() * width;
            const y = Math.random() * height;
            const r = Math.random() * 1.5 + 0.5; // Star radius
            ctx.beginPath();
            ctx.arc(x, y, r, 0, Math.PI * 2);
            ctx.fill();
        }
        ctx.fillStyle = originalFillStyle;
        ctx.globalAlpha = originalAlpha;
    }

    function drawGrid(ctx, width, height, spacing, color) {
        const originalStrokeStyle = ctx.strokeStyle;
        const originalLineWidth = ctx.lineWidth;
        const originalAlpha = ctx.globalAlpha;

        ctx.strokeStyle = color;
        ctx.lineWidth = 0.5; // Thin grid lines
        ctx.globalAlpha *= 0.15; // Very subtle grid

        for (let x = 0.5; x < width; x += spacing) { // Offset by 0.5 for crisp lines
            ctx.beginPath();
            ctx.moveTo(x, 0);
            ctx.lineTo(x, height);
            ctx.stroke();
        }
        for (let y = 0.5; y < height; y += spacing) {
            ctx.beginPath();
            ctx.moveTo(0, y);
            ctx.lineTo(width, y);
            ctx.stroke();
        }
        ctx.strokeStyle = originalStrokeStyle;
        ctx.lineWidth = originalLineWidth;
        ctx.globalAlpha = originalAlpha;
    }

    function drawScanlines(ctx, width, height, lineThickness, color) {
        const originalFillStyle = ctx.fillStyle;
        const originalAlpha = ctx.globalAlpha;
        ctx.fillStyle = color;
        ctx.globalAlpha *= 0.1; // Subtle scanlines

        for (let y = 0; y < height; y += lineThickness * 2) {
            ctx.fillRect(0, y, width, lineThickness);
        }
        ctx.fillStyle = originalFillStyle;
        ctx.globalAlpha = originalAlpha;
    }


    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Standard poster dimensions (e.g., 2:3 ratio like 800x1200)
    const posterWidth = 800;
    const posterHeight = 1200;
    canvas.width = posterWidth;
    canvas.height = posterHeight;

    // 1. Background Fill (covers the whole poster canvas)
    // A dark, slightly desaturated secondary color can work well as a base
    const baseBgColor = hexToRgb(secondaryColor);
    ctx.fillStyle = `rgb(${baseBgColor.r*0.5}, ${baseBgColor.g*0.5}, ${baseBgColor.b*0.5})`; // Darker version of secondary
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. Image Processing and Drawing
    const tempImageCanvas = document.createElement('canvas');
    tempImageCanvas.width = originalImg.width;
    tempImageCanvas.height = originalImg.height;
    const tempImageCtx = tempImageCanvas.getContext('2d');
    tempImageCtx.drawImage(originalImg, 0, 0);

    if (imageFilter !== "none") {
        const imageData = tempImageCtx.getImageData(0, 0, tempImageCanvas.width, tempImageCanvas.height);
        const data = imageData.data;
        
        if (imageFilter === "grayscale") {
            for (let i = 0; i < data.length; i += 4) {
                const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
                data[i] = avg; data[i + 1] = avg; data[i + 2] = avg;
            }
            tempImageCtx.putImageData(imageData, 0, 0);
        } else if (imageFilter === "duotone") {
            const rgbPrimaryHighlight = hexToRgb(primaryColor); // Brighter/main color for highlights
            const rgbSecondaryShadow = hexToRgb(secondaryColor);  // Darker/accent color for shadows
            for (let i = 0; i < data.length; i += 4) {
                const r = data[i], g = data[i + 1], b = data[i + 2];
                const luminance = 0.299 * r + 0.587 * g + 0.114 * b; // perceptual luminance
                const t = luminance / 255; // Factor: 0 for black, 1 for white
                
                data[i]   = Math.round(rgbSecondaryShadow.r * (1 - t) + rgbPrimaryHighlight.r * t);
                data[i+1] = Math.round(rgbSecondaryShadow.g * (1 - t) + rgbPrimaryHighlight.g * t);
                data[i+2] = Math.round(rgbSecondaryShadow.b * (1 - t) + rgbPrimaryHighlight.b * t);
            }
            tempImageCtx.putImageData(imageData, 0, 0);
        } else if (imageFilter === "colorize") {
            // Grayscale first
            for (let i = 0; i < data.length; i += 4) {
                const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
                data[i] = avg; data[i + 1] = avg; data[i + 2] = avg;
            }
            tempImageCtx.putImageData(imageData, 0, 0); // Put grayscale data back

            // Then apply color blend
            tempImageCtx.globalCompositeOperation = 'color'; // 'color' blend mode for tinting
            tempImageCtx.fillStyle = primaryColor;
            tempImageCtx.globalAlpha = 0.75; // Apply tint with some transparency
            tempImageCtx.fillRect(0, 0, tempImageCanvas.width, tempImageCanvas.height);
            tempImageCtx.globalAlpha = 1.0; // Reset alpha
            tempImageCtx.globalCompositeOperation = 'source-over'; // Reset blend mode
        }
    }

    // Calculate image destination size to fit originalImg (from tempImageCanvas)
    // into the posterWidth x posterHeight main_canvas, preserving aspect ratio
    let imgDrawX = 0, imgDrawY = 0, imgDrawWidth = canvas.width, imgDrawHeight = canvas.height;
    const imgAspect = tempImageCanvas.width / tempImageCanvas.height;
    const canvasAspect = canvas.width / canvas.height;

    if (imgAspect > canvasAspect) { // Image is wider than canvas (letterbox)
        imgDrawHeight = canvas.width / imgAspect;
        imgDrawY = (canvas.height - imgDrawHeight) / 2;
    } else { // Image is taller or same aspect (pillarbox)
        imgDrawWidth = canvas.height * imgAspect;
        imgDrawX = (canvas.width - imgDrawWidth) / 2;
    }
    ctx.drawImage(tempImageCanvas, imgDrawX, imgDrawY, imgDrawWidth, imgDrawHeight);


    // 3. Overlay Pattern (drawn over the image and background areas)
    if (overlayPattern === "stars") {
        drawStars(ctx, canvas.width, canvas.height, 200, textColor); // More stars
    } else if (overlayPattern === "grid") {
        drawGrid(ctx, canvas.width, canvas.height, 40, 'rgba(200,200,200,1)'); // Adjusted grid
    } else if (overlayPattern === "scanlines") {
        drawScanlines(ctx, canvas.width, canvas.height, 3, 'rgba(255,255,255,1)'); // Lighter scanlines for visibility
    }


    // 4. Decorative Elements (e.g., planet symbol)
    ctx.fillStyle = primaryColor;
    ctx.globalAlpha = 0.75;
    const planetRadius = Math.min(canvas.width, canvas.height) / 12;
    const planetX = canvas.width * 0.82; // Top-rightish
    const planetY = canvas.height * 0.15;
    ctx.beginPath();
    ctx.arc(planetX, planetY, planetRadius, 0, Math.PI * 2);
    ctx.fill();
    // Rings for the planet
    ctx.strokeStyle = textColor;
    ctx.lineWidth = Math.max(1.5, planetRadius / 12);
    ctx.beginPath(); // Inner ring
    ctx.ellipse(planetX, planetY, planetRadius * 1.3, planetRadius * 0.4, Math.PI / -7, 0, Math.PI * 2);
    ctx.stroke();
    ctx.beginPath(); // Outer ring
    ctx.ellipse(planetX, planetY, planetRadius * 1.6, planetRadius * 0.55, Math.PI / -7, 0, Math.PI * 2);
    ctx.stroke();
    ctx.globalAlpha = 1.0;


    // 5. Text Rendering
	const textStrokeColor = 'rgba(0,0,0,0.7)'; // Slightly transparent black for softer stroke
	const textStrokeWidth = 2;

    // Top Text Block
    const topTextSize = Math.max(32, canvas.height / 25);
    const topTextYPos = canvas.height * 0.08;
    ctx.fillStyle = primaryColor;
    // Angled block for top text
    const topBlockHeight = topTextSize * 1.5;
    ctx.beginPath();
    ctx.moveTo(0, topTextYPos - topBlockHeight / 2 - topBlockHeight*0.1);
    ctx.lineTo(canvas.width, topTextYPos - topBlockHeight / 2 + topBlockHeight*0.1);
    ctx.lineTo(canvas.width, topTextYPos + topBlockHeight / 2 + topBlockHeight*0.1);
    ctx.lineTo(0, topTextYPos + topBlockHeight / 2 - topBlockHeight*0.1);
    ctx.closePath();
    ctx.fill();
    drawStrokedText(ctx, topText.toUpperCase(), canvas.width / 2, topTextYPos, topTextSize, fontFamily, textColor, textStrokeColor, textStrokeWidth);

    // Bottom Text Block
    const bottomTextSize = Math.max(30, canvas.height / 28);
    const bottomTextYPos = canvas.height * 0.92;
    ctx.fillStyle = primaryColor;
    // Angled block for bottom text
    const bottomBlockHeight = bottomTextSize * 1.5;
    ctx.beginPath();
    ctx.moveTo(0, bottomTextYPos - bottomBlockHeight / 2 + bottomBlockHeight*0.1);
    ctx.lineTo(canvas.width, bottomTextYPos - bottomBlockHeight / 2 - bottomBlockHeight*0.1);
    ctx.lineTo(canvas.width, bottomTextYPos + bottomBlockHeight / 2 - bottomBlockHeight*0.1);
    ctx.lineTo(0, bottomTextYPos + bottomBlockHeight / 2 + bottomBlockHeight*0.1);
    ctx.closePath();
    ctx.fill();
    drawStrokedText(ctx, bottomText.toUpperCase(), canvas.width / 2, bottomTextYPos, bottomTextSize, fontFamily, textColor, textStrokeColor, textStrokeWidth);
    
    // Colony Name / Main Slogan
    const colonyNameSize = Math.max(60, canvas.height / 15);
    const bannerHeight = colonyNameSize * 1.5; // Adjusted for better proportion
    const bannerY = canvas.height * 0.75; // Centered lower on the poster
    
    ctx.save(); // Save context for banner drawing
    ctx.beginPath();
    // Make banner extend slightly off-canvas for a bolder look if desired
    const bannerBleed = 30;
    ctx.moveTo(-bannerBleed, bannerY - bannerHeight * 0.1); // Top-left point, slight upward angle
    ctx.lineTo(canvas.width + bannerBleed, bannerY + bannerHeight * 0.1); // Top-right point
    ctx.lineTo(canvas.width + bannerBleed, bannerY + bannerHeight - bannerHeight * 0.1); // Bottom-right
    ctx.lineTo(-bannerBleed, bannerY + bannerHeight + bannerHeight * 0.1); // Bottom-left
    ctx.closePath();
    
    ctx.fillStyle = secondaryColor;
    ctx.fill();
    
    ctx.strokeStyle = textColor; // Outline for the banner itself
    ctx.lineWidth = Math.max(2, colonyNameSize / 25);
    ctx.stroke();
    ctx.restore(); // Restore context

    // Calculate center Y of the slightly skewed banner for text placement
    const textBannerCenterY = bannerY + bannerHeight / 2;
    drawStrokedText(ctx, colonyName.toUpperCase(), canvas.width / 2, textBannerCenterY, colonyNameSize, fontFamily, textColor, textStrokeColor, textStrokeWidth + 1);


    // 6. Final Border
    const borderWidth = Math.max(10, canvas.width / 70); // Slightly thicker border
    ctx.strokeStyle = primaryColor;
    ctx.lineWidth = borderWidth;
    ctx.strokeRect(borderWidth / 2, borderWidth / 2, canvas.width - borderWidth, canvas.height - borderWidth);
    
    // Optional inner accent border with the text color
    const accentBorderWidth = Math.max(2, borderWidth / 4);
    ctx.strokeStyle = textColor;
    ctx.lineWidth = accentBorderWidth;
    const accentOffset = borderWidth + accentBorderWidth / 2 + 3; // Gap from main border
    ctx.strokeRect(accentOffset, accentOffset, canvas.width - accentOffset * 2, canvas.height - accentOffset * 2);

    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 Space Colony Propaganda Creator is a tool designed to transform your images into visually striking propaganda posters for a fictional space colony. Users can customize the output by adding engaging text, choosing colors, and applying various artistic filters such as duotone effects. The tool is ideal for creating promotional material for sci-fi themes, educational projects related to space exploration, or simply for fun artistic endeavors. With a focus on aesthetics, this tool facilitates the creation of compelling visual narratives that capture the imagination.

Leave a Reply

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