Please bookmark this page to avoid losing your image tool!

Viking Runestone Frame Image Creator

(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) {
    // Using a static-like property on the function object to cache the font loading promise
    // This ensures the font is fetched and processed only once, even if processImage is called multiple times.
    if (typeof processImage._fontLoaderPromise === 'undefined') {
        processImage._fontLoaderPromise = null;
    }

    async function _ensureFontLoadedInternal(fontFamily, fontUrl) {
        // First, check if a font with this family name is already loaded and added by this mechanism
        // This avoids issues if document.fonts.check() is true for a system font but not the specific webfont
        if (processImage._fontLoaderPromise) {
            const promiseState = await Promise.race([
                processImage._fontLoaderPromise.then(status => ({ status: 'resolved', value: status })),
                new Promise(resolve => setTimeout(() => resolve({ status: 'pending' }), 0)) // Check current state without re-triggering
            ]);
            if (promiseState.status === 'resolved' && promiseState.value === true) {
                 // Check if the font object is indeed in document.fonts
                for (const font of document.fonts.values()) {
                    if (font.family === fontFamily && font.status === 'loaded') {
                        return true; // Already loaded by a previous call successfully
                    }
                }
                // If promise resolved true but font not found, means something is off, reset promise
                processImage._fontLoaderPromise = null; 
            } else if (promiseState.status === 'pending') {
                 return processImage._fontLoaderPromise; // Still loading, return the existing promise
            }
            // If promise resolved false, it will be reset below and retried.
        }
        
        // If no active promise, or if it previously failed and was reset
        if (!processImage._fontLoaderPromise) {
            processImage._fontLoaderPromise = new Promise(async (resolve) => {
                try {
                    const font = new FontFace(fontFamily, `url(${fontUrl})`);
                    await font.load();
                    document.fonts.add(font);
                    resolve(true); // Font loaded successfully
                } catch (e) {
                    console.error(`Failed to load font "${fontFamily}":`, e);
                    processImage._fontLoaderPromise = null; // Reset on failure to allow retry
                    resolve(false); // Font loading failed
                }
            });
        }
        return processImage._fontLoaderPromise;
    }

    // --- Constants ---
    const FONT_FAMILY = 'Noto Sans Runic';
    const FONT_URL = 'https://fonts.gstatic.com/s/notosansrunic/v21/H4ckBXKMC_BbOsYyL3gq841v0Yg_cnFfdw.woff2';
    
    const FRAME_BASE_COLOR = '#959088'; // Stone gray
    const FRAME_DARK_SPECKLE_COLOR = 'rgba(0, 0, 0, 0.1)';
    const FRAME_LIGHT_SPECKLE_COLOR = 'rgba(255, 255, 255, 0.08)';
    
    const RUNE_SHADOW_COLOR = '#403D39';    // Darker part of the "carved" rune
    const RUNE_HIGHLIGHT_COLOR = '#B0ABA3'; // Lighter, "lit" edge of the carving

    const INNER_SHADOW_COLOR = 'rgba(0, 0, 0, 0.35)';
    const INNER_HIGHLIGHT_COLOR = 'rgba(255, 255, 255, 0.20)';
    const BEVEL_WIDTH = 2; // pixels for inner bevel

    const RUNES = ['ᚠ', 'ᚢ', 'ᚦ', 'ᚨ', 'ᚱ', 'ᚲ', 'ᚷ', 'ᚹ', 'ᚺ', 'ᚾ', 'ᛁ', 'ᛃ', 'ᛇ', 'ᛈ', 'ᛉ', 'ᛊ', 'ᛏ', 'ᛒ', 'ᛖ', 'ᛗ', 'ᛚ', 'ᛜ', 'ᛞ', 'ᛟ'];

    // --- Font Loading ---
    const fontLoaded = await _ensureFontLoadedInternal(FONT_FAMILY, FONT_URL);

    // --- Calculate Dimensions ---
    const imgWidth = originalImg.width;
    const imgHeight = originalImg.height;
    const minDim = Math.min(imgWidth, imgHeight);
    
    const frameThickness = Math.max(35, Math.floor(minDim * 0.12)); // Min 35px thick frame
    let runeSize = Math.max(14, Math.floor(frameThickness * 0.50));
    if (runeSize % 2 !== 0) runeSize +=1; // Prefer even rune size for crispness/centering

    const canvasWidth = imgWidth + 2 * frameThickness;
    const canvasHeight = imgHeight + 2 * frameThickness;

    // --- Canvas Setup ---
    const canvas = document.createElement('canvas');
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    const ctx = canvas.getContext('2d');

    // --- Helper: Draw Stone Texture on a Rect ---
    function drawStoneTexture(x, y, w, h) {
        ctx.fillStyle = FRAME_BASE_COLOR;
        ctx.fillRect(x, y, w, h);
        const numSpeckles = Math.floor((w * h) / 25); // Adjust density of speckles
        for (let i = 0; i < numSpeckles; i++) {
            const speckleX = x + Math.random() * w;
            const speckleY = y + Math.random() * h;
            const speckleRadius = Math.random() * 1.8 + 0.5; // Adjust speckle size
            ctx.fillStyle = (Math.random() < 0.55) ? FRAME_DARK_SPECKLE_COLOR : FRAME_LIGHT_SPECKLE_COLOR;
            ctx.beginPath();
            ctx.arc(speckleX, speckleY, speckleRadius, 0, Math.PI * 2);
            ctx.fill();
        }
    }

    // --- Draw Stone Texture for Frame Parts ---
    drawStoneTexture(0, 0, canvasWidth, frameThickness); // Top
    drawStoneTexture(0, canvasHeight - frameThickness, canvasWidth, frameThickness); // Bottom
    drawStoneTexture(0, frameThickness, frameThickness, imgHeight); // Left (main part)
    drawStoneTexture(canvasWidth - frameThickness, frameThickness, frameThickness, imgHeight); // Right (main part)

    // --- Draw Inner Bevel for the Image Area ---
    ctx.fillStyle = INNER_SHADOW_COLOR;
    ctx.fillRect(frameThickness, frameThickness, imgWidth, BEVEL_WIDTH); // Top shadow
    ctx.fillRect(frameThickness, frameThickness + BEVEL_WIDTH, BEVEL_WIDTH, imgHeight - BEVEL_WIDTH); // Left shadow
    
    ctx.fillStyle = INNER_HIGHLIGHT_COLOR;
    ctx.fillRect(frameThickness + BEVEL_WIDTH, frameThickness + imgHeight - BEVEL_WIDTH, imgWidth - BEVEL_WIDTH, BEVEL_WIDTH); // Bottom highlight
    ctx.fillRect(frameThickness + imgWidth - BEVEL_WIDTH, frameThickness, BEVEL_WIDTH, imgHeight - BEVEL_WIDTH); // Right highlight
    
    // --- Draw the Original Image ---
    ctx.drawImage(originalImg, frameThickness, frameThickness, imgWidth, imgHeight);

    // --- Draw Runes ---
    if (fontLoaded && runeSize >= 10 && frameThickness >= runeSize + 8 /* ensure padding */) {
        ctx.font = `${runeSize}px "${FONT_FAMILY}"`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        const runeSpacingFactor = 0.4; // Relative to runeSize
        const runeVisualSpace = runeSize * (1 + runeSpacingFactor); // Space one rune takes including spacing
        const highlightOffset = Math.max(1, Math.floor(runeSize * 0.06));

        function drawEngravedRune(runeChar, x, y, rotation = 0) {
            ctx.save();
            ctx.translate(x, y);
            ctx.rotate(rotation);
            ctx.fillStyle = RUNE_HIGHLIGHT_COLOR; // Light catches this edge
            ctx.fillText(runeChar, -highlightOffset, -highlightOffset);
            ctx.fillStyle = RUNE_SHADOW_COLOR;   // This is the main, darker part of the rune
            ctx.fillText(runeChar, 0, 0);
            ctx.restore();
        }

        // Horizontal Runes (Top & Bottom)
        const horizontalRuneYTop = frameThickness / 2;
        const horizontalRuneYBottom = canvasHeight - frameThickness / 2;
        const horizontalStartOffset = frameThickness + runeVisualSpace / 2;
        const horizontalEndOffset = canvasWidth - frameThickness - runeVisualSpace / 2;

        for (let side = 0; side < 2; side++) { // 0 for top, 1 for bottom
            const yPos = (side === 0) ? horizontalRuneYTop : horizontalRuneYBottom;
            let currentX = horizontalStartOffset;
            while (currentX <= horizontalEndOffset) {
                drawEngravedRune(RUNES[Math.floor(Math.random() * RUNES.length)], currentX, yPos);
                currentX += runeVisualSpace;
            }
        }
        
        // Vertical Runes (Left & Right)
        const verticalRuneXLeft = frameThickness / 2;
        const verticalRuneXRight = canvasWidth - frameThickness / 2;
        const verticalStartOffset = frameThickness + runeVisualSpace / 2;
        const verticalEndOffset = canvasHeight - frameThickness - runeVisualSpace / 2;

        for (let side = 0; side < 2; side++) { // 0 for left, 1 for right
            const xPos = (side === 0) ? verticalRuneXLeft : verticalRuneXRight;
            const rotation = (side === 0) ? Math.PI / 2 : -Math.PI / 2;
            let currentY = verticalStartOffset;
            while (currentY <= verticalEndOffset) {
                drawEngravedRune(RUNES[Math.floor(Math.random() * RUNES.length)], xPos, currentY, rotation);
                currentY += runeVisualSpace;
            }
        }
    }
    
    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 Viking Runestone Frame Image Creator is a web-based tool that allows users to enhance their images by framing them with a decorative rune-engraved stone border. This tool can be used to create visually appealing graphics that reflect Viking or Norse aesthetics, making it suitable for personal projects, social media posts, or themed events. Users can upload their images and automatically generate a customized stone frame adorned with randomly placed runic characters. The tool is ideal for individuals looking to add a unique, historical touch to their visuals.

Leave a Reply

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

Other Image Tools:

Image Vintage Arcade Cabinet Art Creator

Byzantine Mosaic Frame Creator

Image Fighting Game Character Select Screen Creator

Image UFO Encounter Documentation Creator

Image Fantasy Realm Map Creator

Image Interdimensional Travel Permit Creator

Image Mad Scientist’s Laboratory Notes Creator

Image Underground Resistance Flyer Creator

Image Retro Video Game Box Art Creator

Image Captain’s Naval Journal Creator

Image Renaissance Painting Frame Creator

Image Lost Civilization Artifact Creator

Image Da Vinci Notebook Page Creator

Image Dystopian Citizen ID Creator

Image Monster Hunter Bestiary Creator

Image Vintage Carnival Sideshow Poster Creator

Image Space Explorer’s Log Creator

Image Neolithic Petroglyph Frame Creator

Image Ukiyo-e Japanese Woodblock Print Creator

Image Persian Miniature Painting Creator

Image Sci-Fi Movie Poster Template Creator

Image Horror Movie Poster Template

Image Social Media Milestone Certificate Creator

Halloween Death Certificate Template

Image Anatomical Illustration Frame Creator

Image Romance Novel Cover Template Creator

Image Tabloid Headline Template

Image Space Mission Patch Template Creator

Image Cassette Tape Cover Template Creator

Image Passport Page Template Generator

Image Old Map Frame With Compass Rose Decorator

Image Diploma and Degree Certificate Framer

Image Soviet Propaganda Poster Style Generator

Image Yu-Gi-Oh Card Template Creator

Image Ancient Roman Greek Tablet Frame Creator

Image Marriage Certificate Template Creator

See All →