This small Python program converts ANSI art to raster images. It simulates a PC's text mode using diﬀerent VGA soft fonts (or EGA, or the CGA ROM font) and renders colourful BBS graphics, aﬀectionately known as ANSI (after the
ANSI.SYS MS-DOS driver that rendered them back in the day). So-called ‘ANSI’ graphics are text ﬁles in one of the IBM PC extended ‘ASCII’ encodings. The ﬁles include terminal directives for the MS-DOS terminal driver ANSI.SYS, which implements a small subset of the ANSI X.364 standard for Control Sequences for Video Terminals.
With the special characters in the various IBM PC character sets, box-drawing and crude graphics were feasible. Using ANSI, these graphics could also be coloured and even repositioned for simple animations.
Such ﬁles were commonly used on Bulletin Board Systems (BBS) and other similar software and, despite (or perhaps because) of the limitations of the format, eventually developed into an art form.
ANSI.SYS only supported a small subset of ANSI X.364, and common sense would have it that modern virtual terminals would be able to display these graphics unhindered. Unfortunately, there are three obstacles in this.
- The encodings (‘codepages’) themselves need a certain amount of expertise to render the intended characters on modern (usually Unicode-based) terminals.
- Modern fonts are designed with legibility in mind, and have diﬀerent metrics and proportions to the low-resolution bitmap fonts of the Eighties. As designers used these characters for various purposes (aside from their original ones), we should only reproduce these ﬁles using their original fonts.
- Most modern virtual terminals are either incapable of displaying ANSI X.364 escape sequences (the terminal emulator of Windows Vista is one example), or support a subtly diﬀerent set of commands and features. These factors limit the rendering accuracy of ANSI graphics.
One of my mantras is: nothing is worth doing unless it's reusable and generalised. To do this,
renderconsole, my generalised, abstract terminal rendering framework for Python. This Python package is able to render any terminal control language and convert it to any format. At the moment, only the
ANSI.SYS subset of ANSI X.364 is supported. The library outputs XPM ﬁles by default, but it can use the Python Imaging Library (PIL) to save any bitmap format your heart desires.
Until the advent of the VGA, IBM PCs had a range of 256 visible glyphs per font1.
The IBM MDA, the ﬁrst display adapter (a text-only, monochrome device) oﬀered a 9x14 font, but this is not (as of yet) supported by this program. The IBM CGA supported 8x8 fonts (200 scanlines). The font was in ROM and could not be changed in software. The IBM EGA was the ﬁrst commonly used IBM card to support soft fonts of various sizes, the usual being 8x14. It also included the CGA 8x8 font for compatibility. The IBM VGA contained all the CGA and EGA fonts and added an 8x16 font. This is rendered as 9x16 at the default text resolution of 720x480, which is still with us.
EGA and VGA fonts are simple arrays of bytes. Each character from 0 to 255 is represented by 8, 14, or 16 bytes. The least signiﬁcant bit of each byte represented the rightmost pixel of each character cell. This resulted in fonts of 2,048, 3,584 and 4,096 bytes. This program supports any such uncompressed, unencoded font without metadata, including fonts with arbitrary character heights (it will do its best to guess).
A default VGA font for CP437, the US codepage, is built into the program.
renderconsole and the
ansi2img program are bundled together as part of the
renderconsole distribution. Get it while it's fresh!
It's all available under the terms of the GNU Public License. The example ﬁles in the distribution are © their respective artists.
Here's an example kindly contributed by a reader of my previous homepage and modiﬁed for simplicity:
# Create a memory ﬁle to display "dynamically" on the web with # no ﬁles written to disk. png = StringIO()
font = renderconsole.DefaultFont() framebuﬀer = renderconsole.Framebuﬀer(80, 25, growable=True) term = renderconsole.ANSITerminalEmulator(framebuﬀer) screen = renderconsole.PNGWriter(framebuﬀer, font, stream=png)
# Get data in mem ﬁle. This is the image binary data. Add HTTP headers # to display on the web. Or just use a standard ﬁle open for writing # and you'll already have data saved on it at this point. png.getvalue()
If you'd rather not install anything on your computer, you can use the ANSI converter online. The online version does everything the oﬄine one does, except you're limited to the few fonts it has (CGA, EGA, and VGA, plus Greek versions2).
- 1. The VGA can display up to 512 glyphs by using up more video RAM and turning oﬀ bright backgrounds and blinking. This, however, was such a rare feature to see used that no BBS software could assume its presence, and there was never a standard for what those upper 256 characters had to look like, so portability was nil.
- 2. I have lots of ANSI work with characters coded in Greek CP737, and I had all those fonts lying about anyway.