The substitution
Squint at a page of monospaced text and the characters stop being letters. They become patches of grey. @ covers most of its cell with ink; . covers almost none; + sits somewhere in the middle. Order a set of characters by how much of their cell they fill and you have built a greyscale palette out of a keyboard.
A converter then does something almost trivially simple. Divide the source image into a grid. For each cell, average the pixel brightness. Look up the character whose ink coverage best matches that brightness. Print it. Move on. There is no edge detection, no shape recognition, no cleverness — just a lookup, repeated a few thousand times.
Here is that palette made visible. This is the short ramp published by Paul Bourke, each character repeated six times so the gradient reads:
@@@@@@%%%%%%######******++++++======------::::::......
Nothing is being drawn there. It is ten characters, in an order, repeated. The gradient is entirely in your visual system.
The ramp
The ordered character set is called a ramp, and choosing one is the only real aesthetic decision in the whole process. Bourke's two are the ones most converters ship with:
.:-=+*#%@
$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`'.
Both run dark to light. Note the leading space in the short ramp — a blank cell is the brightest value available, and dropping it is why some output has no white in it at all.
More steps is not automatically better. The long ramp gives seventy tonal levels, but the characters in the middle of it — o a h k b d p q — are close enough in weight that the eye reads them as texture rather than gradation, and the result can look noisy. A ten-step ramp produces cleaner, more poster-like output. Which is better depends entirely on whether you want smooth shading or graphic contrast.
The ramps are also font-dependent, and this is under-appreciated. Ink coverage is a property of the typeface, not of the character. In one monospace font % is denser than #; in another it is the reverse. A ramp tuned for one font is approximately right in every other.
The stretch, and the fix
This is the single most common reason ASCII output looks wrong. Pixels are square. Character cells are not — a monospace cell is roughly twice as tall as it is wide, because that is what makes text readable. Sample an image on a square grid and print it into rectangular cells and you have stretched it vertically by about a factor of two.
The correction is to sample half as often in the vertical direction — take one row of characters for every two rows of the grid you would otherwise use. Bourke states it exactly that way, and notes that if you know the font's aspect ratio precisely you can compute the exact sampling instead of assuming 2:1.
Both blocks below were generated from the same shaded sphere, thirty characters wide. Only the number of rows differs.
30 × 30 — naive
#####**+
#%%%%%%###**+=
%@@@@%%%%%##**+=
%@@@@@@@@%%%###**+=-
%@@@@@@@@@%%%%##**++=:
%@@@@@@@@@@@%%%##**++=-:
@@@@@@@@@@@@%%%###**+=-:
%@@@@@@@@@@@@%%%###**+==-:
#@@@@@@@@@@@@%%%%##***+==-::
%@@@@@@@@@@@@%%%###**++==-::
%@@@@@@@@@@@%%%%###**++==-::
#%@@@@@@@@@@@%%%%##***++==-:::
#%%@@@@@@@@@%%%%###**+++=--:::
#%%@@@@@@@%%%%%###***++==-::::
#%%%%@@@%%%%%%####**+++==-::::
##%%%%%%%%%%%####***++==--::::
*#%%%%%%%%%%####***++===-:::::
*###%%%%%######***+++==--:::::
+*###########****+++==--::::::
**########*****+++==--::::::
+****##******++++==---::::::
=+*********++++===---:::::::
=+++***++++++===---:::::::
=+++++++++====--::::::::
-===========---:::::::::
:--=====----::::::::::
::------::::::::::::
::::::::::::::::
::::::::::::::
::::::::
30 × 15 — vertical halved
%%%%###**+
%@@@@@@%%%%##**++=
%@@@@@@@@@@%%%##**++=-
%@@@@@@@@@@@@%%%###**+==-:
%@@@@@@@@@@@@%%%%##**++==-::
%@@@@@@@@@@@%%%%###**++==-::
#%%@@@@@@@@%%%%%###**++==--:::
#%%%%%%%%%%%%%###***++==--::::
*##%%%%%%%%####****++==--:::::
*##########****+++===--:::::
++**********++++===--:::::::
=+++++++++++===---::::::::
-========----:::::::::
:::--:::::::::::::
::::::::::
The left block is a correct sampling of a circle. It reads as an egg. The right block throws away half the rows and reads as a sphere. Discarding information is the fix.
Width is the only resolution control you have
In a bitmap you set pixel dimensions. In ASCII art you set columns, and that number is your resolution. Eighty columns is the historical default because it is a terminal width, and it is genuinely tight: eighty tonal samples across a whole face is not many. Two hundred columns resolves an eye. Four hundred resolves an eyelash and produces a block of text nobody can display without scrolling.
Every choice past that point is a consequence. Wider means more detail and smaller apparent characters. Narrower means the piece survives being pasted into a chat window. There is no equivalent of “zoom in” — the only way to see more is to re-render at a higher column count, because the characters were never a picture in the first place.
Why photographs disappoint and logos don't
A ten-step ramp is ten tones. A typical photograph carries 256 levels per channel. Compressing 256 into 10 is a brutal quantisation, and what survives it is large areas of consistent value. What does not survive is fine tonal gradation, small text, thin lines, and any detail that depends on colour difference rather than brightness difference — a red logo on a green field can have almost identical luminance and vanish completely.
So the subjects that work are the ones a woodcut would work for: strong silhouettes, hard-lit faces, high-contrast logos, single objects against plain backgrounds. Busy scenes come out as static. If a source image looks good converted to five-level posterisation in an image editor, it will convert well; if it turns to sludge there, no ramp will save it.
The other lever is pre-processing. Raising contrast and clipping the black and white points before conversion pushes the source's tones toward the ends of the ramp, and almost always produces a punchier result than feeding in a flat original. Converting to greyscale first also lets you choose how colours map to brightness, which is a real decision rather than a default — the arithmetic behind that mapping is the same one covered in HEX, RGB and CMYK explained.
The 95 characters, and what came after
ASCII proper is small. The standard defines 128 code points, of which 95 are printable — hex 20 through 7E, space through tilde — and 33 are control characters that were meant to operate teleprinters rather than appear on a page. It was first standardised in 1963. That is the entire palette classic ASCII art had, and the constraint is a large part of the form's character.
Almost everything called “ASCII art” today isn't, strictly. Three extensions matter:
Block elements
Unicode's block-drawing characters — full block, half blocks, quadrants — fill their cell geometrically rather than as glyph shapes. They give flat, clean areas with no letterform texture, and using upper and lower half-blocks with different foreground and background colours effectively doubles the vertical resolution of a terminal. This is how most modern terminal image viewers work.
Braille patterns
The Unicode Braille Patterns block occupies U+2800 to U+28FF — 256 code points, all assigned — and each character encodes an 8-dot cell arranged 2 wide by 4 tall. Which means one character can carry eight independently controllable dots. Render into Braille and a single text cell holds 2×4 sub-pixels instead of one tonal average, an eightfold jump in spatial resolution. It is the reason recent terminal art looks so much sharper than the 1990s kind. It is also, obviously, a hijacking of a writing system for blind readers, and it is meaningless to a screen reader.
ANSI colour
Adding escape sequences for foreground and background colour to each cell decouples value from character: you can print a dense character in a dark colour or a light one and get two different tones from the same glyph. This is the lineage of BBS-era ANSI art and the .nfo file, which is a genuinely distinct tradition — hand-drawn, not converted, and closer to typography than to image processing.
Where it still holds up
ASCII art persists in the places where an image cannot go: terminal splash screens and login banners, README headers, plain-text email, log output, and anywhere a diagram has to survive being copied into a chat message. The property that keeps it alive is that it is text — it diffs, it greps, it pastes, it never 404s, and it needs no renderer beyond a monospace font.
It has a decorative life too. Monospace-and-density is a real graphic idiom, and portraits or lettering rendered as character grids print well precisely because the form is already high-contrast and resolution-independent in the ways print rewards. If you take one that way, render the text to an image rather than trusting a font substitution downstream, and give the result the pixel dimensions its final printed size needs — DPI vs PPI, explained covers that arithmetic, and the character grid's own proportions will decide which of the standard frame ratios it sits in without cropping.
If you want to try the mechanism rather than read about it, our image to ASCII converter runs the whole loop in the browser — set the column count, pick a character set, and watch what happens to a photograph when you take away 246 of its 256 tones.
Written July 2026. The sphere specimens were generated for this page, not traced.
Primary sources: Paul Bourke, “Character representation of grey scale images” — both character ramps quoted above, the statement that they run from black to white, the note on stretching caused by the rectangular aspect ratio of characters, and the correction of sampling half as often in the vertical direction (with exact sampling possible where the font's aspect ratio is known); ASCII — 128 code points comprising 95 printable characters (hex 20 to 7E) and 33 control characters, and first standardisation in 1963; Braille Patterns — the block range U+2800 to U+28FF, all 256 code points assigned, and the 8-dot cell in a 2×4 arrangement. The two sphere blocks were rendered by a short script written for this page (lambert shading, ramp .:-=+*#%@, ambient floor) so that the only variable between them is the row count. No specific character-cell aspect ratio is claimed as universal — roughly 1:2 is a convention of monospace type design, and exact values differ by font.