Please bookmark this page to avoid losing your image tool!

Image Sci-Fi Novel Cover Template

(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,
    title = "GALACTIC FRONTIERS",
    author = "COSMO NOVELIST",
    tagline = "A NEW ERA OF DISCOVERY",
    fontName = "Orbitron, sans-serif", // Comma-separated font family list
    titleFontSize = 70, // in pixels
    authorFontSize = 30, // in pixels
    taglineFontSize = 22, // in pixels
    titleColor = "#00E0FF", // Bright cyan
    authorColor = "#E0E0E0", // Light gray
    taglineColor = "#B0B0B0", // Medium gray
    backgroundColor = "#050015", // Very dark deep blue/purple
    starColor = "#FFFFFF", // Color of stars
    imageOpacity = 0.7, // Opacity of the main image, value 0 to 1
    drawStars = "true", // String "true" or "false" to enable/disable starfield
    titleGlowStrength = 15, // Blur radius for title glow
    otherTextGlowStrength = 5 // Blur radius for other texts' glow
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 600;
    canvas.height = 900;

    const RENDER_STARS = String(drawStars).toLowerCase() === 'true';

    // --- Font Loading ---
    // Use a single style element to manage all dynamically loaded Google Fonts @import rules.
    const dynamicFontLoaderStyleId = 'image-utility-dynamic-font-loader-style';
    let styleElement = document.getElementById(dynamicFontLoaderStyleId);
    if (!styleElement) {
        styleElement = document.createElement('style');
        styleElement.id = dynamicFontLoaderStyleId;
        document.head.appendChild(styleElement);
    }

    // Prepare font name for Google Fonts URL (use the first font in the stack)
    const primaryFontFamilyForURL = fontName.split(',')[0].trim().replace(/\s+/g, '+');
    const fontImportRule = `@import url('https://fonts.googleapis.com/css2?family=${primaryFontFamilyForURL}:wght@400;700&display=swap');`;
    
    // Add the @import rule only if it's not already present in the style element
    if (!styleElement.textContent.includes(fontImportRule)) {
        styleElement.textContent += fontImportRule + "\n";
    }
    
    // Format font name for canvas context (e.g., "Font Name, sans-serif")
    // Ensures font names with spaces are quoted if not already.
    const canvasFontNameFormatted = fontName.split(',').map(f => {
        f = f.trim();
        // Add quotes if font name has spaces and is not already quoted
        if (f.includes(' ') && !f.startsWith('"') && !f.startsWith("'")) {
            return `"${f}"`;
        }
        return f;
    }).join(', ');

    try {
        // Wait for the primary font (and its weights) to be ready.
        const primaryFontToLoad = fontName.split(',')[0].trim();
        await Promise.all([
            document.fonts.load(`400 12px "${primaryFontToLoad}"`), // Regular weight
            document.fonts.load(`700 12px "${primaryFontToLoad}"`)  // Bold weight
        ]);
    } catch (err) {
        console.warn(`Font loading for "${fontName.split(',')[0].trim()}" failed or timed out. Browser might use fallbacks. Error: ${err}`);
    }

    // --- Drawing ---

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

    // 2. Stars (if RENDER_STARS is true)
    if (RENDER_STARS) {
        _drawStarsInternal(ctx, 200, starColor, canvas.width, canvas.height); // Draw 200 stars
    }

    // 3. Original Image (scaled to cover canvas)
    if (originalImg && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
        ctx.globalAlpha = Math.min(1, Math.max(0, parseFloat(imageOpacity))); // Clamp opacity
        
        const canvasAspect = canvas.width / canvas.height;
        const imgAspect = originalImg.naturalWidth / originalImg.naturalHeight;
        let sx = 0, sy = 0, sWidth = originalImg.naturalWidth, sHeight = originalImg.naturalHeight;

        if (imgAspect > canvasAspect) { // Image is wider than canvas, crop sides
            sWidth = originalImg.naturalHeight * canvasAspect;
            sx = (originalImg.naturalWidth - sWidth) / 2;
        } else { // Image is taller than canvas (or same aspect), crop top/bottom
            sHeight = originalImg.naturalWidth / canvasAspect;
            sy = (originalImg.naturalHeight - sHeight) / 2;
        }
        ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = 1.0; // Reset global alpha
    } else {
        // Fallback if image is missing or not loaded: dark placeholder
        ctx.fillStyle = 'rgba(10,0,20,0.5)'; 
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        if (typeof originalImg !== 'undefined' && (!originalImg || originalImg.naturalWidth === 0)) {
            ctx.fillStyle = 'rgba(255,255,255,0.7)';
            ctx.textAlign = 'center';
            ctx.font = '16px sans-serif';
            ctx.fillText("Image loading issue", canvas.width/2, canvas.height/2);
        }
    }
    
    // --- Text Elements ---
    // Define font styles using the loaded (or fallback) font
    const titleFullFont = `700 ${titleFontSize}px ${canvasFontNameFormatted}`;
    const authorFullFont = `400 ${authorFontSize}px ${canvasFontNameFormatted}`;
    const taglineFullFont = `400 ${taglineFontSize}px ${canvasFontNameFormatted}`;

    // Define text positions (centered horizontally)
    const centerX = canvas.width / 2;
    const taglineY = canvas.height * 0.12; // Y-center for tagline block
    const titleY = canvas.height * 0.25;   // Y-center for title block
    const authorY = canvas.height * 0.90;  // Y-center for author block
    
    // Draw Tagline
    _drawMultilineTextWithGlowInternal(ctx, tagline, centerX, taglineY, taglineFullFont, taglineColor, taglineFontSize, 1.2, "center", "middle", taglineColor, otherTextGlowStrength);

    // Draw Title
    _drawMultilineTextWithGlowInternal(ctx, title, centerX, titleY, titleFullFont, titleColor, titleFontSize, 1.1, "center", "middle", titleColor, titleGlowStrength);

    // Draw Author
    _drawMultilineTextWithGlowInternal(ctx, author, centerX, authorY, authorFullFont, authorColor, authorFontSize, 1.2, "center", "middle", authorColor, otherTextGlowStrength);

    return canvas;
}

// --- Helper Functions (internal to the scope of this script if copy-pasted) ---

function _drawStarsInternal(ctx, count, starColor, width, height) {
    ctx.save(); // Save current context state
    ctx.fillStyle = starColor;
    for (let i = 0; i < count; i++) {
        const x = Math.random() * width;
        const y = Math.random() * height;
        const radius = Math.random() * 1.2 + 0.3; // Stars of varying small sizes
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
    }
    ctx.restore(); // Restore context state
}

function _drawMultilineTextWithGlowInternal(
    ctx, text, x, y, 
    font, color, fontSize, lineHeightFactor, 
    textAlign, textBaseline // textBaseline for the whole block's vertical alignment
    , glowColor, glowBlur
) {
    if (!text || String(text).trim() === '') return; // Do not draw if text is empty

    ctx.save(); // Save current context state

    ctx.font = font;
    ctx.fillStyle = color;
    ctx.textAlign = textAlign; // Horizontal alignment for each line
    ctx.textBaseline = "middle"; // Set Canvas baseline for individual lines to middle

    const lines = String(text).toUpperCase().split('\n'); // Split text by newline characters, convert to uppercase
    const lineHeight = fontSize * lineHeightFactor;
    
    // Calculate total height of the text block for vertical centering
    // This accounts for the fact that textBaseline="middle" means y is middle of the line box.
    const totalBlockHeight = lines.length * lineHeight;

    let startY;
    if (textBaseline === "middle") { // Center the entire block around y
        startY = y - (totalBlockHeight / 2) + (lineHeight / 2);
    } else if (textBaseline === "top") { // Align top of block with y
        startY = y + (lineHeight / 2);
    } else if (textBaseline === "bottom") { // Align bottom of block with y
        startY = y - totalBlockHeight + (lineHeight / 2);
    } else { // Default: treat y as top of the first line (if textBaseline was 'top')
        startY = y + (lineHeight / 2); // Assuming y is meant for top of block
    }
    
    // Apply glow effect if specified
    if (glowColor && glowBlur > 0) {
        ctx.shadowColor = glowColor;
        ctx.shadowBlur = glowBlur;
        // For a stronger glow, you could draw the text multiple times
        // or use ctx.strokeText then ctx.fillText. This is a simple shadow.
    }

    // Draw each line of text
    for (let i = 0; i < lines.length; i++) {
        ctx.fillText(lines[i], x, startY + (i * lineHeight));
    }
    
    ctx.restore(); // Restore context state (resets shadow, font, fillStyle, etc.)
}

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 Sci-Fi Novel Cover Template is an online tool designed to create stunning book covers for science fiction novels. Users can upload their original images and customize various elements including the title, author name, tagline, font styles, sizes, colors, and background. With support for added visual effects like starfields and text glows, this tool is perfect for authors, publishers, or anyone looking to design eye-catching covers for print or digital distribution.

Leave a Reply

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