Please bookmark this page to avoid losing your image tool!

Image Magic: The Gathering Card Frame Generator

(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,
    cardName = "Mystic Automaton",
    manaCost = "3U",
    cardType = "Artifact Creature - Construct",
    cardText = "When Mystic Automaton enters the battlefield, draw a card.\n{1}{U}: Scry 1.\n\n\"It sees possibilities hidden to mortal eyes.\"",
    powerToughness = "2/3",
    colorIdentity = "M", // W, U, B, R, G, M (multicolor/gold), A (artifact), L (land), C (colorless)
    artistName = "AI Artist"
) {

    // --- Font Loading ---
    // Ensure fonts are loaded. Creates a <link> in <head> if not already present.
    // This is a simplified approach. In a robust app, you'd manage font loading state more carefully.
    if (!document.getElementById('mtg-card-fonts-stylesheet')) {
        const fontLink = document.createElement('link');
        fontLink.id = 'mtg-card-fonts-stylesheet';
        fontLink.rel = 'stylesheet';
        fontLink.href = 'https://fonts.googleapis.com/css2?family=IM+Fell+English+SC&family=Lora:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:wght@700&display=swap';
        
        const p = new Promise((resolve) => {
            fontLink.onload = async () => {
                try {
                    // Wait for specific fonts to be usable
                    await Promise.all([
                        document.fonts.load('1em "IM Fell English SC"'),
                        document.fonts.load('1em "Lora"'),
                        document.fonts.load('bold 1em "Roboto"')
                    ]);
                } catch (e) {
                    console.warn("Failed to explicitly load fonts via document.fonts.load, will rely on CSS: ", e);
                }
                resolve();
            };
            fontLink.onerror = () => {
                console.error("Failed to load Google Fonts CSS for MTG Card.");
                resolve(); // Resolve anyway to proceed with fallback fonts
            };
        });
        document.head.appendChild(fontLink);
        await p;
    }

    const titleFont = '19px "IM Fell English SC", "Times New Roman", serif';
    const typeFont = '15px "IM Fell English SC", "Times New Roman", serif';
    const textFont = '12px "Lora", "Georgia", serif';
    const textFontItalic = 'italic 12px "Lora", "Georgia", serif';
    const artistFont = '10px "Lora", "Georgia", serif';
    const manaSymbolTextFont = 'bold 11px "Roboto", "Arial", sans-serif';
    const ptFont = 'bold 17px "IM Fell English SC", "Times New Roman", serif';


    // --- Canvas Setup ---
    const cardWidth = 375;
    const cardHeight = 525;
    const canvas = document.createElement('canvas');
    canvas.width = cardWidth;
    canvas.height = cardHeight;
    const ctx = canvas.getContext('2d');


    // --- Helper: Rounded Rectangle ---
    function drawRoundedRect(ctx, x, y, width, height, radius) {
        if (typeof radius === 'number') {
            radius = { tl: radius, tr: radius, br: radius, bl: radius };
        } else {
            const defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 };
            for (let side in defaultRadius) {
                radius[side] = radius[side] || defaultRadius[side];
            }
        }
        ctx.beginPath();
        ctx.moveTo(x + radius.tl, y);
        ctx.lineTo(x + width - radius.tr, y);
        ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
        ctx.lineTo(x + width, y + height - radius.br);
        ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
        ctx.lineTo(x + radius.bl, y + height);
        ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
        ctx.lineTo(x, y + radius.tl);
        ctx.quadraticCurveTo(x, y, x + radius.tl, y);
        ctx.closePath();
    }

    // --- Helper: Get Frame Colors ---
    function getFrameColors(id) {
        const sanitizedId = id.toUpperCase().replace(/[^WUBRGALC]/g, '');
        let type = "GOLD"; // Default

        if (sanitizedId.length === 1) {
            if (sanitizedId === "W") type = "WHITE";
            else if (sanitizedId === "U") type = "BLUE";
            else if (sanitizedId === "B") type = "BLACK";
            else if (sanitizedId === "R") type = "RED";
            else if (sanitizedId === "G") type = "GREEN";
            else if (sanitizedId === "A") type = "ARTIFACT";
            else if (sanitizedId === "L") type = "LAND";
            else if (sanitizedId === "C") type = "COLORLESS";
        } else if (sanitizedId.length > 1) {
            if (sanitizedId.includes("A")) type = "ARTIFACT"; // Prioritize artifact if 'A' is present
            else if (sanitizedId.includes("L")) type = "LAND"; // Prioritize land if 'L' is present
            else if (/[WUBRG]/.test(sanitizedId)) type = "GOLD"; // Multicolor
            else type = "COLORLESS"; // If multiple but no colors (e.g. "CC")
        } else { // Empty or invalid
            type = "GOLD";
        }

        const palettes = {
            TEXT_DARK: "#201008", TEXT_LIGHT: "#F0F0E0",
            WHITE: {
                name: "White", frameGradient: ["#F8F6D8", "#F4F2B6", "#EFECC2"], barGradient: ["#DMD8B8", "#C4BF9E", "#AEAA86"],
                barTextColor: "#3A3028", textBoxBG: "rgba(245, 245, 230, 0.9)", textBoxTextColor: "#3A3028",
                ptBoxBG: "#DMD8B8", ptBoxTextColor: "#3A3028", outerBorderColor: "#6D614A", setSymbolFill: "#D0C8B0", setSymbolStroke: "#444"
            },
            BLUE: {
                name: "Blue", frameGradient: ["#AAE0FA", "#72C2F0", "#4AAADE"], barGradient: ["#0D7EBE", "#0A6093", "#084E77"],
                barTextColor: "#F0F0F0", textBoxBG: "rgba(172, 203, 230, 0.9)", textBoxTextColor: "#102030",
                ptBoxBG: "#0D7EBE", ptBoxTextColor: "#F0F0F0", outerBorderColor: "#083A63", setSymbolFill: "#A0C8E0", setSymbolStroke: "#FFF"
            },
            BLACK: {
                name: "Black", frameGradient: ["#8C827B", "#6E6660", "#504A46"], barGradient: ["#4C423D", "#38312E", "#2A2421"],
                barTextColor: "#F0E8E0", textBoxBG: "rgba(100, 90, 85, 0.9)", textBoxTextColor: "#F0E8E0",
                ptBoxBG: "#4C423D", ptBoxTextColor: "#F0E8E0", outerBorderColor: "#1A1310", setSymbolFill: "#777", setSymbolStroke: "#222"
            },
            RED: {
                name: "Red", frameGradient: ["#FAA8A0", "#F57E72", "#F05F50"], barGradient: ["#D3202A", "#A91820", "#88131A"],
                barTextColor: "#F8F0E0", textBoxBG: "rgba(240, 180, 170, 0.9)", textBoxTextColor: "#301008",
                ptBoxBG: "#D3202A", ptBoxTextColor: "#F8F0E0", outerBorderColor: "#601010", setSymbolFill: "#E8A098", setSymbolStroke: "#FFF"
            },
            GREEN: {
                name: "Green", frameGradient: ["#9FD8A0", "#6BB06E", "#47984C"], barGradient: ["#00783C", "#005C2D", "#004020"],
                barTextColor: "#F0F0F0", textBoxBG: "rgba(160, 200, 160, 0.9)", textBoxTextColor: "#082010",
                ptBoxBG: "#00783C", ptBoxTextColor: "#F0F0F0", outerBorderColor: "#003018", setSymbolFill: "#A0D0A0", setSymbolStroke: "#FFF"
            },
            GOLD: {
                name: "Gold", frameGradient: ["#D4AF37", "#C09B2D", "#AB8723"], barGradient: ["#B08D57", "#92703F", "#7A5C34"],
                barTextColor: "#1A1008", textBoxBG: "rgba(240, 225, 190, 0.9)", textBoxTextColor: "#201808",
                ptBoxBG: "#B08D57", ptBoxTextColor: "#1A1008", outerBorderColor: "#5C4033", setSymbolFill: "#D0B070", setSymbolStroke: "#000"
            },
            ARTIFACT: {
                name: "Artifact", frameGradient: ["#B8B8B8", "#9C9C9C", "#828282"], barGradient: ["#707070", "#5A5A5A", "#484848"],
                barTextColor: "#111111", textBoxBG: "rgba(180, 180, 180, 0.9)", textBoxTextColor: "#111111",
                ptBoxBG: "#707070", ptBoxTextColor: "#111111", outerBorderColor: "#333333", setSymbolFill: "#999", setSymbolStroke: "#222"
            },
            LAND: {
                name: "Land", frameGradient: ["#D8C8B0", "#B09A78", "#907E60"], barGradient: ["#8A7050", "#6D5840", "#544430"],
                barTextColor: "#100804", textBoxBG: "rgba(210, 190, 160, 0.9)", textBoxTextColor: "#201008",
                ptBoxBG: "#8A7050", ptBoxTextColor: "#100804", outerBorderColor: "#403020", setSymbolFill: "#B0A088", setSymbolStroke: "#000"
            },
            COLORLESS: { // For cards like Eldrazi that are not artifacts but colorless
                name: "Colorless", frameGradient: ["#D1CDC9", "#B9B4B0", "#A29D98"], barGradient: ["#8B8580", "#746F6A", "#605B57"],
                barTextColor: "#201810", textBoxBG: "rgba(200, 195, 190, 0.9)", textBoxTextColor: "#201810",
                ptBoxBG: "#8B8580", ptBoxTextColor: "#201810", outerBorderColor: "#433E3A", setSymbolFill: "#AAA", setSymbolStroke: "#333"
            }
        };
        return palettes[type] || palettes.GOLD;
    }
    const palette = getFrameColors(colorIdentity);

    // --- Helper: Draw Mana Symbols ---
    function drawManaSymbols(ctx, costStr, startX, startY, symbolSize, spacing) {
        const symbols = [];
        let current = "";
        // Basic parser: "2WU" -> ["2", "W", "U"], "{X}{R}" -> ["X", "R"]
        for (let i = 0; i < costStr.length; i++) {
            let char = costStr[i];
            if (char === '{') {
                if (current) symbols.push(current); current = ""; // Push existing number
            } else if (char === '}') {
                if (current) symbols.push(current.toUpperCase()); current = "";
            } else if (char.match(/[WUBRGXCSTPQ]|\d/i)) { // WUBRG, X, Colorless (C), Snow (S), Tap (T), Phyrexian (P, only letter)
                if (char.match(/\d/) && current.match(/[A-Z]/i)) { // Number after letter means new symbol
                    symbols.push(current); current = "";
                }
                current += char;
            } else { // If space or other, push current symbol
                 if (current) symbols.push(current.toUpperCase()); current = "";
            }
        }
        if (current) symbols.push(current.toUpperCase());
        
        let currentX = startX;
        const radius = symbolSize / 2;

        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        
        for (const symbol of symbols) {
            let bgColor, fgColor, textSymbol = symbol;
            switch (symbol) {
                case "W": bgColor = "#F8F7F0"; fgColor = "#231F20"; break;
                case "U": bgColor = "#0E69AB"; fgColor = "#CFDDF6"; break;
                case "B": bgColor = "#231F20"; fgColor = "#A5A19F"; break;
                case "R": bgColor = "#D3202A"; fgColor = "#FBC5C9"; break;
                case "G": bgColor = "#00733E"; fgColor = "#C4D5CA"; break;
                case "C": bgColor = "#CEC8C2"; fgColor = "#231F20"; break; // Diamond for colorless
                case "X": bgColor = "#CEC8C2"; fgColor = "#231F20"; break;
                case "S": bgColor = "#A0D8F0"; fgColor = "#0E69AB"; textSymbol= "❄"; break; // Snow
                case "T": bgColor = "#B09A78"; fgColor = "#231F20"; textSymbol= "↷"; break; // Tap
                // Simplified Phyrexian: just the letter with a grey circle like numeric
                case "P/W": case "WP": bgColor = "#F8F7F0"; fgColor = "#7C754D"; textSymbol= "Φ"; break;
                case "P/U": case "UP": bgColor = "#0E69AB"; fgColor = "#7EAAC0"; textSymbol= "Φ"; break;
                case "P/B": case "BP": bgColor = "#231F20"; fgColor = "#8E8886"; textSymbol= "Φ"; break;
                case "P/R": case "RP": bgColor = "#D3202A"; fgColor = "#DC8F94"; textSymbol= "Φ"; break;
                case "P/G": case "GP": bgColor = "#00733E"; fgColor = "#85B299"; textSymbol= "Φ"; break;

                default: // Numeric or unknown - treat as generic cost
                    bgColor = "#CEC8C2"; fgColor = "#231F20"; break;
            }
            
            ctx.font = manaSymbolTextFont; // Reset for each symbol, especially after special chars

            // Draw circle
            ctx.beginPath();
            ctx.arc(currentX + radius, startY + radius, radius, 0, Math.PI * 2);
            ctx.fillStyle = bgColor;
            ctx.fill();
            ctx.strokeStyle = "rgba(0,0,0,0.7)";
            ctx.lineWidth = 0.5;
            ctx.stroke();

            // Draw text
            ctx.fillStyle = fgColor;
            if (textSymbol === "Φ") ctx.font = `bold ${symbolSize * 0.8}px "Times New Roman", serif`; // Phyrexian symbol font
            ctx.fillText(textSymbol, currentX + radius, startY + radius +1); // +1 for better vertical centering

            currentX -= (symbolSize + spacing); // Mana symbols are on the right, so decrement X
        }
        return currentX + symbolSize + spacing; // Return the X coord of where the first symbol started (rightmost edge)
    }

    // --- Helper: Draw Wrapped Text ---
    function drawSmartText(ctx, textToDraw, x, y, maxWidth, lineHeight, maxHeight, flavorFont) {
        const paragraphs = textToDraw.split('\n');
        let currentY = y;
        let isFlavor = false;

        for (let i = 0; i < paragraphs.length; i++) {
            const paragraph = paragraphs[i];
            if (currentY + lineHeight > y + maxHeight - (lineHeight/2)) break; // Check height limit before drawing paragraph

            if (paragraph.trim() === "" && i < paragraphs.length - 1 && paragraphs[i+1].trim() !== "") {
                isFlavor = true; // Next non-empty paragraph is flavor text
                currentY += lineHeight * 0.6; // Smaller gap for flavor text
                continue;
            }
            
            ctx.font = isFlavor ? flavorFont : textFont;
            
            let words = paragraph.split(' ');
            let line = '';

            for (let n = 0; n < words.length; n++) {
                let testLine = line + words[n] + ' ';
                let metrics = ctx.measureText(testLine);
                if (metrics.width > maxWidth && n > 0) {
                    if (currentY + lineHeight > y + maxHeight) break;
                    ctx.fillText(line.trim(), x, currentY);
                    currentY += lineHeight;
                    line = words[n] + ' ';
                } else {
                    line = testLine;
                }
            }
            if (currentY + lineHeight <= y + maxHeight + (lineHeight/2)) { // check before final line of paragraph
                 ctx.fillText(line.trim(), x, currentY);
            } else { break; }
            currentY += lineHeight;
            if (isFlavor) currentY += lineHeight * 0.1; // Slightly more spacing after flavor lines
        }
        return currentY; // Return the Y where drawing stopped
    }


    // --- Layout Constants ---
    const outerMargin = 3;
    const innerMargin = 12; // Margin from black border to colored frame
    const totalBorder = outerMargin + innerMargin; // around elements like art box

    const titleBarHeight = 30;
    const titleBarY = outerMargin + innerMargin / 2.5;
    const titleBarPinchedWidth = 20; // How much the sides of title/type bar pinch in

    const artBoxX = totalBorder + 5;
    const artBoxY = titleBarY + titleBarHeight + 3;
    const artBoxWidth = cardWidth - 2 * artBoxX;
    const artBoxHeight = Math.floor(artBoxWidth * 0.75); // Common art aspect ratio

    const typeLineHeight = 25;
    const typeLineY = artBoxY + artBoxHeight + 3;

    const textBoxX = artBoxX;
    const textBoxY = typeLineY + typeLineHeight + 5;
    const textBoxWidth = artBoxWidth;
    
    const ptBoxWidth = 50;
    const ptBoxHeight = 25;
    const ptBoxPadding = 5;
    const ptBoxX = cardWidth - totalBorder - ptBoxWidth - ptBoxPadding;
    const ptBoxY = cardHeight - totalBorder - ptBoxHeight - ptBoxPadding;

    const textBoxHeight = ptBoxY - textBoxY - 18; // Space for artist name below

    const artistNameY = textBoxY + textBoxHeight + 12;
    const textLineHeight = 14;

    // --- Drawing ---

    // 1. Black outer rounded border
    ctx.fillStyle = 'black';
    drawRoundedRect(ctx, outerMargin, outerMargin, cardWidth - 2 * outerMargin, cardHeight - 2 * outerMargin, 10);
    ctx.fill();

    // 2. Main frame color (gradient fill in a slightly smaller rounded rect)
    const frameGrad = ctx.createLinearGradient(0, innerMargin, 0, cardHeight - innerMargin);
    palette.frameGradient.forEach((stop, index) => frameGrad.addColorStop(index / (palette.frameGradient.length -1), stop));
    ctx.fillStyle = frameGrad;
    drawRoundedRect(ctx, innerMargin, innerMargin, cardWidth - 2 * innerMargin, cardHeight - 2 * innerMargin, 8);
    ctx.fill();

    // Common function for title/type bar shapes
    function drawRuleBar(yPos, height, isTitleBar) {
        const barGrad = ctx.createLinearGradient(0, yPos, 0, yPos + height);
        palette.barGradient.forEach((stop, index) => barGrad.addColorStop(index / (palette.barGradient.length -1), stop));
        ctx.fillStyle = barGrad;

        ctx.beginPath();
        ctx.moveTo(innerMargin + (isTitleBar ? 0 : titleBarPinchedWidth /2) , yPos);
        ctx.lineTo(cardWidth - innerMargin - (isTitleBar ? 0 : titleBarPinchedWidth/2), yPos);
        ctx.lineTo(cardWidth - innerMargin - (isTitleBar ? titleBarPinchedWidth : titleBarPinchedWidth) , yPos + height);
        ctx.lineTo(innerMargin + (isTitleBar ? titleBarPinchedWidth : titleBarPinchedWidth) , yPos + height);
        ctx.closePath();
        ctx.fill();

        // Thin border for separation
        ctx.strokeStyle = palette.outerBorderColor;
        ctx.lineWidth = 0.5;
        ctx.stroke();
    }
    
    // 3. Title bar background
    drawRuleBar(titleBarY, titleBarHeight, true);

    // 4. Type line background
    drawRuleBar(typeLineY, typeLineHeight, false);

    // 5. Text box background
    ctx.fillStyle = palette.textBoxBG;
    drawRoundedRect(ctx, textBoxX - 2, textBoxY - 2, textBoxWidth + 4, textBoxHeight + artistNameY - textBoxY -5, 3); // Add little extra border
    ctx.fill();
    // Inner border for text box
    ctx.strokeStyle = palette.outerBorderColor;
    ctx.lineWidth = 0.5;
    drawRoundedRect(ctx, textBoxX -2 , textBoxY -2, textBoxWidth+4, textBoxHeight + artistNameY - textBoxY -5, 3);
    ctx.stroke();


    // 6. Art image
    if (originalImg && originalImg.complete && originalImg.naturalWidth > 0) {
        const imgAspect = originalImg.naturalWidth / originalImg.naturalHeight;
        const boxAspect = artBoxWidth / artBoxHeight;
        let sx=0, sy=0, sWidth=originalImg.naturalWidth, sHeight=originalImg.naturalHeight;

        if (imgAspect > boxAspect) { // Image wider than box: crop sides
            sWidth = originalImg.naturalHeight * boxAspect;
            sx = (originalImg.naturalWidth - sWidth) / 2;
        } else { // Image taller than box: crop top/bottom
            sHeight = originalImg.naturalWidth / boxAspect;
            sy = (originalImg.naturalHeight - sHeight) / 2;
        }
        ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, artBoxX, artBoxY, artBoxWidth, artBoxHeight);
    } else { // Placeholder if image fails
        ctx.fillStyle = '#555';
        ctx.fillRect(artBoxX, artBoxY, artBoxWidth, artBoxHeight);
        ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.fillText("Image Error", artBoxX + artBoxWidth/2, artBoxY + artBoxHeight/2);
    }

    // 7. Border around art image
    ctx.strokeStyle = palette.outerBorderColor;
    ctx.lineWidth = 1;
    ctx.strokeRect(artBoxX, artBoxY, artBoxWidth, artBoxHeight);


    // Text drawing setup
    ctx.fillStyle = palette.barTextColor;
    ctx.textBaseline = 'middle';

    // Shadow for title/type text
    function setTextShadow(color = "rgba(0,0,0,0.5)", blur = 1, offsetX = 0.5, offsetY = 0.5) {
        ctx.shadowColor = color;
        ctx.shadowBlur = blur;
        ctx.shadowOffsetX = offsetX;
        ctx.shadowOffsetY = offsetY;
    }
    function clearTextShadow() {
        ctx.shadowColor = 'transparent';
        ctx.shadowBlur = 0;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
    }
    
    // 8. Card Name text
    ctx.font = titleFont;
    ctx.textAlign = 'left';
    setTextShadow(palette.name === "Black" || palette.name === "Blue" ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.4)");
    ctx.fillText(cardName, innerMargin + titleBarPinchedWidth + 5, titleBarY + titleBarHeight / 2 + 1);
    clearTextShadow();

    // 9. Mana Symbols
    // Mana symbols are drawn from right to left. StartX is the rightmost edge of the mana cost area.
    const manaSymbolSize = 15;
    const manaSymbolSpacing = -2; // Negative for slight overlap
    const manaCostRightMargin = 5;
    drawManaSymbols(ctx, manaCost, cardWidth - innerMargin - manaCostRightMargin - manaSymbolSize, titleBarY + (titleBarHeight - manaSymbolSize) / 2, manaSymbolSize, manaSymbolSpacing);

    // 10. Card Type text
    ctx.font = typeFont;
    ctx.textAlign = 'left';
    setTextShadow(palette.name === "Black" || palette.name === "Blue" ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.4)");
    ctx.fillText(cardType, innerMargin + titleBarPinchedWidth + 5, typeLineY + typeLineHeight / 2 + 1);
    clearTextShadow();

    // Set symbol placeholder
    const setSymbolRadius = typeLineHeight / 3.5;
    const setSymbolX = cardWidth - innerMargin - titleBarPinchedWidth/2 - setSymbolRadius - 10;
    const setSymbolY = typeLineY + typeLineHeight / 2;
    ctx.beginPath();
    ctx.arc(setSymbolX, setSymbolY, setSymbolRadius, 0, Math.PI * 2);
    ctx.fillStyle = palette.setSymbolFill;
    ctx.fill();
    ctx.strokeStyle = palette.setSymbolStroke;
    ctx.lineWidth = 1;
    ctx.stroke();


    // 11. Main Card Text
    ctx.fillStyle = palette.textBoxTextColor;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'top'; // Important for wrapped text
    drawSmartText(ctx, cardText, textBoxX + 5, textBoxY + 5, textBoxWidth - 10, textLineHeight, textBoxHeight - 5, textFontItalic);

    // 12. Artist Name text
    ctx.font = artistFont;
    ctx.fillStyle = palette.textBoxTextColor; // May differ based on palette
    ctx.textAlign = 'left';
    ctx.textBaseline = 'bottom';
    ctx.fillText(`Illus. ${artistName}`, textBoxX + 5, artistNameY -1); // -1 to pull up a bit from very bottom

    // 13. P/T Box & Text (if P/T provided)
    if (powerToughness && powerToughness.trim() !== "") {
        // Background
        ctx.fillStyle = palette.ptBoxBG;
        // PT Box shape is specific. Simplified: rounded rect
        drawRoundedRect(ctx, ptBoxX, ptBoxY, ptBoxWidth, ptBoxHeight, 5);
        ctx.fill();
        ctx.strokeStyle = palette.outerBorderColor;
        ctx.lineWidth = 1;
        ctx.stroke();

        // Text
        ctx.font = ptFont;
        ctx.fillStyle = palette.ptBoxTextColor;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        setTextShadow(palette.name === "Black" || palette.name === "Blue"  || palette.name === "Green" || palette.name === "Red" ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)");
        ctx.fillText(powerToughness, ptBoxX + ptBoxWidth / 2, ptBoxY + ptBoxHeight / 2 + 1);
        clearTextShadow();
    }

    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 Magic: The Gathering Card Frame Generator allows users to create custom Magic: The Gathering card frames using their own images. Users can specify the card name, mana cost, card type, effects text, power and toughness, and the artist’s name. This tool is particularly useful for Magic: The Gathering players and fans who want to design unique cards for fun, custom game nights, or for creating artistic representations of their favorite game characters. The generated cards feature authentic styling and layouts resembling official cards.

Leave a Reply

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