Skip to main content

Overview

Sixel is a bitmap graphics format that allows terminals to display images inline. tmux includes comprehensive sixel support when compiled with the appropriate libraries, enabling image display in panes.
Sixel support requires:
  • tmux compiled with sixel support (typically requires libsixel)
  • Terminal that supports sixel graphics
  • Enabled via terminal-features

Sixel Protocol

Image Format

From image-sixel.c:26-54, sixel images are represented internally as:
struct sixel_image {
    u_int   x;              // Width in pixels
    u_int   y;              // Height in pixels
    u_int   xpixel;         // Cell width in pixels
    u_int   ypixel;         // Cell height in pixels
    
    u_int   set_ra;         // Raster attributes set
    u_int   ra_x;           // Raster width
    u_int   ra_y;           // Raster height
    
    u_int   *colours;       // Color palette
    u_int   ncolours;       // Number of colors
    u_int   used_colours;   // Colors used
    u_int   p2;             // Color mode parameter
    
    u_int   dx;             // Current X position
    u_int   dy;             // Current Y position  
    u_int   dc;             // Current color
    
    struct sixel_line *lines;  // Image data
};

Sixel Line Structure

From image-sixel.c:29-32:
struct sixel_line {
    u_int       x;          // Width of this line
    uint16_t    *data;      // Pixel data (color indices)
};
Each sixel line contains 6 pixels vertically, encoded as a single character.

Terminal Feature

From tty-features.c:350-358, enable sixel support:
static const char *const tty_feature_sixel_capabilities[] = {
    "Sxl",    // Sixel capability
    NULL
};
static const struct tty_feature tty_feature_sixel = {
    "sixel",
    tty_feature_sixel_capabilities,
    TERM_SIXEL
};

Configuration

~/.tmux.conf
# Enable sixel support for xterm-compatible terminals
set -g terminal-features "xterm*:sixel"

# Verify sixel support
tmux info | grep -i sixel

Sixel Parsing

Image Sequence Format

From image-sixel.c:299-354, sixel sequences start with:
\033P<parameters>q<sixel-data>\033\\
Where:
  • \033P - DCS (Device Control String) introducer
  • <parameters> - Optional parameters
  • q - Sixel command
  • <sixel-data> - Encoded image data
  • \033\\ - ST (String Terminator)

Sixel Parser

From image-sixel.c:299-362, the parser handles:
struct sixel_image *
sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel) {
    struct sixel_image *si;
    const char *cp = buf, *end = buf + len;
    char ch;
    
    if (len == 0 || len == 1 || *cp++ != 'q') {
        log_debug("%s: empty image", __func__);
        return (NULL);
    }
    
    si = xcalloc(1, sizeof *si);
    si->xpixel = xpixel;
    si->ypixel = ypixel;
    si->p2 = p2;
    
    while (cp != end) {
        ch = *cp++;
        switch (ch) {
        case '"':  // Raster attributes
            cp = sixel_parse_attributes(si, cp, end);
            break;
        case '#':  // Color definition
            cp = sixel_parse_colour(si, cp, end);
            break;
        case '!':  // Repeat sequence
            cp = sixel_parse_repeat(si, cp, end);
            break;
        case '-':  // Carriage return / line feed
            si->dx = 0;
            si->dy += 6;
            break;
        case '$':  // Carriage return
            si->dx = 0;
            break;
        default:   // Sixel data
            if (ch < 0x3f || ch > 0x7e)
                goto bad;
            if (sixel_parse_write(si, ch - 0x3f) != 0)
                goto bad;
            si->dx++;
            break;
        }
    }
    
    return (si);
}

Sixel Commands

From image-sixel.c:143-193, define image dimensions:
"Pan;Pad;Ph;Pv
Where:
  • Pan: Pixel aspect ratio numerator
  • Pad: Pixel aspect ratio denominator
  • Ph: Horizontal size in pixels
  • Pv: Vertical size in pixels
Example:
"1;1;800;600
Defines an 800x600 image with 1:1 aspect ratio.

Image Size Limits

From image-sixel.c:26-27:
#define SIXEL_WIDTH_LIMIT 10000
#define SIXEL_HEIGHT_LIMIT 10000
These limits prevent excessive memory usage:
  • Maximum width: 10,000 pixels
  • Maximum height: 10,000 pixels
From image-sixel.c:70-78 and 82-92:
static int
sixel_parse_expand_lines(struct sixel_image *si, u_int y) {
    if (y <= si->y)
        return (0);
    if (y > SIXEL_HEIGHT_LIMIT)
        return (1);
    si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
    si->y = y;
    return (0);
}

static int
sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) {
    if (x <= sl->x)
        return (0);
    if (x > SIXEL_WIDTH_LIMIT)
        return (1);
    if (x > si->x)
        si->x = x;
    sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
    sl->x = si->x;
    return (0);
}

Image Scaling

From image-sixel.c:416-484, images can be scaled to fit terminal cells:
struct sixel_image *
sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, 
    u_int ox, u_int oy, u_int sx, u_int sy, int colours) {
    struct sixel_image *new;
    u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
    
    // Get section of image at ox,oy in image cells
    // Map onto same size in terminal cells
    
    sixel_size_in_cells(si, &cx, &cy);
    
    // Convert cell coordinates to pixels
    pox = ox * si->xpixel;
    poy = oy * si->ypixel;
    psx = sx * si->xpixel;
    psy = sy * si->ypixel;
    
    // Target size in terminal pixels
    tsx = sx * xpixel;
    tsy = sy * ypixel;
    
    new = xcalloc(1, sizeof *si);
    new->xpixel = xpixel;
    new->ypixel = ypixel;
    
    // Scale pixel by pixel
    for (y = 0; y < tsy; y++) {
        py = poy + ((double)y * psy / tsy);
        for (x = 0; x < tsx; x++) {
            px = pox + ((double)x * psx / tsx);
            sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
        }
    }
    
    return (new);
}

Cell Size Calculation

From image-sixel.c:403-414:
void
sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) {
    if ((si->x % si->xpixel) == 0)
        *x = (si->x / si->xpixel);
    else
        *x = 1 + (si->x / si->xpixel);
        
    if ((si->y % si->ypixel) == 0)
        *y = (si->y / si->ypixel);
    else
        *y = 1 + (si->y / si->ypixel);
}

Sixel Output

From image-sixel.c:571-656, generating sixel sequences:
char *
sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) {
    char *buf, tmp[64];
    size_t len, used = 0, tmplen;
    
    len = 8192;
    buf = xmalloc(len);
    
    // Start sequence
    tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2);
    sixel_print_add(&buf, &len, &used, tmp, tmplen);
    
    // Raster attributes
    if (si->set_ra) {
        tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", 
            si->ra_x, si->ra_y);
        sixel_print_add(&buf, &len, &used, tmp, tmplen);
    }
    
    // Color definitions
    for (i = 0; i < ncolours; i++) {
        c = colours[i];
        tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
            i, c >> 25, (c >> 16) & 0x1ff, (c >> 8) & 0xff, c & 0xff);
        sixel_print_add(&buf, &len, &used, tmp, tmplen);
    }
    
    // Image data (compressed)
    for (y = 0; y < si->y; y += 6) {
        sixel_print_compress_colors(si, chunks, y, active, &nactive);
        // Output color chunks
    }
    
    // End sequence
    sixel_print_add(&buf, &len, &used, "\033\\", 2);
    
    return (buf);
}

Compression

From image-sixel.c:520-569, sixel data is run-length encoded:
static void
sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) {
    char tmp[16];
    size_t tmplen;
    
    if (count == 1)
        sixel_print_add(buf, len, used, &ch, 1);
    else if (count == 2) {
        sixel_print_add(buf, len, used, &ch, 1);
        sixel_print_add(buf, len, used, &ch, 1);
    } else if (count == 3) {
        sixel_print_add(buf, len, used, &ch, 1);
        sixel_print_add(buf, len, used, &ch, 1);
        sixel_print_add(buf, len, used, &ch, 1);
    } else if (count != 0) {
        tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
        sixel_print_add(buf, len, used, tmp, tmplen);
    }
}
Short runs (1-3 pixels) are output directly. Longer runs use the repeat syntax.

Displaying Images

Screen Conversion

From image-sixel.c:658-690, convert sixel to tmux screen:
struct screen *
sixel_to_screen(struct sixel_image *si) {
    struct screen *s;
    struct screen_write_ctx ctx;
    struct grid_cell gc;
    u_int x, y, sx, sy;
    
    sixel_size_in_cells(si, &sx, &sy);
    
    s = xmalloc(sizeof *s);
    screen_init(s, sx, sy, 0);
    
    // Draw placeholder box with '~' characters
    memcpy(&gc, &grid_default_cell, sizeof gc);
    gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
    utf8_set(&gc.data, '~');
    
    screen_write_start(&ctx, s);
    if (sx == 1 || sy == 1) {
        for (y = 0; y < sy; y++) {
            for (x = 0; x < sx; x++)
                grid_view_set_cell(s->grid, x, y, &gc);
        }
    } else {
        screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
        for (y = 1; y < sy - 1; y++) {
            for (x = 1; x < sx - 1; x++)
                grid_view_set_cell(s->grid, x, y, &gc);
        }
    }
    screen_write_stop(&ctx);
    return (s);
}
This creates a placeholder screen showing the image dimensions.

Debugging Sixel

From image-sixel.c:377-401, log sixel image details:
void
sixel_log(struct sixel_image *si) {
    struct sixel_line *sl;
    char s[SIXEL_WIDTH_LIMIT + 1];
    u_int i, x, y, cx, cy;
    
    sixel_size_in_cells(si, &cx, &cy);
    log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
    
    for (i = 0; i < si->ncolours; i++)
        log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
        
    for (y = 0; y < si->y; y++) {
        sl = &si->lines[y];
        for (x = 0; x < si->x; x++) {
            if (x >= sl->x)
                s[x] = '_';
            else if (sl->data[x] != 0)
                s[x] = '0' + (sl->data[x] - 1) % 10;
            else
                s[x] = '.';
        }
        s[x] = '\0';
        log_debug("%s: %4u: %s", __func__, y, s);
    }
}
Enable debug logging:
# Start tmux with verbose logging
tmux -vvv new-session

# Check tmux-client-*.log for sixel parsing info

Supported Terminals

Terminals with sixel support:

xterm

Built-in sixel support (configure with --enable-sixel-graphics).
xterm -ti vt340

mlterm

Native sixel support, optimized for performance.

foot

Modern Wayland terminal with sixel support.

WezTerm

Cross-platform terminal with sixel support.

kitty

Supports kitty graphics protocol (not sixel) but has sixel compatibility mode.

iTerm2

Supports inline images protocol (not sixel directly).

Image Tools

Convert images to sixel:

# Using ImageMagick with sixel support
convert image.png sixel:- | cat

# Using libsixel's img2sixel
img2sixel image.png

# Display in tmux pane
img2sixel image.png > /dev/tty

Image viewers:

  • lsix: Thumbnail browser using sixel
  • viu: Image viewer for terminals
  • chafa: Versatile image-to-text converter with sixel support

Limitations

Sixel support has several limitations:
  1. Color depth: Limited to 256 colors typically (though some terminals support more)
  2. Memory usage: Large images consume significant memory
  3. Performance: Rendering can be slow for complex images
  4. Terminal compatibility: Not all terminals support sixel
  5. tmux limitations: Images may not persist across redraws in all situations
From image-sixel.c:210-215:
if (c > SIXEL_COLOUR_REGISTERS) {
    log_debug("%s: too many colours", __func__);
    return (NULL);
}

Best Practices

Use appropriate image sizes - don’t exceed terminal dimensions
Limit color palette to 256 colors for best compatibility
Test sixel support before deploying image-heavy applications
Provide text fallbacks for terminals without sixel support

Resources