Please bookmark this page to avoid losing your image tool!

Image Forbidden Knowledge Page 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,
    pageTitle = "CODEX OBSCURA",
    mainText = "Fragmenta vetusta, in tenebris susurrata.\nScientia prohibita hic iacet.",
    titleFontSize = 36,
    bodyFontSize = 20,
    pageFontFamily = "'UnifrakturMaguntia', cursive",
    textColor = "#3A2F2F", 
    imageEffect = "sepia", // "none", "grayscale", "sepia"
    symbols = "✧✦★✡☥☯♈♉♊♋♌♍♎♏♐♑♒♓☉☽☿♀⊕♂♃♄⊗⊙∴∵∆∇ΣΩ",
    symbolFontFamily = "Arial", // Symbols often render better with a generic font
    symbolColor = "#5a0000", 
    symbolAlpha = 0.2,
    symbolDensity = 0.05, // Proportion of "cells" (symbolSize x symbolSize) to fill
    symbolSize = 22,
    backgroundColor = "#EAE0C8", 
    pagePadding = 40
) {

    // Helper to load a Google Font (specifically for UnifrakturMaguntia in this case)
    const _loadGoogleFont = async (fontFamilyName, fontUrl) => {
        // Check if font is already loaded or available natively
        // fontFamilyName here is the pure name like "UnifrakturMaguntia"
        if (document.fonts.check(`12px "${fontFamilyName}"`)) {
            return true;
        }

        const linkId = `google-font-${fontFamilyName.replace(/\s+/g, '-')}`;
        if (!document.getElementById(linkId)) {
            const link = document.createElement('link');
            link.id = linkId;
            link.href = fontUrl;
            link.rel = 'stylesheet';
            document.head.appendChild(link);
            
            // Wait for the stylesheet to load
            await new Promise((resolve, reject) => {
                let timeoutId = setTimeout(() => reject(new Error('Font stylesheet loading timeout')), 5000);
                link.onload = () => { clearTimeout(timeoutId); resolve(); };
                link.onerror = () => { clearTimeout(timeoutId); reject(new Error('Font stylesheet failed to load'));};
            }).catch(e => console.warn(`Stylesheet for ${fontFamilyName}: ${e.message}. Attempting font load anyway.`));
        }
        
        try {
            // Attempt to load the specific font weight/style
            await document.fonts.load(`12px "${fontFamilyName}"`);
            return true;
        } catch (e) {
            console.error(`Failed to load font "${fontFamilyName}" via document.fonts.load:`, e);
            return false;
        }
    };

    let actualPageFontFamily = pageFontFamily;
    if (pageFontFamily.toLowerCase().includes('unifrakturmaguntia')) {
        const googleFontName = 'UnifrakturMaguntia'; // The actual font family name from Google's CSS
        const fontLoaded = await _loadGoogleFont(googleFontName, 'https://fonts.googleapis.com/css2?family=UnifrakturMaguntia&display=swap');
        if (!fontLoaded) {
            actualPageFontFamily = 'serif'; // Fallback if UnifrakturMaguntia fails
        } else {
            actualPageFontFamily = `'${googleFontName}', cursive`; // Use the successfully loaded font
        }
    }

    // Helper to apply image effects
    const _applyImageEffect = (img, effect) => {
        const tempCanvas = document.createElement('canvas');
        const natWidth = img.naturalWidth || img.width;
        const natHeight = img.naturalHeight || img.height;
        tempCanvas.width = natWidth;
        tempCanvas.height = natHeight;
        const tempCtx = tempCanvas.getContext('2d');
        if (!natWidth || !natHeight) return tempCanvas; // Return empty if image invalid
        
        tempCtx.drawImage(img, 0, 0, natWidth, natHeight);

        if (effect === "none") return tempCanvas;

        const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
        const data = imageData.data;

        for (let i = 0; i < data.length; i += 4) {
            const r = data[i], g = data[i+1], b = data[i+2];
            if (effect === "grayscale") {
                const gray = 0.299 * r + 0.587 * g + 0.114 * b;
                data[i] = data[i+1] = data[i+2] = gray;
            } else if (effect === "sepia") {
                data[i] = Math.min(255, 0.393*r + 0.769*g + 0.189*b);
                data[i+1] = Math.min(255, 0.349*r + 0.686*g + 0.168*b);
                data[i+2] = Math.min(255, 0.272*r + 0.534*g + 0.131*b);
            }
        }
        tempCtx.putImageData(imageData, 0, 0);
        return tempCanvas;
    };

    // Calculate dimensions
    const imgNatWidth = originalImg.naturalWidth || originalImg.width;
    const imgNatHeight = originalImg.naturalHeight || originalImg.height;

    const titleLineHeight = titleFontSize * 1.2; // Estimated height for a line of title text
    const bodyLineHeight = bodyFontSize * 1.5;   // Estimated height for a line of body text
    const mainTextLines = mainText.split('\n');

    const canvas = document.createElement('canvas');
    canvas.width = Math.max(300, imgNatWidth + 2 * pagePadding);
    canvas.height = pagePadding // Top padding
                   + titleLineHeight // Title block height
                   + pagePadding // Space between title and image
                   + imgNatHeight // Image height
                   + pagePadding // Space between image and main text
                   + (mainTextLines.length * bodyLineHeight) // Main text block height
                   + pagePadding; // Bottom padding

    const ctx = canvas.getContext('2d');

    // 1. Background + Parchment Texture
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.save();
    const numSpecks = Math.floor(canvas.width * canvas.height * 0.0015);
    for (let i = 0; i < numSpecks; i++) {
        const x = Math.random() * canvas.width;
        const y = Math.random() * canvas.height;
        const speckWidth = Math.random() * 2.5 + 0.5;
        const speckHeight = Math.random() * 2.5 + 0.5;
        const alpha = Math.random() * 0.25 + 0.05;
        const grayVal = Math.floor(Math.random() * 100) + 30; // Darkish gray specks (30-129)
        ctx.fillStyle = `rgba(${grayVal}, ${grayVal}, ${grayVal}, ${alpha})`;
        ctx.fillRect(x, y, speckWidth, speckHeight);
    }
    ctx.restore();

    // Set text baseline to top for easier Y coordinate management
    ctx.textBaseline = 'top';

    // 2. Processed Image (draw after background texture, before text and symbols that overlay)
    const processedImgCanvas = _applyImageEffect(originalImg, imageEffect);
    const imgX = (canvas.width - imgNatWidth) / 2; // Center image horizontally
    const imgActualY = pagePadding + titleLineHeight + pagePadding; // Y position of image
    if (imgNatWidth > 0 && imgNatHeight > 0) {
        ctx.drawImage(processedImgCanvas, imgX, imgActualY, imgNatWidth, imgNatHeight);
    }


    // 3. Text (Title, Body)
    ctx.fillStyle = textColor;
    ctx.textAlign = 'center';

    // Title
    ctx.font = `${titleFontSize}px ${actualPageFontFamily}`;
    const titleActualY = pagePadding;
    ctx.fillText(pageTitle, canvas.width / 2, titleActualY);

    // Body Text
    ctx.font = `${bodyFontSize}px ${actualPageFontFamily}`;
    let currentTextY = imgActualY + (imgNatHeight > 0 ? imgNatHeight : -pagePadding) + pagePadding; // Start Y for body text
    mainTextLines.forEach(line => {
        ctx.fillText(line, canvas.width / 2, currentTextY);
        currentTextY += bodyLineHeight;
    });

    // 4. Symbols (drawn on top of everything with alpha)
    if (symbols && symbols.length > 0 && symbolDensity > 0) {
        ctx.save();
        ctx.font = `${symbolSize}px ${symbolFontFamily}`;
        ctx.fillStyle = symbolColor;
        ctx.globalAlpha = symbolAlpha;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle'; // Center symbols vertically as well

        const symbolChars = symbols.split(''); // Use this instead of Array.from for wider compatibility (though split handles unicode fine)
        const numSymbolsToDraw = Math.floor((canvas.width / symbolSize) * (canvas.height / symbolSize) * symbolDensity);
        
        for (let i = 0; i < numSymbolsToDraw; i++) {
            const x = Math.random() * canvas.width;
            const y = Math.random() * canvas.height;
            const randomSymbol = symbolChars[Math.floor(Math.random() * symbolChars.length)];
            ctx.fillText(randomSymbol, x, y);
        }
        ctx.restore();
    }
    
    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 Forbidden Knowledge Page Creator is a versatile online tool designed to create visually striking image pages that combine imagery with stylized text elements. It allows users to upload an image, apply effects such as sepia or grayscale, and overlay custom titles and main text. The tool’s unique features include the ability to customize font styles, colors, and backgrounds, as well as the addition of decorative symbols to enhance the page’s aesthetic appeal. This tool is ideal for artists, writers, or anyone looking to create engaging visual content for projects such as art presentations, literary works, or themed digital displays.

Leave a Reply

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