Please bookmark this page to avoid losing your image tool!

Image To ANSI Terminal Art Converter

(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, targetWidth = 80, character = "█") {

    // Standard 16-color ANSI palette (based on common xterm/modern terminal colors)
    const modernAnsiPalette = [
        { name: "Black", r: 0, g: 0, b: 0, code: "\x1b[30m" },
        { name: "Red", r: 205, g: 0, b: 0, code: "\x1b[31m" },
        { name: "Green", r: 0, g: 205, b: 0, code: "\x1b[32m" },
        { name: "Yellow", r: 205, g: 205, b: 0, code: "\x1b[33m" },
        { name: "Blue", r: 0, g: 0, b: 238, code: "\x1b[34m" },
        { name: "Magenta", r: 205, g: 0, b: 205, code: "\x1b[35m" },
        { name: "Cyan", r: 0, g: 205, b: 205, code: "\x1b[36m" },
        { name: "White", r: 229, g: 229, b: 229, code: "\x1b[37m" },
        { name: "BrightBlack", r: 127, g: 127, b: 127, code: "\x1b[90m" }, // Also known as Gray
        { name: "BrightRed", r: 255, g: 0, b: 0, code: "\x1b[91m" },
        { name: "BrightGreen", r: 0, g: 255, b: 0, code: "\x1b[92m" },
        { name: "BrightYellow", r: 255, g: 255, b: 0, code: "\x1b[93m" },
        { name: "BrightBlue", r: 92, g: 92, b: 255, code: "\x1b[94m" },
        { name: "BrightMagenta", r: 255, g: 0, b: 255, code: "\x1b[95m" },
        { name: "BrightCyan", r: 0, g: 255, b: 255, code: "\x1b[96m" },
        { name: "BrightWhite", r: 255, g: 255, b: 255, code: "\x1b[97m" }
    ];

    // Helper function to calculate squared Euclidean distance between_Tool colors
    function colorDistanceSquared(r1, g1, b1, r2, g2, b2) {
        const dr = r1 - r2;
        const dg = g1 - g2;
        const db = b1 - b2;
        return dr * dr + dg * dg + db * db;
    }

    // Helper function to find the closest ANSI color in the palette
    function findClosestAnsiColor(r, g, b, palette) {
        let closestColor = palette[0];
        let minDistance = colorDistanceSquared(r, g, b, closestColor.r, closestColor.g, closestColor.b);

        for (let i = 1; i < palette.length; i++) {
            const currentColor = palette[i];
            const distance = colorDistanceSquared(r, g, b, currentColor.r, currentColor.g, currentColor.b);
            if (distance < minDistance) {
                minDistance = distance;
                closestColor = currentColor;
            }
        }
        return closestColor.code;
    }

    // Dynamically load AnsiUp library for converting ANSI codes to HTML
    // AnsiUp is a UMD module, so it might define a global `AnsiUp` or be usable with `import()`.
    // This method focuses on global `AnsiUp`.
    if (typeof AnsiUp === 'undefined') {
        const scriptId = 'ansi_up_script_loader'; // Unique ID for the script tag
        const ansiUpUrl = 'https://cdn.jsdelivr.net/npm/ansi_up@5.2.1/ansi_up.min.js';

        if (!document.getElementById(scriptId)) {
            const script = document.createElement('script');
            script.id = scriptId;
            script.src = ansiUpUrl;
            script.type = 'text/javascript';
            script.async = true;

            const loadPromise = new Promise((resolve, reject) => {
                script.onload = () => {
                    if (typeof AnsiUp !== 'undefined') {
                        resolve();
                    } else {
                        reject(new Error('AnsiUp did not define global AnsiUp object after script load.'));
                    }
                };
                script.onerror = (event) => {
                    // event might be an ErrorEvent or just a generic Event.
                    // For more detail, one might check event.message or event.type.
                    console.error("Error loading AnsiUp script:", event);
                    reject(new Error('Failed to load AnsiUp script from ' + ansiUpUrl));
                };
            });
            document.head.appendChild(script);
            try {
                await loadPromise;
            } catch (e) {
                console.error("AnsiUp SCRIPT loading error:", e.message);
                const pre = document.createElement('pre');
                pre.textContent = "Error: Could not load AnsiUp library. " + e.message;
                return pre;
            }
        } else {
            // Script tag exists, but AnsiUp might not be defined yet (e.g. still loading)
            // Poll for AnsiUp global variable to become available.
            try {
                await new Promise((resolve, reject) => {
                    let attempts = 0;
                    const intervalId = setInterval(() => {
                        if (typeof AnsiUp !== 'undefined') {
                            clearInterval(intervalId);
                            resolve();
                        } else if (attempts++ > 100) { // Timeout after ~5 seconds (100 attempts * 50ms interval)
                            clearInterval(intervalId);
                            reject(new Error('AnsiUp not defined even after script tag was found (timeout).'));
                        }
                    }, 50); // Check every 50ms
                });
            } catch (e) {
                 console.error("AnsiUp POLLING error:", e.message);
                 const pre = document.createElement('pre');
                 pre.textContent = "Error: AnsiUp library not available. " + e.message;
                 return pre;
            }
        }
    }
    
    // Final check: If AnsiUp is still not available, something went wrong.
    if (typeof AnsiUp === 'undefined') {
        console.error("Critical: AnsiUp is still not defined after loading attempts.");
        const pre = document.createElement('pre');
        pre.textContent = "Error: AnsiUp library failed to initialize correctly.";
        return pre;
    }
    const ansi_up = new AnsiUp();

    // Prepare canvas for image processing
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization for getImageData

    // Ensure the image is loaded and valid
    if (!originalImg || !originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
        console.error("Image is not fully loaded, is invalid, or was not provided.");
        const pre = document.createElement('pre');
        pre.textContent = "Error: Input image is not loaded or is invalid.";
        pre.style.color = "red";
        return pre;
    }
    
    const imgAspectRatio = originalImg.height / originalImg.width;
    
    // This factor adjusts for non-square character cells in typical terminal fonts (e.g., 8x16px).
    // It means characters are often twice as tall as they are wide.
    // To make the image appear with correct proportions in such a font,
    // we sample fewer vertical pixels per character row relative to horizontal.
    // A value of 0.5 assumes font cell height is twice its width.
    // For square cells (e.g. if using a 'perfect' block font or specific CSS), this should be 1.0.
    const charCellAspectRatioCorrection = 0.5; 
    
    const targetHeightChars = Math.max(1, Math.round(targetWidth * imgAspectRatio * charCellAspectRatioCorrection));

    // Set canvas dimensions for sampling: one "pixel" on this canvas per output character.
    canvas.width = targetWidth;
    canvas.height = targetHeightChars;

    // Draw the image onto the canvas, scaled to the target character dimensions.
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This can happen if the image is cross-origin and the canvas becomes "tainted".
        console.error("Error getting ImageData (possibly from a tainted canvas):", e);
        const pre = document.createElement('pre');
        pre.textContent = "Error: Could not get image data. If using a cross-origin image, ensure CORS is enabled for it.";
        pre.style.color = "red";
        return pre;
    }
    const data = imageData.data;

    let ansiString = "";
    const ansiResetCode = "\x1b[0m"; // ANSI Reset All Attributes code

    // Iterate over the sampled "pixels" (which correspond to character cells)
    for (let y = 0; y < canvas.height; y++) { // y is character row
        let currentLineAnsi = "";
        for (let x = 0; x < canvas.width; x++) { // x is character column
            const pixelIndex = (y * canvas.width + x) * 4;
            const r = data[pixelIndex];
            const g = data[pixelIndex + 1];
            const b = data[pixelIndex + 2];
            const a = data[pixelIndex + 3];

            if (a < 128) { // If pixel is significantly transparent, use a space
                // This relies on the <pre> element's background color.
                // For more control, one could set an ANSI background color here.
                currentLineAnsi += " "; 
            } else {
                const ansiColorCode = findClosestAnsiColor(r, g, b, modernAnsiPalette);
                currentLineAnsi += ansiColorCode + character;
            }
        }
        // Add reset code at the end of each line to contain color effects to that line.
        ansiString += currentLineAnsi + ansiResetCode + "\n";
    }
    
    // Convert the raw ANSI string to HTML using AnsiUp
    const htmlOutput = ansi_up.ansi_to_html(ansiString);
    
    // Create a <pre> element to display the HTML content
    const preElement = document.createElement('pre');
    preElement.innerHTML = htmlOutput; // Use innerHTML as ansi_to_html returns an HTML string
    
    // Style the <pre> element for a terminal-like appearance
    preElement.style.fontFamily = "monospace, 'Courier New', Courier"; // Monospaced font is crucial
    preElement.style.fontSize = "10px"; // Adjust for desired density/size
    preElement.style.lineHeight = "1.0";  // Attempt to make character blocks touch vertically
    preElement.style.backgroundColor = "black"; // Default terminal background color
    preElement.style.color = "white"; // Default text color (if not overridden by ANSI)
    preElement.style.whiteSpace = "pre"; // Preserve whitespace and line breaks
    preElement.style.overflow = "auto";  // Add scrollbars if content is too large
    preElement.style.margin = "0";       // Remove default margins from <pre>

    return preElement;
}

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 To ANSI Terminal Art Converter transforms images into ANSI art suitable for terminal displays. Users can specify the target width of the output and choose a character to represent each pixel. This tool is particularly useful for creating retro-style artwork, programming projects, and displaying images in environments where graphical interfaces are not available. It can be employed in coding communities, for customizing terminal outputs, or for creating unique visual elements in text-based applications.

Leave a Reply

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

Other Image Tools:

Image Dashed Line Adder

Image To Dingbat Symbol Art Converter

Image Custom Polygon Cropper

Image Rule Of Thirds Grid Overlay Tool

Image Resizer for Social Media Platforms

Image Circular Crop Tool

Image Date Stamp Adder

Image Circular Guides Adder

Image Center Cropper

Image Petzval Lens Swirly Bokeh Effect Creator

Image Mimiya 645 Medium Format Filter Effect Tool

Photo Fujifilm Klasse W Filter Effect Application

Image Deardorff Large Format Filter Effect Application

Image Lomo LC-A Filter Effect Tool

Image Large Format Filter Effect Application

Image Zone Plate Lens Effect Creator

Photo Kodak Retina Filter Effect Tool

Image Polaroid 600 Filter Effect Tool

Photo Black and White Yellow Filter Effect Tool

Image Contax G2 Film Camera Render Effect Applicator

Image 110 Film Format Filter Effect Tool

Photo Jupiter-9 Portrait Lens Filter Effect

Image Fujifilm GW690 Texas Leica Filter Effect Application

Image Zeiss T* Coating Filter Effect Tool

Image Hoya R72 Infrared Filter Effect Tool

Image Filter Effect for Zeiss Ikon Contaflex

Photo Olympus Mju-II/Stylus Epic Filter Effect Tool

Image NiSi Nano IR ND Filter Effect Tool

Image Polaroid SX-70 Filter Effect Tool

Image Linhof Technika Filter Effect Tool

Image Lee Big Stopper 10-Stop ND Filter Effect Tool

Image Minolta X-700 Film Camera Render Effect Creator

Image ORWO UN54 Motion Picture Film Effect Applicator

Image Shen-Hao Large Format Filter Effect Tool

Image Impossible Project Polaroid Filter Effect Tool

Photo Foma Retropan 320 Film Filter Effect Tool

See All →