Convert an Image to ASCII Art in Python: Preserve the Rows and Spaces

A trailing space can be image data. Trimming it from the first ASCII row changes the width even when the visible symbols look correct.

The original eight pixels become two exact four-character rows. Compare raw output with separate visible-space notation and total bytes before reproducing the mapping in Python.

Convert an Image to ASCII Art in Python: Preserve the Rows and Spaces
Convert an Image to ASCII Art in Python: Preserve the Rows and Spaces
Open the full example ↗

Begin with an opaque grayscale fixture

Use this original 4×2 L-mode image specification, without resizing:

Image row Pixel codes from left to right
0 0, 64, 128, 255
1 255, 128, 64, 0

The fixture is authored pixel data, not a decoded photograph or a measured brightness sample. Its width is four pixels and its height is two pixels.

Choose a four-character ramp in the declared dark-to-light order @, +, :, and an ASCII space. That order is an exercise convention; it is not a calibration of how dark these glyphs appear in every font.

For integer pixel code v from 0 through 255, define the bin index as v * 4 // 256. The original mapping is:

Pixel code Bin index Character
0 0 @
64 1 +
128 2 :
255 3 ASCII space

Dividing by 256 keeps the maximum code in index 3. A formula that produces index 4 for 255 would point outside this four-character ramp. Retain this boundary test before applying the mapping to more pixels.

Rebuild one output line per image row

For this fixture, traverse row 0 left to right, then row 1 left to right. Map each code to a character and group the resulting sequence into chunks of exactly four characters. Do not put every pixel on a separate line or traverse columns first.

The expected first line is @+: , including the space after the colon. The expected second line is :+@, including its leading space. These are exact line contents; the backticks are explanation delimiters and are not part of the file.

For a visible inspection notation only, the two lines can be shown as:

@+:·
·:+@

Here the centered dot marks a space. It must never be substituted into the actual ASCII output.

Check the file as bytes, not only as a picture

Specify one LF byte after each output line, including the last line. Four characters plus LF, twice, makes ten ASCII bytes. A later writer must explicitly preserve that newline policy rather than depend on platform text-mode translation.

Byte position, zero-based Expected byte
0–3 @, +, :, space
4 LF
5–8 space, :, +, @
9 LF

Do not call strip() on the lines or the complete output. Removing row 0's trailing space changes its width from four characters to three; removing row 1's leading space shifts the row. A CRLF file would also differ from this explicitly LF-only byte contract.

Declare the supported input policy

For later file decoding, limit file input to opaque, single-frame PNG or JPEG inputs decoded as L or RGB. Hold unknown transparency, alpha channels, palette mode, CMYK, and animation until an explicit policy is confirmed. A palette image might look opaque, but it is still outside this initial contract.

RGB-to-L conversion is a documented image operation, not a physical luminance measurement or object-recognition result. A remote image URL is also outside this local-input specification; there is no image-fetch or upload service in the packet.

If you later use your own photograph, record its source hash, orientation policy, positive bounded output width, chosen resampling method, and target font-cell proportions. Do not assume monospace cells are square. Keep that resize experiment separate from the unchanged 4×2 fixture, so its byte test continues to mean the same thing.

Try the worked example

Convert the supplied rows and inspect the ten-byte output. Change the first pixel to 255 to introduce a leading space without changing width. Try 256 to see the invalid-byte case. Space notation must never replace raw output.

The page maps supplied grayscale numbers locally; it does not run Python, decode photographs, resize images, or create an output file.

Recreate this example

Build one complete self-contained HTML page for this task: map supplied grayscale numbers into exact ASCII rows without trimming whitespace.

Use these exact original practice data and worked expectations: 4×2 rows [0,64,128,255] and [255,128,64,0]; ramp @+: then ASCII space; index=floor(v*4/256). First row @+: with trailing space; second starts with space then :+@; LF after each; ten ASCII bytes.

Implement the following useful local interactions: Provide two four-value rows and Convert. Require integer codes 0–255. Show raw text, separate space notation, row lengths, byte count including final LF. No photo decoding, Python execution, or download. Preserve the supplied source text and distinguish an uncomputed state, a successful local result, and an invalid or incomplete input. Update output only from the current input snapshot. An input edit must retire stale feedback until the reader runs the relevant action again. Do not silently invent missing values or replace a failing case with a success.

Design the page as a compact source-and-result developer workbench with readable code, labeled inputs, and a separate explanation area. Put a compact task heading, actual sample data, the main result, and usable controls early enough to be useful on a narrow screen. Use readable text of at least 16px, generous paragraph spacing, strong contrast, native labels, accessible buttons, and visible keyboard focus. Stack long records at 360px and wrap code without page-level horizontal overflow.

Use type=button and explicit querySelector-based click handlers rather than relying on form submission. If Enter is useful, handle it only on the relevant input and ignore isComposing. Keep effects within this open page; no remote dependencies, network calls, persistence, fake downloads, or automated sending. The page maps supplied grayscale numbers locally; it does not run Python, decode photographs, resize images, or create an output file. Place worked explanations beside their data and identify teacher or synthetic examples clearly. Output only the complete HTML document, with inline styles and script and LF line endings.

Review the grayscale-to-character mapper in NOS

Port this exact fixture into the native converter before trying a photograph. Use the explanation in NOS to review the raw rows beside the visible-space notation and ten-byte check, then revise eligible static newline instructions in visual editing mode.

Keep reading