Skip to content

[Feat] Social Media Image Generator #114

Description

@FumingPower3925

Description

Generate correctly-sized images for every social media platform. Add text with customizable font/size/color, background gradients or solid colors, logo overlays, and patterns. Solves the "what size is an OG image again?" problem — select a platform, customize, and download.

Features

  • Platform presets with correct dimensions:
    Platform Preset Dimensions
    Open Graph Link preview 1200 × 630
    Twitter/X Summary Large Image 1200 × 675
    Twitter/X Summary 800 × 418
    Instagram Post (square) 1080 × 1080
    Instagram Story / Reel 1080 × 1920
    Instagram Landscape 1080 × 566
    LinkedIn Post 1200 × 627
    LinkedIn Cover 1584 × 396
    Facebook Post 1200 × 630
    Facebook Cover 820 × 312
    YouTube Thumbnail 1280 × 720
    YouTube Banner 2560 × 1440
    Pinterest Pin 1000 × 1500
    Discord Embed 1200 × 675
    Twitch Offline Banner 1920 × 1080
    Custom User-defined W × H
  • Text layers:
    • Multiple text blocks, each independently positioned
    • Font family: system-ui, serif, monospace, or Google Fonts (top 20 loaded on demand)
    • Font size: auto-scale or manual (px)
    • Font weight: light, regular, bold, black
    • Color picker with opacity
    • Text alignment: left, center, right
    • Text shadow: color, blur, offset
    • Letter spacing and line height
    • Text transform: uppercase, lowercase, capitalize
    • Max width with word wrap
  • Background options:
    • Solid color
    • Linear gradient (angle + 2–4 stops)
    • Radial gradient
    • Mesh gradient (4-point gradient)
    • Image upload (with fit/fill/cover modes, blur/darken overlay)
    • Patterns: dots, grid, diagonal lines, noise, waves (SVG-based overlays)
  • Logo/Image overlay:
    • Drop a logo PNG/SVG, position anywhere
    • Resize with aspect ratio lock
    • Opacity control
    • Corner presets: top-left, top-right, bottom-left, bottom-right, center
  • Shape elements:
    • Rectangles, circles, lines (decorative elements)
    • Color, opacity, border
  • Templates — Pre-designed layouts:
    • Blog post header (title + subtitle + gradient)
    • Product launch (centered text + badge)
    • Quote card (large text + attribution)
    • Event announcement (date + title + location)
    • Minimalist (text + solid background)
  • Safe zone guides — Show where text might get cropped on different platforms
  • Export: PNG, JPEG (quality slider), WebP

Implementation

Dependencies

Zero — Canvas API for all rendering.

Core Logic

interface ImageConfig {
    width: number;
    height: number;
    background: Background;
    layers: Layer[];
}

type Background =
    | { type: 'solid'; color: string }
    | { type: 'linear-gradient'; angle: number; stops: { color: string; position: number }[] }
    | { type: 'radial-gradient'; stops: { color: string; position: number }[] }
    | { type: 'image'; src: string; fit: 'cover' | 'contain' | 'fill'; overlay?: string }
    | { type: 'pattern'; pattern: 'dots' | 'grid' | 'lines' | 'noise'; color: string; opacity: number };

type Layer =
    | { type: 'text'; text: string; x: number; y: number; font: FontConfig; maxWidth?: number; align: string }
    | { type: 'image'; src: HTMLImageElement; x: number; y: number; width: number; height: number; opacity: number }
    | { type: 'shape'; shape: 'rect' | 'circle' | 'line'; x: number; y: number; width: number; height: number; fill: string; opacity: number };

async function renderImage(config: ImageConfig): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    canvas.width = config.width;
    canvas.height = config.height;
    const ctx = canvas.getContext('2d')!;
    
    // 1. Background
    drawBackground(ctx, config.width, config.height, config.background);
    
    // 2. Pattern overlay (if applicable)
    if (config.background.type === 'pattern') {
        drawPattern(ctx, config.width, config.height, config.background);
    }
    
    // 3. Layers (ordered front-to-back)
    for (const layer of config.layers) {
        ctx.save();
        switch (layer.type) {
            case 'text':
                drawTextLayer(ctx, layer);
                break;
            case 'image':
                ctx.globalAlpha = layer.opacity;
                ctx.drawImage(layer.src, layer.x, layer.y, layer.width, layer.height);
                break;
            case 'shape':
                drawShape(ctx, layer);
                break;
        }
        ctx.restore();
    }
    
    return canvas;
}

function drawTextLayer(ctx: CanvasRenderingContext2D, layer: TextLayer): void {
    ctx.font = `${layer.font.weight} ${layer.font.size}px ${layer.font.family}`;
    ctx.fillStyle = layer.font.color;
    ctx.textAlign = layer.align as CanvasTextAlign;
    ctx.letterSpacing = `${layer.font.letterSpacing ?? 0}px`;
    
    if (layer.font.shadow) {
        ctx.shadowColor = layer.font.shadow.color;
        ctx.shadowBlur = layer.font.shadow.blur;
        ctx.shadowOffsetX = layer.font.shadow.offsetX;
        ctx.shadowOffsetY = layer.font.shadow.offsetY;
    }
    
    // Word wrap
    if (layer.maxWidth) {
        const lines = wrapText(ctx, layer.text, layer.maxWidth);
        lines.forEach((line, i) => {
            ctx.fillText(line, layer.x, layer.y + i * layer.font.size * (layer.font.lineHeight ?? 1.4));
        });
    } else {
        ctx.fillText(layer.text, layer.x, layer.y);
    }
}

function drawPattern(ctx: CanvasRenderingContext2D, w: number, h: number, bg: PatternBackground): void {
    ctx.globalAlpha = bg.opacity;
    ctx.fillStyle = bg.color;
    switch (bg.pattern) {
        case 'dots':
            for (let x = 0; x < w; x += 20) for (let y = 0; y < h; y += 20) {
                ctx.beginPath(); ctx.arc(x, y, 1.5, 0, Math.PI * 2); ctx.fill();
            }
            break;
        case 'grid':
            ctx.lineWidth = 0.5; ctx.strokeStyle = bg.color;
            for (let x = 0; x < w; x += 30) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); }
            for (let y = 0; y < h; y += 30) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); }
            break;
        case 'lines':
            ctx.lineWidth = 0.5; ctx.strokeStyle = bg.color;
            for (let i = -h; i < w + h; i += 15) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i + h, h); ctx.stroke(); }
            break;
    }
    ctx.globalAlpha = 1;
}

Platform Presets

const PRESETS: Record<string, { name: string; width: number; height: number; category: string }> = {
    'og': { name: 'Open Graph', width: 1200, height: 630, category: 'General' },
    'twitter-large': { name: 'Twitter Large Image', width: 1200, height: 675, category: 'Twitter/X' },
    'twitter-summary': { name: 'Twitter Summary', width: 800, height: 418, category: 'Twitter/X' },
    'ig-post': { name: 'Instagram Post', width: 1080, height: 1080, category: 'Instagram' },
    'ig-story': { name: 'Instagram Story', width: 1080, height: 1920, category: 'Instagram' },
    'linkedin-post': { name: 'LinkedIn Post', width: 1200, height: 627, category: 'LinkedIn' },
    'yt-thumb': { name: 'YouTube Thumbnail', width: 1280, height: 720, category: 'YouTube' },
    // ... all presets
};

Privacy

All rendering via Canvas API. Images never leave the browser. Uploaded logos/backgrounds are processed locally only.

UI

  • Preset bar (top): Platform buttons grouped by category, custom size input
  • Canvas (center): Live preview at scaled size, click-to-select layers, drag to reposition
  • Layer panel (right):
    • Add Text / Add Image / Add Shape buttons
    • Layer stack with reorder, visibility toggle, delete
    • Selected layer properties (font, color, position, size, opacity)
  • Background panel: Tabs for Solid / Gradient / Image / Pattern
  • Templates drawer: Pre-designed template thumbnails, click to apply
  • Export bar (bottom): PNG / JPEG (quality) / WebP buttons, dimensions displayed
  • Safe zone toggle: Show/hide cropping guides
  • Responsive — canvas scales down on mobile, panels become tabs

Files to Create/Modify

File Purpose
tools/social-image/package.json Workspace package
tools/social-image/src/renderer.ts Canvas rendering engine, text wrapping, patterns
tools/social-image/src/presets.ts Platform dimension presets
tools/social-image/src/templates.ts Pre-designed template configs
tools/social-image/src/tool.ts Public API: renderImage, export functions
tools/social-image/src/page.ts Canvas editor, layer panel, preset bar
tools/social-image/src/meta.ts ToolMeta
tools/social-image/src/index.ts Exports
tools/social-image/tests/renderer.unit.test.ts Dimension accuracy, text wrapping, pattern generation
tools/social-image/tests/presets.unit.test.ts Preset dimension validation
tools/social-image/tests/page.unit.test.ts DOM rendering tests
src/index.ts Register route
src/index.html Add to tool grid

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions