Please bookmark this page to avoid losing your image tool!

Knight Story 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, heroName = "Сэр Ланселот", kingdomName = "Авалон") {
    // Dynamically load a medieval Cyrillic-supporting Google Font
    const fontName = "Cormorant Garamond";
    const fontURL = `https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600;700&display=swap`;
    
    if (!document.querySelector(`link[href="${fontURL}"]`)) {
        const link = document.createElement("link");
        link.rel = "stylesheet";
        link.href = fontURL;
        document.head.appendChild(link);
    }
    
    // Attempt to wait for the font to load, fallback safely if it fails
    try {
        await document.fonts.ready;
        await document.fonts.load(`700 36px "${fontName}"`);
        await document.fonts.load(`600 24px "${fontName}"`);
    } catch (e) {
        console.warn("Custom font loading failed, using fallback web-safe fonts.", e);
    }

    // Set up canvas dimensions (Landscape Story Book style)
    const canvas = document.createElement("canvas");
    canvas.width = 850;
    canvas.height = 550;
    const ctx = canvas.getContext("2d");

    // === 1. Draw Old Parchment Background ===
    // Base parchment color
    ctx.fillStyle = "#ebddc0";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Apply radial gradient vignette to look like aged paper
    const grad = ctx.createRadialGradient(
        canvas.width / 2, canvas.height / 2, 80, 
        canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height)
    );
    grad.addColorStop(0, "rgba(0,0,0,0)");
    grad.addColorStop(1, "rgba(120, 80, 50, 0.6)");
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Draw ornate decorative borders
    ctx.strokeStyle = "#5a3d1c";
    
    // Outer thick line
    ctx.lineWidth = 4;
    ctx.strokeRect(20, 20, canvas.width - 40, canvas.height - 40);
    
    // Inner thin lines
    ctx.lineWidth = 1;
    ctx.strokeRect(28, 28, canvas.width - 56, canvas.height - 56);
    ctx.strokeRect(12, 12, canvas.width - 24, canvas.height - 24);

    // === 2. Draw Knight's Portrait (the provided image) ===
    const imgX = 50;
    const imgY = 70;
    const imgW = 320;
    const imgH = 410;

    // Dark underlay for image
    ctx.fillStyle = "#111";
    ctx.fillRect(imgX, imgY, imgW, imgH);

    // Calculate 'object-fit: cover' behavior for canvas
    const imgRatio = originalImg.width / originalImg.height;
    const boxRatio = imgW / imgH;
    let sX = 0, sY = 0, sW = originalImg.width, sH = originalImg.height;

    if (imgRatio > boxRatio) {
        sW = originalImg.height * boxRatio;
        sX = (originalImg.width - sW) / 2;
    } else {
        sH = originalImg.width / boxRatio;
        sY = (originalImg.height - sH) / 2;
    }

    // Draw the actual image
    ctx.drawImage(originalImg, sX, sY, sW, sH, imgX, imgY, imgW, imgH);

    // Draw frame around the knight's portrait
    ctx.strokeStyle = "#b89947"; // Golden stroke
    ctx.lineWidth = 4;
    ctx.strokeRect(imgX, imgY, imgW, imgH);
    ctx.strokeStyle = "#3d250c"; // Dark iron stroke
    ctx.lineWidth = 2;
    ctx.strokeRect(imgX - 4, imgY - 4, imgW + 8, imgH + 8);
    ctx.strokeRect(imgX + 4, imgY + 4, imgW - 8, imgH - 8);

    // === 3. Generate Knight Story Text ===
    const textX = 410;
    const maxTextWidth = 390;
    const centerRight = textX + maxTextWidth / 2;
    
    // Story Title
    ctx.fillStyle = "#2b1b0b"; 
    ctx.font = `700 36px "${fontName}", Georgia, serif`;
    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    ctx.fillText("История рыцаря", centerRight, 70);

    // Title Decoration (Line and Diamond)
    ctx.strokeStyle = "#5a3d1c";
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(centerRight - 120, 125);
    ctx.lineTo(centerRight + 120, 125);
    ctx.stroke();

    ctx.fillStyle = "#5a3d1c";
    ctx.beginPath();
    ctx.moveTo(centerRight, 120);
    ctx.lineTo(centerRight + 8, 125);
    ctx.lineTo(centerRight, 130);
    ctx.lineTo(centerRight - 8, 125);
    ctx.fill();

    // Story Content
    ctx.font = `600 24px "${fontName}", Georgia, serif`;
    ctx.textAlign = "left";
    
    const storyText = `В славном королевстве ${kingdomName} из поколения в поколение передавалась легенда о величайшем герое. Имя ему было ${heroName}. Говорят, его доспехи сияли ярче полуденного солнца, а меч рассекал даже драконью чешую. Перед вами подлинный портрет рыцаря, чудом сохранившийся в архивах старого замка. Пусть его подвиги живут в веках, вдохновляя новые поколения на великие свершения!`;

    // Routine to word-wrap text
    let textY = 160;
    const words = storyText.split(" ");
    let line = "";
    const lineHeight = 34;

    for (let n = 0; n < words.length; n++) {
        const testLine = line + words[n] + " ";
        const metrics = ctx.measureText(testLine);
        if (metrics.width > maxTextWidth && n > 0) {
            ctx.fillText(line, textX, textY);
            line = words[n] + " ";
            textY += lineHeight;
        } else {
            line = testLine;
        }
    }
    ctx.fillText(line, textX, textY);

    // === 4. Add a Medieval Wax Seal ===
    const sealX = canvas.width - 65;
    const sealY = canvas.height - 65;
    
    // Outer jagged wax
    ctx.fillStyle = "#8a1c1c";
    ctx.beginPath();
    for (let i = 0; i < 20; i++) {
        const angle = (i / 20) * Math.PI * 2;
        const radius = i % 2 === 0 ? 30 : 25;
        if (i === 0) ctx.moveTo(sealX + Math.cos(angle) * radius, sealY + Math.sin(angle) * radius);
        else ctx.lineTo(sealX + Math.cos(angle) * radius, sealY + Math.sin(angle) * radius);
    }
    ctx.closePath();
    ctx.fill();

    // Inner smooth seal depression
    ctx.fillStyle = "#5c1010";
    ctx.beginPath();
    ctx.arc(sealX, sealY, 20, 0, Math.PI * 2);
    ctx.fill();

    // Seal Emblem (Letter)
    ctx.fillStyle = "#d99c9c";
    ctx.font = `700 24px "${fontName}", Georgia, serif`;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    // Using the first letter of the hero's name
    ctx.fillText(heroName.charAt(0).toUpperCase() || "R", sealX, sealY + 2);

    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 Knight Story Generator is an image processing tool that transforms a standard portrait into a stylized medieval storybook page. By combining an uploaded image with custom names for a hero and their kingdom, the tool generates a landscape-oriented composition featuring an aged parchment background, ornate decorative borders, and an elegant portrait frame. The final output includes a thematic legend written in a classic serif font and is finished with a decorative medieval-style wax seal. This tool is ideal for creating personalized fantasy artwork, role-playing game (RPG) character profiles, or creative storytelling assets for social media and gaming communities.

Leave a Reply

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