Please bookmark this page to avoid losing your image tool!

Victorian Image Calling Card 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,
    nameText = "Your Name",
    fontFamily = "Great Vibes, cursive", // Primary font (e.g., Google Font) first, then generic fallbacks
    fontSize = 30, // In pixels
    textColor = "#38220f", // Dark brown/sepia
    backgroundColor = "#f0e6d6", // Antique white/cream
    borderColor = "#5c3d21", // Medium brown/sepia for main border
    borderWidth = 8, // Outer border thickness
    cardWidth = 450,
    cardHeight = 270,
    imageOvalBorderColor = "#7a5c43", // Slightly lighter/different brown for image oval
    imageOvalBorderWidth = 2,
    cornerRadius = 15 // For the card's rounded corners
) {
    const canvas = document.createElement('canvas');
    canvas.width = cardWidth;
    canvas.height = cardHeight;
    const ctx = canvas.getContext('2d');

    // Helper function for drawing rounded rectangles
    function roundedRectPath(ctx, x, y, width, height, radius) {
        if (width < 2 * radius) radius = width / 2;
        if (height < 2 * radius) radius = height / 2;
        if (radius < 0) radius = 0;
        
        ctx.beginPath();
        ctx.moveTo(x + radius, y);
        ctx.arcTo(x + width, y, x + width, y + height, radius);
        ctx.arcTo(x + width, y + height, x, y + height, radius);
        ctx.arcTo(x, y + height, x, y, radius);
        ctx.arcTo(x, y, x + width, y, radius);
        ctx.closePath();
    }

    // Attempt to load the primary font if it's "Great Vibes" (as an example of CDN font)
    const primaryFontName = fontFamily.split(',')[0].trim().replace(/"/g, '');
    if (primaryFontName === "Great Vibes") {
        try {
            const greatVibesFont = new FontFace('Great Vibes', 'url(https://fonts.gstatic.com/s/greatvibes/v14/RWmMoKWR9v4_unPq0pGRxcpoBGw.woff2)');
            await greatVibesFont.load();
            document.fonts.add(greatVibesFont);
        } catch (e) {
            console.warn("Could not load 'Great Vibes' font. Ensure network access or use a system font.", e);
        }
    }

    // 1. Draw Background (with rounded corners)
    roundedRectPath(ctx, 0, 0, canvas.width, canvas.height, cornerRadius);
    ctx.fillStyle = backgroundColor;
    ctx.fill();

    // 2. Draw Borders
    // Outer thick border
    const outerBorderRadius = Math.max(0, cornerRadius - borderWidth / 2);
    roundedRectPath(ctx, borderWidth / 2, borderWidth / 2, canvas.width - borderWidth, canvas.height - borderWidth, outerBorderRadius);
    ctx.strokeStyle = borderColor;
    ctx.lineWidth = borderWidth;
    ctx.stroke();

    // Inner thin decorative border
    const innerBorderOffset = borderWidth + 3; // Gap from outer border's path start + thin border's own width adjustment
    const innerBorderRadius = Math.max(0, cornerRadius - innerBorderOffset);
    if (canvas.width - 2 * innerBorderOffset > 0 && canvas.height - 2 * innerBorderOffset > 0) {
        roundedRectPath(ctx, innerBorderOffset, innerBorderOffset, canvas.width - 2 * innerBorderOffset, canvas.height - 2 * innerBorderOffset, innerBorderRadius);
        ctx.strokeStyle = borderColor; // Could be a slightly different shade
        ctx.lineWidth = 1;
        ctx.stroke();
    }
    
    // Content padding: defines the main area for image and text
    const contentPadding = innerBorderOffset + 5; // Extra space from the inner line

    // 3. Image Placement (in an oval on the left)
    const imgAreaLeft = contentPadding;
    const imgVisibleAreaWidth = cardWidth * 0.32; // Proportion of card width for image oval
    const imgOvalEffectiveWidth = imgVisibleAreaWidth - 2 * imageOvalBorderWidth;
    const imgOvalEffectiveHeight = cardHeight * 0.70 - 2 * imageOvalBorderWidth; // Proportion of card height

    const ovalCenterX = imgAreaLeft + imgVisibleAreaWidth / 2;
    const ovalCenterY = cardHeight / 2;
    const ovalRadiusX = imgOvalEffectiveWidth / 2;
    const ovalRadiusY = imgOvalEffectiveHeight / 2;

    if (ovalRadiusX > 0 && ovalRadiusY > 0) {
        ctx.save();
        ctx.beginPath();
        ctx.ellipse(ovalCenterX, ovalCenterY, ovalRadiusX, ovalRadiusY, 0, 0, 2 * Math.PI);
        
        // Stroke the
        ctx.strokeStyle = imageOvalBorderColor;
        ctx.lineWidth = imageOvalBorderWidth;
        ctx.stroke();
        
        ctx.clip(); // Clip to the oval path for drawing the image

        // Calculate aspect ratios to draw the image to "cover" the oval
        const imgAspect = originalImg.width / originalImg.height;
        const ovalAspect = ovalRadiusX / ovalRadiusY; // ellipse radii aspect

        let sx, sy, sWidth, sHeight;
        const destWidth = ovalRadiusX * 2;
        const destHeight = ovalRadiusY * 2;

        if (imgAspect > ovalAspect) { // Image is wider than oval area, crop sides
            sHeight = originalImg.height;
            sWidth = sHeight * ovalAspect;
            sx = (originalImg.width - sWidth) / 2;
            sy = 0;
        } else { // Image is taller or same aspect, crop top/bottom
            sWidth = originalImg.width;
            sHeight = sWidth / ovalAspect;
            sx = 0;
            sy = (originalImg.height - sHeight) / 2;
        }
        
        // Ensure sx, sy, sWidth, sHeight are valid
        if (sWidth > 0 && sHeight > 0 && originalImg.width - sx >= sWidth && originalImg.height - sy >= sHeight) {
             ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 
                        ovalCenterX - ovalRadiusX, ovalCenterY - ovalRadiusY, 
                        destWidth, destHeight);
        } else {
            // Fallback: draw entire image centered, scaled to fit (letterbox/pillarbox)
            let fitScale = Math.min(destWidth / originalImg.width, destHeight / originalImg.height);
            let fitWidth = originalImg.width * fitScale;
            let fitHeight = originalImg.height * fitScale;
            ctx.drawImage(originalImg, 
                ovalCenterX - fitWidth/2 , ovalCenterY - fitHeight/2, 
                fitWidth, fitHeight);
        }
        ctx.restore(); // Remove clipping path
    }


    // 4. Text Rendering (Name on the right)
    const textStartX = imgAreaLeft + imgVisibleAreaWidth + cardWidth * 0.04; // Start X for text area with a gap
    const textAreaRight = cardWidth - contentPadding; // End X for text area
    const textAreaWidth = Math.max(0, textAreaRight - textStartX);
    
    const textRenderX = textStartX + textAreaWidth / 2;
    const textRenderY = cardHeight / 2;

    ctx.fillStyle = textColor;
    // Construct font string carefully for proper quoting and fallbacks
    const fontParts = fontFamily.split(',').map(f => f.trim());
    const mainFont = fontParts[0].includes(' ') ? `"${fontParts[0]}"` : fontParts[0];
    const fallbacks = fontParts.slice(1).join(', ');
    const fullFontString = `${fontSize}px ${mainFont}${fallbacks ? ', ' + fallbacks : ''}`;
    
    ctx.font = fullFontString;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    if (textAreaWidth > 0) {
        ctx.fillText(nameText, textRenderX, textRenderY, textAreaWidth); // Max width for fillText
    }

    // 5. Optional Flourishes around the text
    if (textAreaWidth > 0) {
        const textMetrics = ctx.measureText(nameText);
        // Cap flourish length to not exceed text area or a proportion of text width
        const flourishL = Math.min(textMetrics.width * 0.6, textAreaWidth * 0.8); 
        const flourishYOff = fontSize * 0.7; // Vertical offset from text baseline
        const flourishCurveHeight = 5; // How much the flourish curves

        if (flourishL > 10) { // Draw flourishes only if they are of a meaningful length
             ctx.strokeStyle = textColor;
             ctx.lineWidth = 1;

            // Flourish above text
            ctx.beginPath();
            ctx.moveTo(textRenderX - flourishL / 2, textRenderY - flourishYOff);
            ctx.quadraticCurveTo(textRenderX, textRenderY - flourishYOff - flourishCurveHeight, textRenderX + flourishL / 2, textRenderY - flourishYOff);
            ctx.stroke();

            // Flourish below text
            ctx.beginPath();
            ctx.moveTo(textRenderX - flourishL / 2, textRenderY + flourishYOff);
            ctx.quadraticCurveTo(textRenderX, textRenderY + flourishYOff + flourishCurveHeight, textRenderX + flourishL / 2, textRenderY + flourishYOff);
            ctx.stroke();
        }
    }
    
    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 Victorian Image Calling Card Template tool allows users to create personalized calling cards with a vintage aesthetic. Users can input their name, select font styles, and customize colors for the text, background, and borders. A space for a personal image is included, which is displayed in an oval shape on the card. This tool is ideal for creating unique and elegant calling cards for personal use, business networking, special events, or any occasion where a distinctive introduction is desired.

Leave a Reply

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