NAME

Imager::File::SIXEL - read and write SIXEL images with Imager

SYNOPSIS

use Imager;
use Imager::File::SIXEL;

# Show an image in a terminal that supports SIXEL.
my $img = Imager->new(file => 'photo.png')
  or die Imager->errstr;
$img->write(fh => \*STDOUT, type => 'sixel')
  or die $img->errstr;

# Encode into a Perl string instead.
my $sixel = '';
$img->write(data => \$sixel, type => 'sixel', sixel_max_colors => 64)
  or die $img->errstr;

# Write a SIXEL file and read it back.
$img->write(file => 'photo.six')
  or die $img->errstr;
my $decoded = Imager->new(file => 'photo.six')
  or die Imager->errstr;

# Read every image of a file that holds several.
my @images = Imager->read_multi(file => 'recording.six', type => 'sixel')
  or die Imager->errstr;

# Write several images into one file.
Imager->write_multi({ file => 'slides.six', type => 'sixel' }, @images)
  or die Imager->errstr;

DESCRIPTION

SIXEL is the bitmap graphics format of the DEC VT200 to VT300 series of terminals. Many current terminal emulators understand it, among them xterm, mlterm, foot, WezTerm, Contour, mintty, Windows Terminal, iTerm2 and Konsole, which makes it a common way to show images inside a terminal.

This module adds the file type sixel to Imager:

The encoder is written in C and is fast enough to drive animations, depending on the settings and the image ("PERFORMANCE").

You use this module through Imager's usual methods read(), read_multi(), write() and write_multi() with type => 'sixel'. The module itself has no functions or methods that you call directly.

Loading the module

use Imager;
use Imager::File::SIXEL;

Loading the module registers with Imager:

Whether you need the use line:

Detection by content only works when the data starts with the 7-bit introducer ESC P. Data that starts with anything else, such as a recording of a terminal session that begins with text, or the 8-bit introducer byte 0x90, is not recognized by its content. Read it from a file whose name ends in .six or .sixel, or pass type => 'sixel'.

DOCUMENTATION

The documentation of this module has three pages:

TERMINOLOGY

QUICK REFERENCE

Read options at a glance

Pass these to $img->read(...), Imager->new(file => ...) or Imager->read_multi(...).

| Option | Values | Default | Effect | | ------------------ | ------------- | ------- | ------------------------------- | | page | 0, 1, 2, ... | 0 | which image of the data to read | | allow_incomplete | false or true | false | accept data that is cut off |

Details: page, allow_incomplete.

Write options at a glance

Pass these to $img->write(...) or Imager->write_multi(...).

| Option | Values | Default | | ----------------------- | -------------------------------- | ----------- | | sixel_palette | 'adaptive' or 'webmap' | 'adaptive' | | sixel_max_colors | an integer from 1 to 256 | 256 | | colors | array ref of 1 to 256 colors | (none) | | sixel_dither | 'diffusion', 'ordered' or 'none' | 'diffusion' | | sixel_alpha_threshold | an integer from 0 to 255 | 128 | | sixel_pan | an integer from 1 to 2147483647 | 1 | | sixel_pad | an integer from 1 to 2147483647 | 1 |

| Option | Effect | | ------------------------ | ------------------------------------------------------ | | sixel_palette | computed palette or the fixed 216-color webmap palette | | sixel_max_colors | largest number of colors the encoder picks itself | | colors | your own palette | | sixel_dither | how colors missing from the palette are approximated | | sixel_alpha_threshold | which pixels of an image with alpha are transparent | | sixel_pan, sixel_pad | pixel aspect ratio (height to width) |

Details: "Write options". The sixel_ options are stored on the image as tags; colors is not. See "Write options are stored on the image".

Tags at a glance

| Tag | Value | | -------------- | -------------------------------------------------------- | | i_format | 'sixel' | | sixel_pan | pixel aspect ratio, height part (1 for current encoders) | | sixel_pad | pixel aspect ratio, width part (1 for current encoders) | | i_incomplete | 1 if the image was cut off (only with allow_incomplete) |

Details: "Tags set when reading".

EXAMPLES

Imager::File::SIXEL::Examples shows every feature in use, with figures that show what each write option does to the image. Its examples, by topic:

Showing and saving images

Reading SIXEL data

Changing the look of the output

Options, errors and animation

READING

my $img = Imager->new;
$img->read(file => 'image.six', type => 'sixel')
  or die $img->errstr;

my @images = Imager->read_multi(file => 'stream.six', type => 'sixel')
  or die Imager->errstr;

Any input source that Imager supports works: file, fh, data, callback, see Imager::Files.

What is read

The decoder searches the input for SIXEL images and skips everything else: text, escape sequences and other control strings before, between and after the images. A recording of a terminal session can therefore be read directly. Each SIXEL image becomes one Imager image:

"READING" in Imager::File::SIXEL::Format describes in detail which data is accepted and how the decoded image is built.

Read options

page

Which image to read with read() or Imager->new(...), counting from 0.

Values: an integer from 0 to 2147483647.

Default: 0, the first image.

read_multi() ignores this option and returns every image.

allow_incomplete

Whether to accept an image cut off by the end of the input (truncated).

Values: any Perl value, taken as true or false.

Default: false.

If false, such an image makes the read fail with premature end of SIXEL data. If true, the part that was read is returned, and the tag i_incomplete is set to 1 on that image. If that part has no pixels and declares no size, the read still fails, with SIXEL image contains no pixels. allow_incomplete works the same way with read_multi(); there, a cut-off image is always the last one, and the images before it are returned complete.

Tags set when reading

Resource limits

Images larger than the limits set with Imager->set_file_limits are rejected, whether the size is declared in the SIXEL data or results from the pixels painted. By default, Imager limits only the memory of an image, to 1 GiB. The decoder also stops with the error SIXEL data paints too many pixels when the data paints the same pixels over far more often than any real image does; the exact limit is in "Resource limits" in Imager::File::SIXEL::Format.

Before reading untrusted data, set file limits that fit your application and limit the size of the input you accept, as shown in "Read untrusted data safely (limit memory and time)" in Imager::File::SIXEL::Examples.

WRITING

$img->write(file => 'image.six')
  or die $img->errstr;

my $sixel = '';
$img->write(data => \$sixel, type => 'sixel', sixel_dither => 'ordered')
  or die $img->errstr;

Imager->write_multi({ file => 'frames.six', type => 'sixel' }, @images)
  or die Imager->errstr;

Any output target that Imager supports works: file, fh, data, callback, see Imager::Files. With file, the type is taken from the extension .six or .sixel; with the other targets, pass type => 'sixel'.

What is written

Each image is written as one SIXEL image in plain 7-bit ASCII, with the image size and the pixel aspect ratio in its raster attributes and at most 256 color registers. Images with an alpha channel are marked so that unpainted pixels are transparent. write_multi() writes the images one after another, with nothing in between.

SIXEL expresses colors as percentages, 101 levels per channel instead of 256, so every color is rounded to the nearest level. Writing an image that was read from SIXEL data again loses nothing further, as long as it is written with its own colors.

"WRITING" in Imager::File::SIXEL::Format describes the exact output and the color precision in detail.

Write options

Every option can be omitted. Invalid values make the write fail with a message that names the option, before any SIXEL data is written (see "DIAGNOSTICS"). With write_multi(), the options of all images are checked before the first image is written.

Integer options take a decimal integer: an optional + or - followed by digits, with nothing else, not even spaces. Leading zeros are allowed. A Perl number works if it turns into such a string, so 16 and 16.0 are both 16, but the strings '16.0', ' 16', '0x10' and '1e2' are invalid. (One exception: a single trailing line break, as in "16\n", is accepted, because Imager stores such a value as an integer.)

Keyword options must match exactly, including case and spaces: 'ordered' is valid, 'Ordered' and 'ordered ' are not.

Passing undef for a sixel_ option removes the setting stored on the image, so the default applies (see "Write options are stored on the image").

Do not pass a reference other than an array reference as the value of a sixel_ option:

With file, Imager creates or empties the file before the options are checked, so a write that fails because of an invalid option leaves an empty file behind.

sixel_palette

Which palette to use when colors is not given. With 'webmap', the webmap palette is always used. With 'adaptive', an image that has few enough colors is written with its own colors instead; see "How the palette is chosen".

Values: 'adaptive' or 'webmap', in lowercase; the comparison is case sensitive.

Default: 'adaptive'.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

Figure: "Use the fixed webmap palette (sixel_palette)" in Imager::File::SIXEL::Examples.

sixel_max_colors

The maximum number of colors (color registers) of a palette that the encoder picks itself: the adaptive palette, or the image's own colors.

Values: an integer from 1 to 256.

Default: 256.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

It limits the adaptive palette, and it decides whether an image is written with its own colors (rules 3 and 4 of "How the palette is chosen"). It does not limit the webmap palette or a palette passed with colors. Fewer colors give smaller SIXEL data and faster drawing, at the cost of quality.

Figure: "Reduce the number of colors (sixel_max_colors)" in Imager::File::SIXEL::Examples.

colors

Your own palette.

Values: a reference to an array of 1 to 256 colors.

Default: none.

Stored on the image: no.

Each entry is one of:

The alpha value of a color is ignored. Imager::Color::Float objects are not accepted.

$img->write(data => \$sixel, type => 'sixel',
            colors => ['#000000', [255, 255, 255], 'red']);

colors takes precedence over sixel_palette and sixel_max_colors. Every pixel is written in one of these colors, approximated as set by sixel_dither.

Figure: "Use your own (custom) palette (colors)" in Imager::File::SIXEL::Examples.

sixel_dither

How pixels whose color is not in the palette are written.

Values: 'diffusion', 'ordered' or 'none', in lowercase; the comparison is case sensitive.

Default: 'diffusion'.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

With 'diffusion' and 'ordered', the nearest palette color is looked up with each channel reduced to 64 levels instead of 256, which is faster; the dithering compensates for the small error this adds. Images written with their own colors (rules 3 and 4 of "How the palette is chosen") are never dithered, because every pixel is in the palette.

Figure: "Choose how colors are approximated (sixel_dither)" in Imager::File::SIXEL::Examples.

sixel_alpha_threshold

Which pixels of an image with an alpha channel are left transparent.

Values: an integer from 0 to 255.

Default: 128.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

Pixels whose alpha value is below the threshold are not painted, so the terminal background shows through. All other pixels are painted in their color, ignoring their alpha value. With 0, every pixel is painted, including fully transparent ones, in the color stored in their red, green and blue channels; areas of a new image that were never drawn on are black. The option has no effect on images without an alpha channel.

Figure: "Images with transparency (sixel_alpha_threshold)" in Imager::File::SIXEL::Examples.

sixel_pan

The height part of the pixel aspect ratio written into the image. Each pixel is sixel_pan / sixel_pad times as high as it is wide.

Values: an integer from 1 to 2147483647.

Default: 1.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

Terminals that honor the ratio stretch the image accordingly; others ignore it. Images read from SIXEL data carry the tags sixel_pan and sixel_pad, so they keep their aspect ratio when written again.

Figure: "Non-square pixels (sixel_pan, sixel_pad)" in Imager::File::SIXEL::Examples.

sixel_pad

The width part of the pixel aspect ratio; see sixel_pan.

Values: an integer from 1 to 2147483647.

Default: 1.

Stored on the image: yes, later writes use it too; see "Write options are stored on the image".

Write options are stored on the image

This module stores every write option whose name starts with sixel_ as a tag of the same name on the image before writing it, following the convention of Imager's own file formats. This has three consequences:

With write_multi():

The colors option is not stored.

How the palette is chosen

The first rule that applies decides:

  1. If colors is given, that palette is used.
  2. If sixel_palette is 'webmap', the webmap palette is used.
  3. If the image is a paletted image whose color table has at most sixel_max_colors entries, its color table is used: color register n holds color table entry n, except that an entry that rounds to the same SIXEL percentages as an earlier entry uses the register of that earlier entry. This is the fastest way and loses nothing apart from the rounding to SIXEL percentages. Use it to control the palette yourself, see "Use the palette of a paletted image" in Imager::File::SIXEL::Examples.
  4. If the painted pixels of the image (see sixel_alpha_threshold) have at most sixel_max_colors different colors, these colors are used. The colors are counted after they are rounded to SIXEL percentages, so two colors that round to the same percentages count once and share a color register.
  5. Otherwise an adaptive palette of at most sixel_max_colors colors is computed.

In rules 3 and 4 every pixel color is in the palette, so sixel_dither has no effect.

Image types and bit depth

Images of any type and sample size can be written:

DISPLAYING IMAGES IN A TERMINAL

The terminal draws SIXEL data at the text cursor. Where the cursor is afterwards depends on the terminal: usually on the line below the image, in some terminals on the last text line the image covers. Print a line break after the image so that the next output starts below it.

Does my terminal support SIXEL?

A terminal that supports SIXEL answers the primary device attributes request, ESC [ c, with a list of numbers separated by semicolons, and one of these numbers is 4. To check by hand, run this in bash or zsh:

printf '\e[c'; read -r -s -t 1 -d c answer; echo "${answer#*\[}"

It prints the answer, for example ?62;4;6;22 or ?64;1;4;22. Both of these contain the number 4 (the 4 inside 64 does not count), so the terminal supports SIXEL. If only an empty line is printed, the terminal did not answer within one second. Some terminals need SIXEL switched on. xterm, for example, must emulate a VT340 and, for images with more than 16 colors, needs more color registers than the 16 of the VT340:

xterm -ti vt340 -xrm 'XTerm*numColorRegisters: 256'

Terminals with fewer than 256 color registers

Such terminals draw an image with wrong colors when the image uses more colors than they have registers. For them, set sixel_max_colors to their register count, do not use the webmap palette, which needs 216 registers, and do not pass more colors with colors than the terminal has registers.

ANIMATION

The encoder is fast enough for real-time animation. Encode each frame into a string and draw it at a fixed position:

use Imager;
use Imager::File::SIXEL;
use Time::HiRes qw(sleep time);

binmode STDOUT, ':raw';
STDOUT->autoflush(1);

print "\e[?25l\e[2J";    # hide the cursor, clear the screen
my $fps = 30;
my $start = time;
for my $n (0 .. 299) {
  my $frame = render_frame($n);    # your code; returns an Imager image

  my $sixel = '';
  $frame->write(data => \$sixel, type => 'sixel',
                sixel_palette => 'webmap', sixel_dither => 'ordered')
    or die $frame->errstr;

  # Move the cursor to the top left corner and draw the frame
  # as one synchronized update.
  print "\e[?2026h\e[H", $sixel, "\e[?2026l";

  my $wait = $start + ($n + 1) / $fps - time;
  sleep $wait if $wait > 0;
}
print "\e[?25h\n";    # show the cursor again

ESC [ ? 2026 h and ESC [ ? 2026 l begin and end a synchronized update, which keeps the terminal from showing half-drawn frames. Terminals that do not support it ignore these sequences.

The frame must fit into the terminal window with at least one text line to spare below it. Otherwise the terminal scrolls after each frame and the frames jump. The script examples/sixel-animate.pl in the distribution is a complete version of this loop.

Recommendations:

PERFORMANCE

The encoder builds the palette from a color histogram, maps the pixels through a lookup table of nearest palette colors, and writes the SIXEL data band by band. Each band's pixels are grouped by color in linear time, split into runs of columns, and packed into as few passes over the band as possible (see "THE SIXEL FORMAT" in Imager::File::SIXEL::Format). In fully painted bands, the first pass paints whole columns that later passes paint over, so that it compresses into long repeats. On the author's test images, the SIXEL data for the same pixels was typically 4 to 20 percent smaller than that of libsixel 1.10.5 with adaptive palettes, and more with fixed palettes. For very simple images both are about the same size.

The table shows the times for one complete $img->write(data => \$buffer, type => 'sixel') call, measured with examples/sixel-bench.pl on an Intel Core i5-12600K with Perl 5.38 and Imager 1.033, in milliseconds per call (ms), the resulting frames per second (fps) and the size of the SIXEL data in bytes. The "Synth" columns are for the benchmark's default image, smooth gradients with noise. The "Photo" columns are for snake.png from the libsixel distribution, a photograph with fine detail, which is about the hardest case for the encoder.

| Size | Dither | Palette | Synth ms | Synth fps | Synth bytes | Photo ms | Photo fps | Photo bytes | | ------- | --------- | -------- | -------: | --------: | ----------: | -------: | --------: | ----------: | | 256x256 | diffusion | adaptive | 2.1 | 472 | 70607 | 5.5 | 182 | 98613 | | 256x256 | ordered | adaptive | 1.5 | 685 | 65440 | 3.7 | 270 | 110899 | | 256x256 | none | adaptive | 1.7 | 603 | 65944 | 4.8 | 207 | 89583 | | 256x256 | ordered | webmap | 0.9 | 1155 | 36965 | 1.5 | 680 | 52080 | | 256x256 | none | webmap | 0.6 | 1629 | 9256 | 1.5 | 673 | 25949 | | 192x128 | diffusion | adaptive | 0.9 | 1073 | 27619 | 3.1 | 324 | 45451 | | 192x128 | ordered | adaptive | 0.7 | 1483 | 26704 | 2.4 | 419 | 49446 | | 192x128 | none | adaptive | 0.8 | 1307 | 26971 | 2.9 | 342 | 42365 | | 192x128 | ordered | webmap | 0.5 | 2200 | 15222 | 0.8 | 1277 | 22875 | | 192x128 | none | webmap | 0.3 | 3814 | 3885 | 0.8 | 1280 | 12476 | | 640x480 | diffusion | adaptive | 8.9 | 112 | 373095 | 15.4 | 65 | 341301 | | 640x480 | ordered | adaptive | 5.8 | 172 | 329641 | 8.7 | 115 | 389983 | | 640x480 | none | adaptive | 6.4 | 157 | 329569 | 11.4 | 88 | 267180 | | 640x480 | ordered | webmap | 3.3 | 305 | 160163 | 4.3 | 231 | 203313 | | 640x480 | none | webmap | 3.0 | 335 | 49565 | 4.2 | 236 | 73404 |

Every setting encodes all three sizes of both images at more than 60 frames per second on this machine. At 640 x 480, the default settings leave the least headroom, which is one reason why ordered dithering is recommended for animations. To measure your own machine and images, run examples/sixel-bench.pl, which accepts --file. Decoding a 640 x 480 image takes about 5 milliseconds.

DIAGNOSTICS

Failures are reported through $img->errstr or Imager->errstr as usual; see "Handle errors" in Imager::File::SIXEL::Examples. The messages specific to this module are listed here. N stands for a number. In the unknown ... value messages, ... stands for the value you passed; in file size limit - ..., for Imager's explanation.

Messages when reading

Messages when writing

Messages when reading or writing

LIMITATIONS

EXAMPLE PROGRAMS

The examples directory of the distribution holds three complete programs. Each one prints its options with --help.

INSTALLATION

Requirements: Perl 5.24 or later, Imager 1.013 or later with its headers, and a C compiler. With cpanm:

cpanm Imager::File::SIXEL

From a source checkout:

cpanm --installdeps .
perl Makefile.PL
make
make test
make install

SEE ALSO

Imager::File::SIXEL::Examples, Imager::File::SIXEL::Format.

Imager, Imager::Files, Imager::ImageTypes.

The SIXEL chapter of the VT330/VT340 Programmer Reference Manual: https://vt100.net/docs/vt3xx-gp/chapter14.html.

libsixel, the reference implementation of SIXEL encoding and decoding: https://github.com/libsixel/libsixel.

The source code: https://github.com/davenonymous/perl-imager-sixel.

AUTHOR

davenonymous dave@davenonymous.com

COPYRIGHT AND LICENSE

Copyright (C) 2026 davenonymous.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.