v0.7.0
This release rebuilds the color and ANSI-rendering pipeline around numpy structured arrays, vectorizes every colorspace conversion, and adds SAUCE metadata parsing for ANS files. It removes several public symbols and changes the signatures and return types of the core conversion and image functions, so existing callers will need updates.
Removals
AnsiImageremoved from bothchromaticandchromatic.image. The rendering it provided now runs throughimg2ansiandansi2imgoperating oncolor_chainand structured arrays.to_sgr_arrayremoved from bothchromaticandchromatic.image.color_chain.masksproperty removed.color_chain.merge()removed.color_chain.from_masks()classmethod removed. Build acolor_chainfrom the constructor, which accepts an iterable of(SgrSequence, str)pairs, bareSgrSequence, orstr, or from the newcolor_chain.fromarray.color_chain.__or__removed, so the|operator no longer merges chains.
Compatibility notes
Colorspace conversions return ndarrays
hsl2rgb, rgb2hsl, hsv2rgb, rgb2hsv, lab2rgb, rgb2lab, xyz2lab, lab2xyz, xyz2rgb, rgb2xyz, and rgb_diff now accept any arraylike of RGB values and return a numpy ndarray whose shape matches the input. A single three-channel input that previously came back as a Python tuple now returns a length-3 ndarray. Code that indexes or unpacks the result is unaffected; code that required an actual tuple (for hashing, dict keys, or isinstance checks) must wrap the result with tuple(...).
The scalar and integer helpers hexstr2rgb, int2rgb, rgb2int, rgb2hexstr, ansi_4bit_to_rgb, and the tuple-input paths of ansi_8bit_to_rgb, nearest_ansi_4bit_rgb, and nearest_ansi_8bit_rgb still return tuples or ints for tuple or int input; they only gained array support.
read_ans and render_ans take a binary buffer and parse SAUCE
Both functions now read from a binary buffer instead of decoded text. read_ans(buf, fallback=None) returns (content, metadata), where metadata holds the columns, lines, font name, ANSi flags, date, and comments resolved from the file's SAUCE record, falling back to shutil.get_terminal_size() (or the provided fallback) when no record is present. render_ans(buf, fallback=None, font=None, ...) resolves shape and font from the same record, so its former required shape argument is gone and font defaults to the SAUCE-declared font when available.
Old form:
render_ans(decoded_text, shape=(width, height), font=VGA437)New form:
with open(path, "rb") as f:
render_ans(f) # shape and font come from SAUCE, or from `fallback`reshape_ansi takes a shape tuple and flags, returns a color_chain
reshape_ansi(s, w, h) -> str became reshape_ansi(s, shape, flags=0) -> color_chain. Pass (width, height) as a single tuple and an optional ReshapeAnsiFlag value; the reflowed output is a color_chain rather than a string.
img2ansi returns a color_chain, and sort_glyphs and bg defaults changed
img2ansi now returns a color_chain by default (or a raw structured ndarray when outarray=True). The sort_glyphs=reversed sentinel is replaced by sort_glyphs=-1. The bg default changed from black (0, 0, 0) to None, meaning no background fill unless one is passed.
ansi2img accepts array grids, and bg_default is now black
ansi2img accepts a structured ndarray, a color_chain, a list of color_chain, or the previous list[list[ColorStr]], and its first parameter was renamed from ansi_array to arr. The bg_default value changed from 'auto' to black (0, 0, 0), so backgrounds are no longer inferred by default.
ansi_quantize no longer equalizes
The equalize keyword was removed from ansi_quantize; equalize the image yourself before calling it. Its ansi_type argument now takes an AnsiColorParam.
translate_cp437 is string-only
translate_cp437 no longer accepts integer input. It takes a str and an optional ignore iterable of code points to leave untranslated.
Python 3.12 support
requires-python is now >=3.12,<4.0, lowered from >=3.13, with classifiers declared for 3.12, 3.13, and 3.14. Supporting 3.12 required replacing the runtime PEP 604 union string in FontArgType with typing.Union plus a forward reference, and removing a type-parameter default that 3.12 could not import.
New features
numpy interop for color_chain
color_chain gains a dtype class attribute, exposed publicly as ColorChainDType (a structured void dtype), an __array__ method so np.asarray(chain) works, a fromarray classmethod, and a term_array() method that returns a structured two-dimensional ndarray of the chain's SGR state and text. The new SgrFlag, an IntFlag of SGR display attributes (bold, italics, blink, negative, and so on), is exported for packing SGR state into these arrays.
iCE colors and bold-color reshaping
ReshapeAnsiFlag controls how reshape_ansi promotes SGR state. BOLD_COLORS maps SGR 1 onto bright foreground colors following the IBM VGA convention, ICE_COLORS maps SGR 5 onto bright background colors (iCE colors), and BOLD_FONT and RESET_BOLD_AND_FAINT govern the bold bit. render_ans turns on iCE colors automatically when a file's SAUCE ANSi flags request them.
color_chain is a mutable sequence
color_chain is now a MutableSequence, so it supports in-place insert, item assignment, and deletion. As part of this, shrink() compacts the sequence in place and returns None, where it previously returned a new instance.
SgrSequence.shrink()
SgrSequence gains a shrink() method that compacts its parameter sequence in place.
backtrans_cp437
backtrans_cp437(x, keys=None) reverses translate_cp437, mapping CP437 glyphs back to their control-code code points. It is exported from chromatic.image.
--alpha option for the image command
The ansify image command gains --alpha, which enables an alpha channel with default background 0 and foreground 0xFF and pads three-channel color defaults to four channels.
Structural pattern matching
SgrSequence and color_chain define __match_args__, so both can be destructured in match statements.
Performance improvements and changes
Vectorized colorspace conversions
The colorspace conversions are reimplemented as whole-array numpy operations rather than per-pixel Python calls, which is the source of the ndarray return type described above. Four-bit ANSI lookup now indexes a precomputed ANSI_4BIT_RGB_LUT array instead of a per-value mapping.
Vectorized glyph interpolation
img2ascii maps luminance onto glyphs by fancy-indexing into a <U1 character array instead of calling numpy.frompyfunc. Passing ret_img=True returns the character grid as an ndarray.
img2ansi array fast path
Passing outarray=True to img2ansi builds and returns the structured ndarray directly, skipping the encode and decode round-trip through color_chain that the default path performs.
Changes
demo.butterfly_randcolor renamed
The demo function butterfly_randcolor is now butterfly_rainbowcolor and draws a hue gradient using rgb2hsl and hsl2rgb.
Fixes
Empty SGR parameters parse as zero
SGR sequences with empty or omitted parameters, such as ESC[;m or an extended-color sequence with a missing field, were dropped during parsing. They are now treated as 0, matching terminal behavior. An extended-color introducer (38 or 48) followed by anything other than 2 or 5 now raises ValueError rather than parsing incorrectly.
Foreground and background lookup respects resets
SgrSequence color resolution scanned the entire parameter list and could report a foreground or background color that a later reset had already cleared. Lookup now stops at a reset (0), a default-foreground (39), or a default-background (49) code, so a reset color correctly resolves to unset.
SgrSequence construction preserves duplicates
The SgrSequence constructor no longer silently drops duplicate items from its input iterable, and SgrSequence.__eq__ now compares byte content instead of object identity.
CLI hex color parsing
The image command's color parser accepts 3-, 4-, 6-, and 8-digit hex, expanding the short forms, validates the value against the u24 or u32 range, and reports the expected byte counts on failure. ANSI text dumped by the ansify command now ends with a reset sequence.
Full Changelog: v0.6.1...v0.7.0