ASCII Art Image Rendering for w3m

Download Patch

Overview

This patch adds native ASCII art image rendering to w3m, enabling inline image display in terminals without framebuffer, sixel, or proprietary graphics protocol support. Images are converted to colored ASCII/ANSI text and rendered directly in the terminal, making them viewable over SSH sessions, TTY environments, terminal multiplexers, and any text-based display.

Features


Examples

Here’s what w3m looks like rendering images as ASCII art in my text only terminal, using this configuration:

inline_img_protocol 5
asciiart_display chafa --size=%dx%d --stretch --colors=256 \
    --symbols=space+solid+stipple+block+border+diagonal+dot+quad+half+hhalf+vhalf+braille+technical+geometric+ascii+wide \
    '%s' 2>/dev/null

https://wiki.evageeks.org/File:OP_C015_ritsuko.jpg

https://wiki.evageeks.org/Characters_(Portal)

Installation

Requirements

  1. w3m compiled with USE_IMAGE support
  2. An ASCII art generator program:

Applying the Patch

cd /path/to/w3m
patch -p1 < w3m-ascii-art.patch
./configure
make
make install

Configuration

Basic Setup

Add to ~/.w3m/config:

inline_img_protocol 5
asciiart_display chafa --size=%dx%d --colors=256 --symbols=block --stretch '%s' 2>/dev/null

Or use the options menu (o key) to select “ASCII art” for the inline image protocol.

Generator Command Format

The asciiart_display command uses printf-style placeholders:

Placeholder Description
%d (first) Width in characters
%d (second) Height in characters
%s Image file path

Important: Redirect stderr to /dev/null to avoid display interference.

Configuration Examples

High Quality (Unicode blocks with 256 colors):

asciiart_display chafa --size=%dx%d --colors=256 --symbols=block --stretch '%s' 2>/dev/null

Classic ASCII only:

asciiart_display chafa --size=%dx%d --colors=256 --symbols=ascii --stretch '%s' 2>/dev/null

Monochrome ASCII (jp2a):

asciiart_display jp2a --width=%d --height=%d '%s' 2>/dev/null

Colored ASCII (jp2a):

asciiart_display jp2a --width=%d --height=%d --colors '%s' 2>/dev/null

Retro Style (libcaca):

asciiart_display img2txt -W %d -H %d '%s' 2>/dev/null

Usage

Once configured, images automatically render as ASCII art when browsing. No manual intervention required.

Key Bindings

Key Action
I Restart image loading and display
ESC I Stop image loading
o Open options menu (toggle display_image)
h,j,k,l Vi-style navigation for large images
<,> Horizontal scrolling

Quick Test

# View local image
w3m file:///path/to/image.jpg

# View HTML with images
w3m https://example.com/gallery.html

# Test inline rendering
echo '<img src="test.jpg" width="80" height="40">' | w3m -T text/html

Technical Implementation

Architecture

The implementation spans 10 files with ~950 lines of changes:

display.c     - Draw condition updates for ASCII protocol
file.c        - ALT text suppression and dimension scaling
fm.h          - Protocol constant and ImageCache extensions
image.c       - Parallel download/conversion with worker pool
main.c        - Main loop polling for ASCII mode
rc.c          - Configuration parsing for new options
terms.c       - ASCII art rendering and ANSI filtering
terms.h       - Function declarations

Protocol Constant

A new inline image protocol constant enables ASCII art mode:

// fm.h
#define INLINE_IMG_ASCII  5

This integrates with existing protocols (0-4) and is selected via inline_img_protocol configuration.

ImageCache Extensions

The ImageCache structure gains two new fields for ASCII conversion tracking:

typedef struct _imageCache {
    // ... existing fields ...
    char *ascii_file;     /* Path to cached ASCII art output */
    char ascii_loaded;    /* ASCII conversion status */
} ImageCache;

Worker Pool Pattern

Image downloads and conversions use a parallel worker pool:

static int getCpuCores(void)
{
    int cores = 1;
#ifdef _SC_NPROCESSORS_ONLN
    long result = sysconf(_SC_NPROCESSORS_ONLN);
    if (result > 0)
        cores = (int)result;
#else
    // Fallback: parse /proc/cpuinfo
#endif
    return cores;
}

Workers are set to cpu_cores / 2 (minimum 1, maximum 8) for optimal throughput without overwhelming the system.

Dimension Calculation

Images are scaled to fit terminal dimensions while preserving usability:

void calculate_ascii_dimensions(int w, int h, int *full_w, int *full_h)
{
    int max_width = COLS;
    int max_height = LINES;

    if (w > max_width || h > max_height) {
        double scale_w = (double)max_width / w;
        double scale_h = (double)max_height / h;
        double scale = (scale_w < scale_h) ? scale_w : scale_h;
        *full_w = (int)(w * scale);
        *full_h = (int)(h * scale);
    } else {
        *full_w = w;
        *full_h = h;
    }
}

ANSI Escape Sequence Filtering

Generator output is filtered to remove problematic terminal control sequences:

static int is_ansi_sequence(const char *s, int *length)
{
    if (s[0] != '\033') return 0;
    if (s[1] == '[') {
        // CSI sequence: ESC [ ... <terminator>
        // Terminators: 0x40-0x7E (@ through ~)
        int i = 2;
        while (s[i] && !((s[i] >= 0x40 && s[i] <= 0x7E)))
            i++;
        if (s[i]) {
            *length = i + 1;
            return 1;
        }
    }
    return 0;
}

This handles: - Color codes (ESC[...m) - Cursor positioning (ESC[...H) - Cursor visibility (ESC[?25h, ESC[?25l) - All CSI sequences with terminators in the 0x40-0x7E range

Rendering Pipeline

┌─────────────────────────────────────────────────────────────────┐
│  1. HTML parsed, <img> tags identified with dimensions          │
├─────────────────────────────────────────────────────────────────┤
│  2. ImageCache created with ascii_file temp path                │
├─────────────────────────────────────────────────────────────────┤
│  3. Worker pool downloads remote images in parallel             │
├─────────────────────────────────────────────────────────────────┤
│  4. On download complete: fork() to run ASCII generator         │
│     - Command: asciiart_display %d %d '%s' > temp_file          │
├─────────────────────────────────────────────────────────────────┤
│  5. Conversion status polled, ascii_loaded flag set             │
├─────────────────────────────────────────────────────────────────┤
│  6. When ALL images complete: single display refresh            │
├─────────────────────────────────────────────────────────────────┤
│  7. put_image_ascii() renders output with ANSI filtering        │
│     - Positions cursor at image location                        │
│     - Reads cached file line by line                            │
│     - Filters control sequences, preserves colors               │
│     - Clips to viewport bounds                                  │
└─────────────────────────────────────────────────────────────────┘

Single-Refresh Optimization

To prevent flickering with multiple images, the display only refreshes once all images are complete:

// Check if ALL images are complete
all_complete = 1;
for (j = 0, a = al->anchors; j < al->nanchor; j++, a++) {
    if (a->image && a->image->cache) {
        cache = a->image->cache;
        if (cache->loaded == IMG_FLAG_UNLOADED)
            all_complete = 0;
        if (cache->loaded == IMG_FLAG_LOADED &&
            cache->ascii_loaded != IMG_FLAG_LOADED)
            all_complete = 0;
    }
}

if (all_complete) {
    displayBuffer(image_buffer, B_REDRAW_IMAGE);
    image_buffer->image_loaded = TRUE;
}

Buffer Size Handling

Large images with dense ANSI sequences required buffer expansion:

#define ASCII_LINE_BUF_SIZE 65536  /* 64KB for long lines with ANSI codes */

A typical 256-color block character with positioning can be 20+ bytes per character, requiring substantial buffer space for wide images.


Differences from Other Protocols

Feature ASCII Art (5) External (0) Sixel (2) Kitty (4)
External process Generator only w3mimgdisplay img2sixel ImageMagick
Terminal support Universal X11/Framebuffer Sixel-capable Kitty only
Output type Text characters Pixels Pixels Pixels
Copyable Yes No No No
ALT text Suppressed Shown Shown Shown

Troubleshooting

Images show as [IMG]

Images show as [ERR]

Display corruption

Performance issues


For best results:


Commits

This feature was implemented across three commits:

  1. 0fc2590 — Add ASCII art image display support (initial implementation)
  2. 85e9410 — Improve ASCII art image display with parallel processing
  3. abed4f5 — Fix ASCII art buffer truncation for large images

License

This patch is released under the same license as w3m (MIT-like).

See Also