Please bookmark this page to avoid losing your image tool!

Image Typewriter Text Filter Effect 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, characterSet = " .:-=+*#%@", fontSize = 10, textColor = "black", backgroundColor = "white") {
    // Validate image object and its dimensions
    // Use naturalWidth/Height for the true dimensions of the image, not its skewed display size
    if (!originalImg || typeof originalImg.naturalWidth === 'undefined' || typeof originalImg.naturalHeight === 'undefined') {
        console.error("Original image is invalid or not fully loaded. Ensure originalImg is an HTMLImageElement and img.complete is true or it's loaded via other means.");
        const errCanvas = document.createElement('canvas');
        // Set to 0x0 or some default small size if originalImg is completely unusable
        errCanvas.width = (originalImg && typeof originalImg.naturalWidth !== 'undefined') ? originalImg.naturalWidth : 0;
        errCanvas.height = (originalImg && typeof originalImg.naturalHeight !== 'undefined') ? originalImg.naturalHeight : 0;
        return errCanvas;
    }

    const imgWidth = originalImg.naturalWidth;
    const imgHeight = originalImg.naturalHeight;

    if (imgWidth === 0 || imgHeight === 0) {
        console.error("Image has zero natural width or height.");
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = imgWidth; 
        emptyCanvas.height = imgHeight;
        return emptyCanvas;
    }

    // Validate parameters
    if (typeof fontSize !== 'number' || isNaN(fontSize) || fontSize <= 0) {
        console.warn(`Invalid fontSize: ${fontSize}. Must be a positive number. Using default value 10.`);
        fontSize = 10;
    }
    if (typeof characterSet !== 'string' || characterSet.length === 0) {
        console.warn(`Invalid characterSet: "${characterSet}". Must be a non-empty string. Using default value " .:-=+*#%@".`);
        characterSet = " .:-=+*#%@";
    }
    if (typeof textColor !== 'string') {
        console.warn(`Invalid textColor: "${textColor}". Must be a string. Using default value "black".`);
        textColor = "black";
    }
    if (typeof backgroundColor !== 'string') {
        console.warn(`Invalid backgroundColor: "${backgroundColor}". Must be a string. Using default value "white".`);
        backgroundColor = "white";
    }
    
    // Create a temporary canvas to draw the original image. This allows us to access its pixel data.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = imgWidth;
    tempCanvas.height = imgHeight;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); // Hint for optimization if available

    if (!tempCtx) {
        console.error("Failed to get 2D context for temporary canvas. Your browser might not support CanvasRenderingContext2D.");
        const errCanvas = document.createElement('canvas');
        errCanvas.width = imgWidth; errCanvas.height = imgHeight; // Return empty canvas of original size
        return errCanvas;
    }

    try {
        tempCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Error drawing original image to temporary canvas:", e);
        const errCanvas = document.createElement('canvas');
        errCanvas.width = imgWidth; errCanvas.height = imgHeight;
        // Optionally draw an error message on this canvas
        return errCanvas;
    }

    // Create the output canvas where the typewriter text effect will be rendered.
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = imgWidth;
    outputCanvas.height = imgHeight;
    const outputCtx = outputCanvas.getContext('2d');

    if (!outputCtx) {
        console.error("Failed to get 2D context for output canvas.");
        const errCanvas = document.createElement('canvas');
        errCanvas.width = imgWidth; errCanvas.height = imgHeight;
        return errCanvas;
    }

    // Fill the background of the output canvas.
    outputCtx.fillStyle = backgroundColor;
    outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);

    // Set font properties for drawing characters.
    // A monospaced font like 'Courier New' is essential for the grid-like typewriter effect.
    outputCtx.font = `${fontSize}px 'Courier New', monospace`;
    outputCtx.fillStyle = textColor;
    outputCtx.textBaseline = 'top'; // Aligns the top of the character to the drawing y-coordinate.
    outputCtx.textAlign = 'left';   // Aligns characters to the left of the drawing x-coordinate.

    const step = fontSize; // Each character will represent a square cell of 'step x step' pixels.

    // Iterate over the image in blocks (cells).
    for (let y = 0; y < imgHeight; y += step) {
        for (let x = 0; x < imgWidth; x += step) {
            // Determine the actual size of the current block in the source image.
            // This can be smaller than 'step' if at the edges of the image.
            const blockWidth = Math.min(step, imgWidth - x);
            const blockHeight = Math.min(step, imgHeight - y);

            // Get pixel data for the current block from the temporary canvas.
            let imageData;
            try {
                imageData = tempCtx.getImageData(x, y, blockWidth, blockHeight);
            } catch (e) {
                // This error often occurs due to canvas tainting (e.g., cross-origin image without CORS).
                console.error(`Error getting image data at (${x},${y}) for block (${blockWidth}x${blockHeight}):`, e);
                if (e.name === 'SecurityError') {
                    // If canvas is tainted, pixel manipulation is not allowed.
                    // Clear any partial drawing and show an error message on the canvas.
                    console.error("Canvas is tainted (SecurityError). Cannot process pixel data. Ensure the image is same-origin or has CORS headers.");
                    outputCtx.clearRect(0,0, outputCanvas.width, outputCanvas.height); 
                    outputCtx.fillStyle = backgroundColor;
                    outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
                    
                    const prevFont = outputCtx.font; // Save current font
                    outputCtx.font = `14px Arial`; // Use a readable font for the error
                    outputCtx.fillStyle = textColor; 
                    outputCtx.textAlign = 'center';
                    outputCtx.textBaseline = 'middle';
                    outputCtx.fillText("Error: Cannot process image due to security restrictions.", outputCanvas.width / 2, outputCanvas.height / 2 - 10);
                    outputCtx.fillText("(Tainted canvas - check image origin and CORS).", outputCanvas.width / 2, outputCanvas.height / 2 + 10);
                    outputCtx.font = prevFont; // Restore font for any subsequent (unlikely) operations
                    return outputCanvas; // Abort processing and return canvas with error message.
                }
                continue; // Skip this block for other getImageData errors.
            }
            
            const data = imageData.data; // Pixel data: [R,G,B,A, R,G,B,A, ...]
            let sumGray = 0;
            const pixelCount = data.length / 4; // Each pixel has 4 components (RGBA).

            if (pixelCount === 0) { // Should not happen if blockWidth/Height > 0 logic is correct.
                continue;
            }

            // Calculate the average grayscale value for the block.
            for (let i = 0; i < data.length; i += 4) {
                const r = data[i];
                const g = data[i+1];
                const b = data[i+2];
                // Using the standard luminance formula for grayscale conversion.
                const gray = 0.299 * r + 0.587 * g + 0.114 * b;
                sumGray += gray;
            }
            const avgGray = sumGray / pixelCount; // Average gray value (0-255).

            // Map the average gray value to a character from the characterSet.
            // The characterSet is assumed to be ordered from lightest appearance to darkest/densest.
            // Example: " .:-=+*#%@" (space is lightest, @ is darkest).
            // avgGray = 255 (white) should map to the first character (index 0).
            // avgGray = 0   (black) should map to the last character (index characterSet.length - 1).
            // So, we need an inverse relationship with avgGray, normalized to 0-1.
            const normalizedInvBrightness = 1 - (avgGray / 255); // 0 for white (max brightness), 1 for black (min brightness).
            let charIndex = Math.round(normalizedInvBrightness * (characterSet.length - 1));
            
            // Clamp index to ensure it's valid for the characterSet array.
            charIndex = Math.max(0, Math.min(characterSet.length - 1, charIndex));

            const charToDraw = characterSet[charIndex];

            // Draw the selected character onto the output canvas at the top-left of the current cell (x, y).
            outputCtx.fillText(charToDraw, x, y);
        }
    }

    return outputCanvas;
}

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 Typewriter Text Filter Effect Tool allows users to convert images into a text representation using characters to simulate pixel colors. By specifying a character set, font size, text color, and background color, users can create visually interesting text art based on the original image’s grayscale values. This tool is useful for creating unique designs for social media posts, digital art projects, or any creative work where a text-based representation of an image is desired.

Leave a Reply

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