Please bookmark this page to avoid losing your image tool!

Image Detective 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.
function processImage(
    originalImg,
    title = "THE SHADOW OF DOUBT",
    author = "A. WRITER",
    tagline = "SOME SECRETS ARE BEST LEFT BURIED...",
    titleFontColor = "#FFFFFF",
    authorFontColor = "#E0E0E0",
    taglineFontColor = "#D0D0D0",
    overlayColor = "rgba(0,0,0,0.4)", 
    mainFontFamily = "Impact, 'Arial Black', Gadget, sans-serif",
    taglineFontFamily = "'Palatino Linotype', 'Book Antiqua', Palatino, serif",
    titleFontSize = 70, 
    authorFontSize = 30, 
    taglineFontSize = 20,
    textShadowBlur = 4, 
    textShadowOffsetX = 2, 
    textShadowOffsetY = 2, 
    textShadowColor = "rgba(0,0,0,0.7)"
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const canvasWidth = 600;
    const canvasHeight = 900;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    // 1. Draw background image (cover and crop/scale)
    if (originalImg && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
        const imgAspect = originalImg.naturalWidth / originalImg.naturalHeight;
        const canvasAspect = canvasWidth / canvasHeight;
        let sWidth, sHeight, sx, sy;

        if (imgAspect > canvasAspect) { // Image is wider rel. to canvas: crop image sides
            sHeight = originalImg.naturalHeight;
            sy = 0;
            sWidth = originalImg.naturalHeight * canvasAspect;
            sx = (originalImg.naturalWidth - sWidth) / 2;
        } else { // Image is taller or same aspect rel. to canvas: crop image top/bottom
            sWidth = originalImg.naturalWidth;
            sx = 0;
            sHeight = originalImg.naturalWidth / canvasAspect;
            sy = (originalImg.naturalHeight - sHeight) / 2;
        }
        
        // Ensure sx, sy, sWidth, sHeight are valid and within image bounds
        sx = Math.max(0, Math.round(sx)); // Round to avoid subpixel issues
        sy = Math.max(0, Math.round(sy));
        sWidth = Math.round(sWidth);
        sHeight = Math.round(sHeight);

        // check if calculated source dimensions are valid before drawing
        if (sWidth > 0 && sHeight > 0 && sx + sWidth <= originalImg.naturalWidth && sy + sHeight <= originalImg.naturalHeight) {
             ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, canvasWidth, canvasHeight);
        } else {
            // Fallback: draw image centered, possibly letterboxed (fit, not cover) if cover calc failed
            // Or more simply, draw a placeholder
            ctx.fillStyle = '#333333'; 
            ctx.fillRect(0, 0, canvasWidth, canvasHeight);
            console.warn("Image source rectangle calculation error. Drawing placeholder for image.");
        }

    } else {
        // Fallback for invalid image object (e.g. not loaded or zero dimensions)
        ctx.fillStyle = '#1a1a1a'; // Dark placeholder background
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        ctx.font = "16px Arial";
        ctx.fillStyle = "#777";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Image Not Available", canvasWidth / 2, canvasHeight / 2);
    }


    // 2. Apply overlay
    if (overlayColor && overlayColor.trim() !== "") {
        // Basic validation attempt for color string
        const tempStyle = new Option().style;
        tempStyle.color = overlayColor; // Assign to a temporary style property
        if (tempStyle.color !== '' || overlayColor.toLowerCase() === 'transparent') { // Check if browser could parse it or it's 'transparent'
             ctx.fillStyle = overlayColor;
             ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        } else {
            console.warn("Invalid overlayColor provided, skipping overlay:", overlayColor);
        }
    }
    
    // Text styling helper functions
    function applyTextStyles(fillColor) {
        ctx.fillStyle = fillColor;
        if (textShadowBlur > 0 || textShadowOffsetX !== 0 || textShadowOffsetY !== 0) {
            if (textShadowColor && textShadowColor.trim() !== "") {
                ctx.shadowColor = textShadowColor;
                ctx.shadowBlur = textShadowBlur;
                ctx.shadowOffsetX = textShadowOffsetX;
                ctx.shadowOffsetY = textShadowOffsetY;
            }
        }
    }

    function resetTextStyles() {
        ctx.shadowColor = 'rgba(0,0,0,0)';
        ctx.shadowBlur = 0;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
    }
    
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    // 3. Draw Tagline
    if (tagline && tagline.trim() !== "") {
        applyTextStyles(taglineFontColor);
        ctx.font = `italic ${taglineFontSize}px ${taglineFontFamily}`;
        const taglineY = canvasHeight * 0.08;
        ctx.fillText(tagline.toUpperCase(), canvasWidth / 2, taglineY);
        resetTextStyles();
    }

    // 4. Draw Title
    if (title && title.trim() !== "") {
        applyTextStyles(titleFontColor);
        ctx.font = `bold ${titleFontSize}px ${mainFontFamily}`;
        
        const maxTitleLineWidth = canvasWidth * 0.9;
        const words = title.toUpperCase().split(' ');
        let currentBuildLine = '';
        const titleLinesToDraw = [];
        
        for (let i = 0; i < words.length; i++) {
            const word = words[i];
            // Add space unless it's the first word in currentBuildLine or last word overall
            const testLine = currentBuildLine + (currentBuildLine === '' ? '' : ' ') + word;
            const metrics = ctx.measureText(testLine);

            if (metrics.width > maxTitleLineWidth && currentBuildLine !== '') {
                titleLinesToDraw.push(currentBuildLine);
                currentBuildLine = word;
            } else {
                currentBuildLine = testLine;
            }
        }
        if (currentBuildLine.trim() !== '') {
            titleLinesToDraw.push(currentBuildLine);
        }

        const titleLineHeight = titleFontSize * 1.15; 
        const numTitleLines = titleLinesToDraw.length;
        
        const titleBlockCenterY = canvasHeight * 0.30; 
        
        let currentDisplayTitleLineY = titleBlockCenterY - ((numTitleLines - 1) * titleLineHeight) / 2;

        for (const singleLine of titleLinesToDraw) {
             ctx.fillText(singleLine, canvasWidth / 2, currentDisplayTitleLineY);
             currentDisplayTitleLineY += titleLineHeight;
        }
        resetTextStyles();
    }

    // 5. Draw Author
    if (author && author.trim() !== "") {
        applyTextStyles(authorFontColor);
        ctx.font = `bold ${authorFontSize}px ${mainFontFamily}`;
        const authorY = canvasHeight * 0.92;
        ctx.fillText(author.toUpperCase(), canvasWidth / 2, authorY);
        resetTextStyles();
    }

    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 Detective Novel Cover Template is a tool designed to create custom book covers for detective novels. Users can easily upload their images and personalize their covers by adding a title, author name, and tagline. The tool offers options to customize the font colors, sizes, and styles, as well as the overlay color to enhance the aesthetics of the cover. This tool is ideal for authors, publishers, or anyone looking to design appealing book covers for print or digital distribution.

Leave a Reply

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