Please bookmark this page to avoid losing your image tool!

Image Fighting Game Character Select Creator Tool

(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,
    characterName = "PLAYER",
    slotWidth = 120,
    slotHeight = 150,
    borderColor = "#444444",
    selectedBorderColor = "gold",
    borderThickness = 4,
    selected = "false", // Should be string "true" or "false"
    nameplateHeight = 25,
    nameplateColor = "rgba(0,0,0,0.75)",
    nameplateTextColor = "white",
    fontFamily = "'Press Start 2P', monospace",
    fontSize = 8,
    imagePadding = 4,
    textPadding = 4, // Horizontal padding for text in nameplate
    cornerRadius = 6,
    imageObjectFit = "cover", // "cover" or "contain"
    imageBackgroundColor = "rgba(20,20,20,1)"
) {
    const canvas = document.createElement('canvas');
    canvas.width = slotWidth;
    canvas.height = slotHeight;
    const ctx = canvas.getContext('2d');

    // 1. Font Loading (if 'Press Start 2P' is specified)
    let effectiveFontFamily = fontFamily;
    if (fontFamily.toLowerCase().includes("press start 2p")) {
        const fontFaceName = 'Press Start 2P'; // Exact name for FontFace
        let fontAvailable = false;
        if (document.fonts) {
            try {
                fontAvailable = await document.fonts.check(`1px "${fontFaceName}"`);
            } catch (e) {
                // Some browsers might throw error on check (e.g. security restrictions)
                // console.warn(`Font check for "${fontFaceName}" failed: ${e}. Will attempt to load.`);
            }
        }

        if (!fontAvailable) {
            const font = new FontFace(fontFaceName, 'url(https://fonts.gstatic.com/s/pressstart2p/v15/e3t4euO8T-267oIAQAu6PQfoundryitelg.woff2)');
            try {
                await font.load();
                if (document.fonts) {
                    document.fonts.add(font);
                } else {
                    // Fallback for very old browsers or environments without document.fonts
                    // console.warn("document.fonts API not available. Font may not render correctly.");
                }
            } catch (e) {
                // console.warn(`Failed to load '${fontFaceName}' font. System fallbacks will be used. Error: ${e}`);
                // Attempt to remove the failed font from the family string to allow CSS fallbacks
                const regexSafeFaceName = fontFaceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special chars
                const fontPatterns = [
                    new RegExp(`^['"]?${regexSafeFaceName}['"]?,\\s*`, 'i'), // At the beginning, followed by a comma
                    new RegExp(`,\\s*['"]?${regexSafeFaceName}['"]?$`, 'i'), // At the end, preceded by a comma
                    new RegExp(`,\\s*['"]?${regexSafeFaceName}['"]?,\\s*`, 'i'), // In the middle
                    new RegExp(`^['"]?${regexSafeFaceName}['"]?$`, 'i') // Only font specified
                ];
                for (const pattern of fontPatterns) {
                    if (pattern.test(effectiveFontFamily)) {
                        effectiveFontFamily = effectiveFontFamily.replace(pattern, pattern.source.includes(',') ? ',' : ''); // remove with comma or just font
                        break;
                    }
                }
                effectiveFontFamily = effectiveFontFamily.replace(/,\s*,/, ',').replace(/^,|,$/, '').trim();
                if (!effectiveFontFamily) effectiveFontFamily = "monospace"; // Ensure there's always a fallback
            }
        }
    }

    // Helper for creating a rounded rectangle path
    function pathRoundedRectHelper(x, y, w, h, rInput) {
        let r = rInput;
        if (typeof r === 'number') {
            r = {tl: r, tr: r, br: r, bl: r};
        } else { // Ensure all corners have a value
            const defaultRadiusVal = 0;
            r = {
                tl: r.tl || defaultRadiusVal, tr: r.tr || defaultRadiusVal,
                br: r.br || defaultRadiusVal, bl: r.bl || defaultRadiusVal
            };
        }
        // Cap radius to prevent visual glitches
        const minHalfDim = Math.min(w / 2, h / 2);
        r.tl = Math.max(0, Math.min(r.tl, minHalfDim));
        r.tr = Math.max(0, Math.min(r.tr, minHalfDim));
        r.bl = Math.max(0, Math.min(r.bl, minHalfDim));
        r.br = Math.max(0, Math.min(r.br, minHalfDim));

        ctx.beginPath();
        ctx.moveTo(x + r.tl, y);
        ctx.lineTo(x + w - r.tr, y);
        if (r.tr > 0) ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr); else ctx.lineTo(x + w, y);
        ctx.lineTo(x + w, y + h - r.br);
        if (r.br > 0) ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h); else ctx.lineTo(x + w, y+h);
        ctx.lineTo(x + r.bl, y + h);
        if (r.bl > 0) ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl); else ctx.lineTo(x, y+h);
        ctx.lineTo(x, y + r.tl);
        if (r.tl > 0) ctx.quadraticCurveTo(x, y, x + r.tl, y); else ctx.lineTo(x,y);
        ctx.closePath();
    }
    
    const effectiveGlobalRadius = Math.max(0, cornerRadius);

    // Calculate geometry for content area (inside the border)
    const contentX = borderThickness;
    const contentY = borderThickness;
    const contentWidth = Math.max(0, slotWidth - 2 * borderThickness);
    const contentHeight = Math.max(0, slotHeight - 2 * borderThickness);
    const contentCornerRadius = Math.max(0, effectiveGlobalRadius - borderThickness);
    
    // 2. Draw Slot Background (for the content area)
    ctx.fillStyle = imageBackgroundColor;
    if (contentWidth > 0 && contentHeight > 0) {
      if (contentCornerRadius > 0) {
          pathRoundedRectHelper(contentX, contentY, contentWidth, contentHeight, contentCornerRadius);
          ctx.fill();
      } else {
          ctx.fillRect(contentX, contentY, contentWidth, contentHeight);
      }
    }

    // 3. Draw Character Image
    if (originalImg && originalImg.complete && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
        const imgBoxX = contentX + imagePadding;
        const imgBoxY = contentY + imagePadding;
        const imgBoxWidth = Math.max(0, contentWidth - 2 * imagePadding);
        const imgBoxHeight = Math.max(0, contentHeight - nameplateHeight - 2 * imagePadding);

        if (imgBoxWidth > 0 && imgBoxHeight > 0) {
            ctx.save();
            const imgClipRadius = Math.max(0, contentCornerRadius - imagePadding);
            
            ctx.beginPath();
            ctx.moveTo(imgBoxX + imgClipRadius, imgBoxY);
            ctx.lineTo(imgBoxX + imgBoxWidth - imgClipRadius, imgBoxY);
            if (imgClipRadius > 0) ctx.quadraticCurveTo(imgBoxX + imgBoxWidth, imgBoxY, imgBoxX + imgBoxWidth, imgBoxY + imgClipRadius); else ctx.lineTo(imgBoxX + imgBoxWidth, imgBoxY);
            ctx.lineTo(imgBoxX + imgBoxWidth, imgBoxY + imgBoxHeight);
            ctx.lineTo(imgBoxX, imgBoxY + imgBoxHeight);
            ctx.lineTo(imgBoxX, imgBoxY + imgClipRadius);
            if (imgClipRadius > 0) ctx.quadraticCurveTo(imgBoxX, imgBoxY, imgBoxX + imgClipRadius, imgBoxY); else ctx.lineTo(imgBoxX, imgBoxY);
            ctx.closePath();
            ctx.clip();

            const imgNaturalAspect = originalImg.naturalWidth / originalImg.naturalHeight;
            const imgBoxAspect = imgBoxWidth / imgBoxHeight;
            let dWidth, dHeight, dXDraw, dYDraw;

            if (imageObjectFit === "cover") {
                if (imgNaturalAspect > imgBoxAspect) { 
                    dHeight = imgBoxHeight;
                    dWidth = dHeight * imgNaturalAspect;
                } else { 
                    dWidth = imgBoxWidth;
                    dHeight = dWidth / imgNaturalAspect;
                }
            } else { // "contain"
                if (imgNaturalAspect > imgBoxAspect) { 
                    dWidth = imgBoxWidth;
                    dHeight = dWidth / imgNaturalAspect;
                } else { 
                    dHeight = imgBoxHeight;
                    dWidth = dHeight * imgNaturalAspect;
                }
            }
            dXDraw = imgBoxX + (imgBoxWidth - dWidth) / 2;
            dYDraw = imgBoxY + (imgBoxHeight - dHeight) / 2;
            
            ctx.drawImage(originalImg, 0, 0, originalImg.naturalWidth, originalImg.naturalHeight, dXDraw, dYDraw, dWidth, dHeight);
            ctx.restore();
        }
    }

    // 4. Draw Nameplate Background
    const nameplateDrawX = contentX;
    const nameplateDrawY = Math.max(contentY, contentY + contentHeight - nameplateHeight);
    const nameplateDrawWidth = contentWidth;
    const actualNameplateHeight = Math.min(nameplateHeight, contentHeight);


    if (nameplateDrawWidth > 0 && actualNameplateHeight > 0) {
        ctx.fillStyle = nameplateColor;
        ctx.beginPath();
        ctx.moveTo(nameplateDrawX, nameplateDrawY);
        ctx.lineTo(nameplateDrawX + nameplateDrawWidth, nameplateDrawY);
        ctx.lineTo(nameplateDrawX + nameplateDrawWidth, nameplateDrawY + actualNameplateHeight - contentCornerRadius);
        if (contentCornerRadius > 0) ctx.quadraticCurveTo(nameplateDrawX + nameplateDrawWidth, nameplateDrawY + actualNameplateHeight, nameplateDrawX + nameplateDrawWidth - contentCornerRadius, nameplateDrawY + actualNameplateHeight); else ctx.lineTo(nameplateDrawX + nameplateDrawWidth, nameplateDrawY + actualNameplateHeight);
        ctx.lineTo(nameplateDrawX + contentCornerRadius, nameplateDrawY + actualNameplateHeight);
        if (contentCornerRadius > 0) ctx.quadraticCurveTo(nameplateDrawX, nameplateDrawY + actualNameplateHeight, nameplateDrawX, nameplateDrawY + actualNameplateHeight - contentCornerRadius); else ctx.lineTo(nameplateDrawX, nameplateDrawY + actualNameplateHeight);
        ctx.closePath(); 
        ctx.fill();
    }

    // 5. Draw Nameplate Text
    if (nameplateDrawWidth > 0 && actualNameplateHeight > 0 && fontSize > 0) {
        ctx.fillStyle = nameplateTextColor;
        ctx.font = `${fontSize}px ${effectiveFontFamily}`;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        
        const textMaxWidth = Math.max(0, nameplateDrawWidth - 2 * textPadding);
        const textX = nameplateDrawX + nameplateDrawWidth / 2;
        const textY = nameplateDrawY + actualNameplateHeight / 2; // Center vertically in nameplate
        if (textMaxWidth > 0) {
           ctx.fillText(characterName, textX, textY, textMaxWidth);
        }
    }

    // 6. Draw Border
    if (borderThickness > 0) {
        ctx.strokeStyle = (String(selected).toLowerCase() === "true") ? selectedBorderColor : borderColor;
        ctx.lineWidth = borderThickness;

        const borderPathX = borderThickness / 2;
        const borderPathY = borderThickness / 2;
        const borderPathWidth = Math.max(0, slotWidth - borderThickness);
        const borderPathHeight = Math.max(0, slotHeight - borderThickness);
        const borderPathRadius = Math.max(0, effectiveGlobalRadius - borderThickness / 2);

        if (borderPathWidth > 0 && borderPathHeight > 0) {
           if (borderPathRadius > 0) {
               pathRoundedRectHelper(borderPathX, borderPathY, borderPathWidth, borderPathHeight, borderPathRadius);
               ctx.stroke();
           } else {
               ctx.strokeRect(borderPathX, borderPathY, borderPathWidth, borderPathHeight);
           }
        }
    }
    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 Fighting Game Character Select Creator Tool allows users to create customized character selection slots for fighting games. Users can upload images of game characters and add personalized nameplates with character names. The tool includes options for adjusting the dimensions and styling of the slots, such as border color, thickness, and background colors, as well as text formatting for the nameplates. This tool is ideal for game developers, modders, or fans seeking to enhance their fighting game experiences by crafting unique character select screens.

Leave a Reply

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