Please bookmark this page to avoid losing your image tool!

Image Gothic Horror Novel Cover Designer

(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 = "The Abyssal Crypt",
    author = "Evelyn Blackwood",
    tagline = "Some secrets are buried for a reason...",
    primaryColor = "crimson", // Color for the main title (e.g., "crimson", "#FF0000")
    secondaryColor = "#D3D3D3", // Color for author and tagline (e.g., "lightgray", "#D3D3D3")
    fontName = '"Nosifer", cursive' // Full CSS font-family string for titles/author
) {
    // 1. Determine the primary font to load from Google Fonts (if applicable)
    // Assumes the first font in the fontName string is the one to fetch from Google Fonts.
    const primaryFontForLoading = fontName.split(',')[0].trim().replace(/^"|"$/g, '');

    // 2. Dynamically load the primary font if specified and not already available
    if (primaryFontForLoading) {
        const fontId = `google-font-${primaryFontForLoading.replace(/[^a-z0-9]/gi, '-')}`; // Sanitize ID
        if (!document.getElementById(fontId)) {
            const link = document.createElement('link');
            link.id = fontId;
            link.rel = 'stylesheet';
            link.href = `https://fonts.googleapis.com/css2?family=${primaryFontForLoading.replace(/\s/g, '+')}:wght@400&display=swap`;
            document.head.appendChild(link);

            try {
                if (document.fonts) { // Check if FontFaceSet API is supported
                    await document.fonts.load(`1em "${primaryFontForLoading}"`);
                } else {
                    // Fallback for older browsers: give some time for CSS to load the font.
                    // This is not foolproof but better than nothing.
                    await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
                }
            } catch (err) {
                console.warn(`Font "${primaryFontForLoading}" could not be loaded or loading failed: ${err}. System fallbacks will be used.`);
            }
        } else if (document.fonts && !document.fonts.check(`1em "${primaryFontForLoading}"`)) {
            // Font link tag exists, but font not yet usable, try to await its load
            try {
                await document.fonts.load(`1em "${primaryFontForLoading}"`);
            } catch (err) {
                 console.warn(`Font "${primaryFontForLoading}" (existing link) failed to load: ${err}.`);
            }
        }
    }

    // 3. Create canvas and context
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Standard book cover aspect ratio (e.g., 6x9 inches) -> 600x900 pixels
    const coverWidth = 600;
    const coverHeight = 900;
    canvas.width = coverWidth;
    canvas.height = coverHeight;

    // 4. Draw and process the background image
    ctx.clearRect(0, 0, coverWidth, coverHeight);

    // Calculate aspect ratios and drawing parameters to make the image cover the canvas
    const imgAspectRatio = originalImg.width / originalImg.height;
    const canvasAspectRatio = coverWidth / coverHeight;
    let drawWidth, drawHeight,offsetX, offsetY;

    if (originalImg.width === 0 || originalImg.height === 0) { // Handle invalid image gracefully
        ctx.fillStyle = '#101020'; // Dark fallback background
        ctx.fillRect(0,0,coverWidth, coverHeight);
        drawWidth=0; drawHeight=0; offsetX=0; offsetY=0; // skip image drawing
    } else if (imgAspectRatio > canvasAspectRatio) { 
        drawHeight = coverHeight;
        drawWidth = drawHeight * imgAspectRatio;
        offsetX = (coverWidth - drawWidth) / 2;
        offsetY = 0;
         ctx.drawImage(originalImg, offsetX, offsetY, drawWidth, drawHeight);
    } else { 
        drawWidth = coverWidth;
        drawHeight = drawWidth / imgAspectRatio;
        offsetX = 0;
        offsetY = (coverHeight - drawHeight) / 2;
         ctx.drawImage(originalImg, offsetX, offsetY, drawWidth, drawHeight);
    }


    // Apply a dark, moody overlay to the image
    ctx.fillStyle = "rgba(10, 0, 20, 0.65)"; // Dark purple/indigo overlay
    ctx.fillRect(0, 0, coverWidth, coverHeight);

    // Add a vignette effect (darkened edges)
    const vignetteOuterRadius = Math.sqrt(Math.pow(coverWidth / 2, 2) + Math.pow(coverHeight / 2, 2));
    const gradient = ctx.createRadialGradient(
        coverWidth / 2, coverHeight / 2, coverWidth / 3.5, // Inner circle (lighter area)
        coverWidth / 2, coverHeight / 2, vignetteOuterRadius * 0.90 // Outer circle (darker area)
    );
    gradient.addColorStop(0, 'rgba(0,0,0,0)');
    gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, coverWidth, coverHeight);

    // 5. Text rendering helper function
    const getLinesForText = (text, maxWidth, fontStyle) => {
        // Temporarily set font to measure
        const currentFont = ctx.font;
        ctx.font = fontStyle;

        const words = text.split(' ');
        let line = '';
        const lines = [];
        for (let n = 0; n < words.length; n++) {
            const testLine = line + words[n] + ' ';
            if (ctx.measureText(testLine).width > maxWidth && n > 0) {
                lines.push(line.trim());
                line = words[n] + ' ';
            } else {
                line = testLine;
            }
        }
        lines.push(line.trim());
        ctx.font = currentFont; // Restore font
        return lines;
    };
    
    const drawTextLines = (lines, x, startY, lineHeight, fontStyle, color, shadow, align = 'center', baseline = 'top') => {
        ctx.font = fontStyle;
        ctx.fillStyle = color;
        ctx.textAlign = align;
        ctx.textBaseline = baseline;

        if (shadow) {
            ctx.shadowColor = 'rgba(0,0,0,0.8)';
            ctx.shadowBlur = 8;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
        } else {
            ctx.shadowColor = 'transparent';
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
        }

        let currentY = startY;
        for (const l of lines) {
            ctx.fillText(l, x, currentY);
            currentY += lineHeight;
        }
        ctx.shadowColor = 'transparent'; // Reset shadow
        return currentY; // Return Y position after the last line
    };


    // 6. Define text properties and draw text elements
    const marginX = coverWidth * 0.08; 
    const contentMaxWidth = coverWidth - 2 * marginX;
    let currentYPos = coverHeight * 0.12;

    // Tagline
    if (tagline && tagline.trim() !== "") {
        const taglineText = tagline.toUpperCase();
        const taglineFontSize = Math.floor(coverWidth / 32);
        const isStylisticFont = ["Nosifer", "Creepster", "Metal Mania"].includes(primaryFontForLoading);
        const taglineFontFamily = isStylisticFont ? '"Georgia", Times, serif' : fontName;
        const taglineFontStyle = `italic ${taglineFontSize}px ${taglineFontFamily}`;
        
        const taglineLines = getLinesForText(taglineText, contentMaxWidth * 0.85, taglineFontStyle);
        currentYPos = drawTextLines(taglineLines, coverWidth / 2, currentYPos, taglineFontSize * 1.3, taglineFontStyle, secondaryColor, false);
        currentYPos += coverHeight * 0.04; 
    } else {
        currentYPos = coverHeight * 0.20; 
    }

    // Title
    const titleText = title.toUpperCase();
    const titleFontSize = Math.floor(coverWidth / Math.max(7, titleText.length / 2.5)); // Adjust size based on length
    const titleFontStyle = `bold ${titleFontSize}px ${fontName}`;
    const titleLineHeight = titleFontSize * 1.05; 
    currentYPos = Math.max(currentYPos, coverHeight * (tagline && tagline.trim() !== "" ? 0.18 : 0.22)); 
    
    const titleLines = getLinesForText(titleText, contentMaxWidth, titleFontStyle);
    currentYPos = drawTextLines(titleLines, coverWidth / 2, currentYPos, titleLineHeight, titleFontStyle, primaryColor, true);
    currentYPos += coverHeight * 0.05;

    // Author
    const authorText = author.toUpperCase();
    const authorFontSize = Math.floor(coverWidth / 24);
    const authorFontStyle = `normal ${authorFontSize}px ${fontName}`; // Use 'normal' weight explicitly
    const authorLineHeight = authorFontSize * 1.2;

    const authorLines = getLinesForText(authorText, contentMaxWidth * 0.9, authorFontStyle);
    const authorBlockHeight = authorLines.length * authorLineHeight;
    
    // Try to place author in lower ~15-25% of cover, ensuring it's after title.
    let authorY = Math.max(currentYPos + coverHeight * 0.05, coverHeight * 0.93 - authorBlockHeight);
    authorY = Math.min(authorY, coverHeight * 0.93 - authorBlockHeight); // Ensure it fits from bottom

    drawTextLines(authorLines, coverWidth / 2, authorY, authorLineHeight, authorFontStyle, secondaryColor, true);

    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 Gothic Horror Novel Cover Designer is a specialized tool that allows users to create visually captivating book covers for gothic horror novels. Users can upload an image and customize various elements of the cover, including the book title, author name, tagline, and the color scheme used for the text. The tool also provides options for different font styles to enhance the overall aesthetic. Ideal for authors, publishers, or anyone looking to design an engaging book cover, this tool delivers a professional-quality design that captures the dark and eerie essence of the gothic horror genre.

Leave a Reply

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