~/charsets/braille · 2×4 dot cells

Image to ASCII braille art

Braille turns each text cell into eight independently lit dots. That gives a photo more shape, edges and texture than a classic character ramp at the same width — still as copyable Unicode text, still converted entirely in your browser.

semaphore --charset braille --dither – □ ✕
[ how_braille_ascii_works ]

How does image to braille art work?

Unicode reserves 256 braille characters, U+2800 through U+28FF: every on/off pattern of a 2×4 dot cell. Semaphore reads eight luminance samples for that cell, spreads the threshold error across the dot grid, and writes one code point. A classic ramp still has one sample per character at the same width. The engine excerpts and the before/after figures are in Unicode braille is an 8-pixel bitmap font.

01 sample 2×4

Each output character represents two source columns and four source rows, preserving small features without making the text grid wider.

02 dither luminance

Floyd–Steinberg dithering moves threshold error into nearby dots. Smooth shadows become intentional dot patterns instead of flat patches.

03 emit Unicode

The final output is plain text from the Unicode braille block. Copy it into terminals, READMEs, chat, source comments or a .txt file.

[ eight_samples ]

Why does braille hold eight times the detail?

A ramp charset samples the image once per character. At C columns and R rows that is C times R samples, and a longer ramp only adds shades. Braille spends the same character on a 2 by 4 dot, so the sample grid is 2C by 4R: eight times as many points, and the output is still plain text.

Eight dots with two states each is 256, which is why the Unicode block is exactly that long. Dot n sets bit n-1 of the offset from U+2800. Numbered the way the standard numbers them:

1  4
2  5
3  6
7  8

The bottom pair sits under the first six because six-dot braille came first and the computer-braille row was added later. In the engine those eight positions are a 4 by 2 bit table: 0x01 and 0x08, then 0x02 and 0x10, then 0x04 and 0x20, then 0x40 and 0x80. A lit dot ORs its bit into 0x2800. At 120 columns the braille grid is 240 samples across; the ten-step ramp .:-=+*#%@ at the same width is still 120 samples across.

[ error_diffusion ]

Why does a plain threshold look flat?

Each dot is on or off, so a braille character has no medium grey of its own. Threshold a smooth gradient at 128 and the midpoint becomes a hard edge: flat dark on one side, flat light on the other. Floyd–Steinberg dithering, published in 1976 for printers that could only put a dot or not, walks that grid left to right and top to bottom. Each sample snaps to 0 or 255, and the error is pushed onto neighbors not yet visited: 7/16 to the right, 3/16 below-left, 5/16 below, 1/16 below-right. The sixteenths sum to 1, so brightness is moved, not invented. A region at 30% grey ends up with about 30% of its dots lit.

The dither runs on the 2C by 4R luminance array, before any dots are packed into a character, so error can cross a cell boundary. Dither at character size and then expand, and every cell comes out all eight dots on or all eight off: a two-shade ramp drawn with braille. The array is floating point. Clamping each step to a byte would drop the error that overshoots 0 or 255, and the highlights and shadows would drift.

[ pipeline ]

What order does the converter run in?

Four passes, all inside the tab. Nothing here is uploaded, and production CSP is connect-src 'none'.

  1. 01 draw 2C by 4R

    The source is drawn onto a canvas of exactly two samples per column and four per row. The browser's own resampler does that reduction.

  2. 02 weight the pixels

    Brightness and contrast are applied per channel, then Rec. 709 luma: 0.2126 red, 0.7152 green, 0.0722 blue. Those weights run on the gamma-encoded canvas values. Neutral greys agree with linear-light luminance; saturated colours come out a little darker. Invert, when it is on, flips the luma.

  3. 03 dither the dot grid

    Floyd–Steinberg runs across the whole grid. It is on by default. With it off, step 04 is a hard threshold.

  4. 04 pack eight dots

    A sample at or above 128 lights its dot. After dithering, every sample is already 0 or 255, so the comparison only decides anything when dithering is off.

A monospace cell is taller than it is wide, about three parts wide to five tall. Row count divides by that ratio, so a square image at 120 columns is not 120 rows. Skip the correction and the portrait stretches. Split a 3:5 cell into 2 by 4 and each dot is nearly square, which is why this grid works as pixels. JetBrains Mono contains none of the 256 braille glyphs, so this page measures the operating-system fallback and fits the preview to that advance.

[ choose_a_charset ]

Braille detail or classic ASCII texture?

choose braille for detail

Portraits, landscapes and screenshots with fine edges benefit most. Start around 100–160 columns, keep dithering on, then raise contrast only if the subject looks washed out.

choose a ramp for compatibility

Classic .:-=+*#%@ art has the familiar ASCII look and survives environments whose fonts lack braille glyphs. Blocks suit low-resolution posters; binary suits graphic patterns; detailed keeps photographic tone inside ordinary text.

JetBrains Mono does not contain braille glyphs, so browsers use the operating system's monospace fallback for those cells. That is expected and keeps the text portable across modern platforms.

[ other_charsets ]

The other five charsets

standard

Ten steps of plain ASCII. The classic look, maximum portability.

detailed

Sixty-eight shades of punctuation and letters. The photographic ramp.

blocks

Four fills and a space. Reads as pixels rather than characters.

minimal

Five steps, wide apart. Built for tiny avatars and icons.

binary

A space, a zero, a one. The code-rain look.

[ faq ]

Frequently asked questions

Why does Unicode braille ASCII art have higher resolution than character ramps?

Each standard ASCII character represents a single sampled cell, whereas a Unicode braille pattern character represents a 2×4 dot subgrid (8 dots total). At the same column width, braille provides 2× horizontal and 4× vertical resolution, yielding eight times the spatial detail.

Why does braille art require Floyd–Steinberg error diffusion dithering?

Because each braille dot is strictly binary (on or off) without innate grayscale value, simple thresholding creates posterized, harsh edges. Floyd–Steinberg dithering diffuses quantization errors across neighboring pixels (7/16 right, 3/16 down-left, 5/16 down, 1/16 down-right), reproducing smooth continuous tones.

Why does braille ASCII render differently in some fonts or terminals?

Braille patterns span Unicode U+2800 to U+28FF. Fonts like JetBrains Mono omit braille glyphs, prompting OS fallback rendering. Most modern systems (macOS, Windows 10/11, Linux with DejaVu Sans) render these fallback glyphs uniformly, though cell height and width ratios may slightly differ across terminal emulators.

Make the dots yours.

Open the converter, select braille, and tune the same live controls used to render the portrait above. Your image bytes never leave the tab.

Open the braille ASCII converter