Image to ASCII Art: How Brightness Becomes Characters
Turn any photo into ASCII art. Learn how brightness maps to characters, how output width changes detail, and why everything runs locally in your browser.
Image to ASCII Art: How Brightness Becomes Characters
ASCII art looks like magic until you see the rule behind it: a picture is just a grid of brightness values, and each value gets swapped for a character that happens to take up about the same amount of ink. Dark spots become heavy glyphs. Light spots become near-empty ones. Stack those characters in a monospace grid and your eye reassembles the photo from the texture, the same way a newspaper halftone fools you with dots.
This post walks through the mechanics of converting an image to ASCII: how brightness drives character choice, why output width is the one knob that matters most, and why the whole thing can run on your own machine without uploading a single byte. You can follow along in the Image to ASCII Art converter as you read.
Each Pixel Block Becomes One Character
The core idea is a mapping. The converter samples the image down to a small grid — say 80 columns wide — and treats each cell as a single brightness reading from 0 (black) to 255 (white). It then picks a character from a ramp: an ordered string of glyphs sorted from dense to sparse.
A classic ramp is @#%*+=-:. followed by a space. The @ sign fills nearly its whole cell with ink, so it stands in for the darkest pixels. The period barely marks the page, so it represents the brightest areas just before pure white, which is a blank space. Everything in between fills the gray range in order. Brightness 0 picks the first glyph, brightness 255 picks the last, and a linear scale maps every value in between.
Crucially, brightness is not the raw average of red, green, and blue. Human eyes weight green far more than blue, so a good converter uses a luma formula — Rec. 709 weights of roughly 0.21 red, 0.72 green, 0.07 blue. That keeps a bright yellow from reading as "dark" just because its blue channel is low. The tool also pre-multiplies semi-transparent pixels against white, so a logo with a cut-out background does not collapse into a black smear.
So the pipeline is short: decode the image, shrink it to a character grid, read each cell's luma, and index into the ramp. Repeat for every cell, join the rows with newlines, and you have ASCII art.
Dense Glyphs for Dark, Sparse Glyphs for Light
The direction of the ramp depends on where the art will live. On a dark terminal, ink is light text on a black field, so a dense glyph like @ adds light and should map to bright pixels — wait, no, this is exactly the trap. The honest mental model is "how much ink fills the cell," and on a dark terminal a glyph's ink is bright. So for a black background you usually want dark image pixels to stay empty (space) and bright pixels to fill with @.
That is why an invert toggle exists. By default many converters assume a light page: dark image pixels become dense glyphs (@, #), bright pixels become sparse ones (., space). Flip invert and the whole ramp reverses. If your art comes out looking like a photo negative, you targeted the wrong background — toggle invert and it snaps back.
The practical rule:
- Pasting into a white README or a printed page? Keep the default. Dark pixels get heavy glyphs.
- Pasting into a dark terminal or a black-themed site? Turn invert on. Now bright pixels get heavy glyphs and shadows stay empty.
Get this backwards and the image is still "there," just tonally flipped — which is occasionally a cool effect, but rarely the one you wanted.
Resolution and Width: The Detail Tradeoff
Width is the single most important setting. It sets how many columns of characters the image is sampled into, and that fixes the resolution of your final art. More columns means each pixel block is smaller, so finer detail survives. Fewer columns means each block swallows more of the image, giving you a chunky, posterized look.
There is a vertical catch. A monospace character cell is about twice as tall as it is wide. If you sampled a 100×100 image into a 100×100 character grid, the result would look stretched to nearly double its height. So converters halve the row count automatically: 100 columns of a square image produces roughly 50 rows. That is why a "width 100" portrait lands around 100×50 characters.
There is also an upper bound to chasing detail. ASCII art only has as many tonal steps as your ramp has characters — a 10-glyph ramp gives 10 shades, a 32-glyph ramp gives 32. Past a certain width you are no longer adding real detail, just spreading the same handful of tones across more cells. A 4000px source and an 800px source sampled to 80 columns produce identical output, because both get downscaled to the same small grid first.
A Worked Example: One Photo, Three Widths
I ran the same headshot through at three settings to feel the tradeoff. Here is what happened at each width.
Width 40. The face is a vague oval of gradient. You can tell it is a portrait and roughly where the eyes sit, but the mouth is a single dark glyph and any text in the background is gone. The whole thing is about 40×20 characters — small enough to drop into a chat message without it wrapping. Good for a tiny avatar, useless for likeness.
Width 100. Now the eyes, nose, and mouth resolve. Skin midtones show shape because there are enough cells to grade from . through + to #. The output is roughly 100×50 characters — perfect for the top of a README, wrapped in a fenced code block so the monospace grid holds. This is the sweet spot for most photos.
Width 220. Strands of hair and the texture of the sweater appear. But the file is now around 24 KB of text, and on a phone the lines wrap unless the viewport is wide. It is gorgeous on a desktop terminal and unreadable on mobile. I shipped the width-100 version and kept width 220 for the print-only PDF.
The lesson from sitting with it: width is not "more is better," it is "match the destination." A --help banner wants 60 columns so it fits an 80×24 terminal. A README mosaic wants 100. A poster wants 220. Pick the width for where the text will be read, not for maximum fidelity.
Monospace Output and Local Processing
Two things make or break the result after conversion.
First, the output must be monospace. Every glyph has to occupy an identical cell width or the columns drift and the picture dissolves. Paste ASCII art into a normal Markdown paragraph and a proportional font will smear it instantly — an i is narrower than an m, and the alignment is gone. Always wrap the output in a fenced code block or a <pre> tag so the browser renders it with a fixed-width font. If your terminal font is closer to 1.5:1 than 2:1 and the art looks slightly tall, a CSS line-height: 0.8 on the <pre> tightens it.
Second, the conversion runs entirely in your browser. The image is read into a Blob, drawn onto an off-screen canvas, and its pixels are read back with getImageData — all client-side. There is no upload, no analytics ping carrying your filename, no temporary server copy. You can confirm it yourself: open the DevTools Network tab, drop a file, and watch zero requests fire. That matters when the "image" is a screenshot of something private — a dashboard, a contract, a chat log. The pixels never leave the tab, and closing it leaves nothing behind.
If you want to shrink a heavy source before converting, run it through the local image compressor first — though since the converter downscales internally to a few hundred pixels anyway, this only saves decode time on very large files, not output quality.
Where to Go From Here
ASCII art from a photo is the playful end of a larger habit: treating images and text as two views of the same data. Once you are comfortable mapping brightness to glyphs, custom ramps open up — spell a name light-to-dark, or use Unicode block elements ░▒▓█ for a smooth tiled look instead of punctuation.
Try a logo at width 80 with a 10-level ramp for crisp line art, then the same logo at width 120 with a 32-level ramp and watch the gradients fill in. The Image to ASCII Art tool keeps all of it local, so experiment freely with anything on your screen. When you need pure typed-out banner text instead of a converted picture, the ASCII Art Generator renders words as figlet-style letterforms — a different job, same monospace canvas.
Made by Toolora · Updated 2026-06-13