Adapters¶
Adapters bridge external image libraries into dapple's Canvas. Each adapter converts from a specific source format to a Canvas object with the appropriate bitmap and color arrays.
The core dapple library depends only on numpy. Adapters are optional and import their dependencies lazily -- you only need pillow installed if you use the PIL adapter, matplotlib if you use the matplotlib adapter, and so on.
The Adapter Protocol¶
All adapters implement a simple protocol:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Adapter(Protocol):
def to_canvas(self) -> Canvas: ...
Each adapter also provides a convenience function (from_array, from_pil, etc.) that wraps the adapter class in a single call.
The Framebuffer Pattern¶
The common pattern across all adapters:
- Some library renders to a bitmap (numpy array, PIL Image, matplotlib figure, Cairo surface).
- The adapter extracts the pixel data as a numpy array.
- For RGB data, luminance is computed using ITU-R BT.601 coefficients:
0.299R + 0.587G + 0.114B. - A Canvas is constructed with the grayscale bitmap and optional RGB colors.
This means any library that can produce a bitmap can feed into dapple. The adapter just handles the conversion.
NumpyAdapter / from_array¶
Converts numpy arrays directly to Canvas. This is the most basic adapter since Canvas already stores numpy arrays internally.
Dependency: numpy (always available -- it is a core dependency).
Usage¶
import numpy as np
from dapple.adapters import from_array
# 2D grayscale array
grayscale = np.random.rand(48, 80).astype(np.float32)
canvas = from_array(grayscale)
# 3D RGB array -- luminance bitmap is computed automatically
rgb = np.random.rand(48, 80, 3).astype(np.float32)
canvas = from_array(rgb)
Class interface¶
from dapple.adapters import NumpyAdapter
adapter = NumpyAdapter(array, renderer=braille)
canvas = adapter.to_canvas()
Input requirements¶
| Shape | Interpretation | Canvas result |
|---|---|---|
(H, W) |
Grayscale bitmap | bitmap only, no colors |
(H, W, 3) |
RGB color image | bitmap from luminance, colors from RGB |
Values should be in the range 0.0--1.0. Arrays with integer dtype or values outside this range should be normalized before passing to the adapter.
Normalizing integer arrays¶
# uint8 image (0-255)
uint8_array = np.random.randint(0, 256, (48, 80, 3), dtype=np.uint8)
float_array = uint8_array.astype(np.float32) / 255.0
canvas = from_array(float_array)
PILAdapter / from_pil / load_image¶
Converts PIL (Pillow) Image objects to Canvas. Handles L (grayscale), RGB, RGBA, and other PIL modes.
Dependency: pip install pillow
from_pil -- convert an existing PIL Image¶
from PIL import Image
from dapple.adapters import from_pil
img = Image.open("photo.jpg")
canvas = from_pil(img)
# With resizing
canvas = from_pil(img, width=160) # scale to 160px wide
canvas = from_pil(img, height=80) # scale to 80px tall
canvas = from_pil(img, width=160, height=80) # exact dimensions
| Parameter | Type | Default | Description |
|---|---|---|---|
image |
Image |
(required) | PIL Image object |
width |
int\|None |
None |
Target width (proportional scaling if height omitted) |
height |
int\|None |
None |
Target height (proportional scaling if width omitted) |
renderer |
Renderer\|None |
None |
Default renderer for the Canvas |
Resizing uses Lanczos resampling (high quality).
load_image -- load from file path¶
from dapple.adapters.pil import load_image
canvas = load_image("photo.jpg")
canvas = load_image("photo.jpg", width=160)
This is a shorthand for Image.open(path) followed by from_pil(...).
Color mode handling¶
| PIL Mode | Canvas Result |
|---|---|
L |
Grayscale bitmap, no colors |
RGB |
Bitmap from luminance, colors from RGB |
RGBA |
Converted to RGB (alpha discarded), then as RGB |
| Other | Converted to grayscale (L), bitmap only |
Class interface¶
from dapple.adapters import PILAdapter
adapter = PILAdapter(img, width=160, renderer=quadrants)
canvas = adapter.to_canvas()
MatplotlibAdapter / from_matplotlib¶
Captures a matplotlib Figure as a Canvas. The figure is rendered to a PNG in memory, then converted to pixel arrays.
Dependency: pip install matplotlib (and optionally pillow for better PNG decoding)
Usage¶
import matplotlib
matplotlib.use("Agg") # non-interactive backend
import matplotlib.pyplot as plt
from dapple.adapters import from_matplotlib
from dapple import braille
# Create a plot
fig, ax = plt.subplots()
ax.plot([0, 1, 2, 3], [0, 1, 0, 1])
ax.set_title("Example")
# Convert to canvas
canvas = from_matplotlib(fig, width=160)
canvas.out(braille)
plt.close(fig)
| Parameter | Type | Default | Description |
|---|---|---|---|
figure |
Figure |
(required) | Matplotlib Figure object |
width |
int\|None |
None |
Target width in pixels |
height |
int\|None |
None |
Target height in pixels |
dpi |
int |
100 |
Rendering DPI |
renderer |
Renderer\|None |
None |
Default renderer for the Canvas |
How it works¶
fig.savefig()renders the figure to a PNG in a BytesIO buffer.- If pillow is available, the PNG is decoded with
Image.open(). - Otherwise,
fig.canvas.draw()andfig.canvas.tostring_rgb()extract raw pixel data. - RGB is converted to Canvas with luminance bitmap.
The figure's size is adjusted before rendering if width or height are specified. The dpi parameter controls the rendering resolution.
Tip: Use the
Aggbackend (matplotlib.use("Agg")) to avoid opening a GUI window. This is especially important in headless environments (servers, CI, SSH).
Class interface¶
from dapple.adapters import MatplotlibAdapter
adapter = MatplotlibAdapter(fig, width=160, dpi=150)
canvas = adapter.to_canvas()
CairoAdapter / from_cairo¶
Converts Cairo ImageSurface objects to Canvas. Handles ARGB32, RGB24, and A8 surface formats.
Dependency: pip install pycairo
Usage¶
import cairo
from dapple.adapters import from_cairo
from dapple import quadrants
# Create a Cairo surface and draw on it
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 100)
ctx = cairo.Context(surface)
# White background
ctx.set_source_rgb(1, 1, 1)
ctx.paint()
# Red rectangle
ctx.set_source_rgb(1, 0, 0)
ctx.rectangle(20, 20, 60, 60)
ctx.fill()
# Blue circle
ctx.set_source_rgb(0, 0, 1)
ctx.arc(150, 50, 30, 0, 2 * 3.14159)
ctx.fill()
# Convert to canvas
canvas = from_cairo(surface)
canvas.out(quadrants)
| Parameter | Type | Default | Description |
|---|---|---|---|
surface |
ImageSurface |
(required) | Cairo ImageSurface object |
renderer |
Renderer\|None |
None |
Default renderer for the Canvas |
Surface format handling¶
| Cairo Format | Canvas Result |
|---|---|
FORMAT_ARGB32 |
RGB colors (alpha ignored), luminance bitmap |
FORMAT_RGB24 |
RGB colors, luminance bitmap |
FORMAT_A8 |
Grayscale bitmap only |
Cairo stores pixels in BGRA order on little-endian systems. The adapter handles the byte-order conversion to RGB.
Class interface¶
from dapple.adapters import CairoAdapter
adapter = CairoAdapter(surface, renderer=braille)
canvas = adapter.to_canvas()
ANSIAdapter / from_ansi¶
Parses ANSI-colored terminal output back into a Canvas. This reverses the rendering process: given braille, quadrant, sextant, or ASCII terminal art (with or without ANSI color codes), it reconstructs the bitmap and color arrays.
Dependency: none (uses only numpy and the standard library)
Usage¶
from dapple.adapters.ansi import from_ansi
# Parse braille art
text = "\u283f\u283f\u283f" # three full braille characters
canvas = from_ansi(text)
# canvas.bitmap.shape == (4, 6)
# Parse with explicit format
canvas = from_ansi(colored_text, format="quadrants")
# Auto-detect format
canvas = from_ansi(mystery_text) # detects braille/quadrants/sextants/ascii
| Parameter | Type | Default | Description |
|---|---|---|---|
text |
str |
(required) | ANSI-colored terminal art |
format |
str\|None |
None |
"braille", "quadrants", "sextants", "ascii", or None for auto-detect |
charset |
str |
" .:-=+*#%@" |
Character ramp for ASCII format parsing |
Format detection¶
When format=None, the parser counts the number of braille, quadrant, sextant, and ASCII characters in the input and picks the dominant format.
Color parsing¶
The parser handles the following ANSI SGR (Select Graphic Rendition) sequences:
- Basic 16 colors (codes 30--37, 90--97 for foreground; 40--47, 100--107 for background)
- 256-color mode (
38;5;N/48;5;N) - 24-bit true color (
38;2;R;G;B/48;2;R;G;B) - Reset (
0)
Colors are mapped to the reconstructed Canvas: foreground colors are applied to "active" pixels (dots, filled quadrants), and the bitmap structure is derived from the character encoding.
Class interface¶
from dapple.adapters.ansi import ANSIAdapter
adapter = ANSIAdapter(format="braille")
canvas = adapter.parse(text)
Use cases¶
- Round-trip testing: render to text, parse back, compare bitmaps.
- Terminal art processing: load existing ANSI art, apply transforms, re-render in a different format.
- Format conversion: parse quadrant art, re-render as braille (or vice versa).
Summary¶
| Adapter | Source | Dependency | Function | Class |
|---|---|---|---|---|
| NumpyAdapter | numpy array | (core) | from_array() |
NumpyAdapter |
| PILAdapter | PIL Image | pillow | from_pil() |
PILAdapter |
| (file loading) | image file path | pillow | load_image() |
-- |
| MatplotlibAdapter | mpl Figure | matplotlib | from_matplotlib() |
MatplotlibAdapter |
| CairoAdapter | Cairo surface | pycairo | from_cairo() |
CairoAdapter |
| ANSIAdapter | ANSI text | (core) | from_ansi() |
ANSIAdapter |
Imports¶
# Convenience functions
from dapple.adapters import from_array, from_pil, from_matplotlib, from_cairo, from_ansi
# Or from specific modules
from dapple.adapters.pil import load_image
from dapple.adapters.ansi import from_ansi, detect_format
# Classes
from dapple.adapters import (
NumpyAdapter, PILAdapter, MatplotlibAdapter, CairoAdapter, ANSIAdapter,
)