← All libraries

CandyMosaic

🍬 CandyMosaic

Image-to-cell renderer

port of x/mosaic image sixel kitty iterm2 ext-gd

Image-to-cell renderer for the terminal. PNG/JPEG/static GIF decoded via ext-gd and rendered via the best available protocol: Sixel, Kitty graphics, iTerm2 inline images, Unicode half-block, or quarter-block fallback.

Install

composer require sugarcraft/candy-mosaic

Quickstart

use SugarCraft\Mosaic\Mosaic;
use SugarCraft\Mosaic\ImageSource;

$mosaic = Mosaic::halfBlock();
$image  = ImageSource::fromFile('cat.png');
$ansi   = $mosaic->render($image, width: 40, height: 20);
echo $ansi;

API

use SugarCraft\Mosaic\Renderer\QuarterBlockRenderer;

// Probe terminal once, pick best protocol
$mosaic = Mosaic::probe();

// Force a specific backend
$mosaic = Mosaic::sixel();
$mosaic = Mosaic::kitty();
$mosaic = Mosaic::iterm2();
$mosaic = Mosaic::halfBlock();

// Render — returns ANSI bytes
$ansi = $mosaic->render($image, width: 40, height: 20);

// Builder for fine-grained control
$mosaic = Mosaic::builder()
    ->withRenderer(new QuarterBlockRenderer())
    ->withResize(width: 40, height: 20)
    ->build();

What's in the box

Sixel rendererxterm, foot, mlterm, wezterm, contour support.
Kitty graphics protocolkitty, ghostty, wezterm support.
iTerm2 inline imagesOSC 1337 support for iTerm2, wezterm, mintty.
Unicode half-blockUniversal fallback using ▀ + 24-bit fg/bg.
Unicode quarter-blockHigher fidelity using ░▒▓█ 2×2 sub-pixel rendering.
ext-gd integrationPNG, JPEG, static GIF via GD (imagecreatefrompng, imagecreatefromjpeg, imagecreatefromgif).
Protocol detectionMosaic::probe() checks environment variables and DA1 capability query. Cached per-process.
Kitty virtual-image placementTransmit once (a=T), then place at multiple offsets (a=p) to reduce bandwidth. Use KittyOptions::transmit() then KittyOptions::place().
Kitty zlib compressionCompress PNG payload with zlib (f=1) before base64-encoding via KittyOptions::withCompression(1). Useful for large images.
Half-block transparencyTransparent pixels (alpha=127) emit no SGR codes — terminal default background shows through. Enables overlay compositing on existing terminal content.
Sixel 256-color fallbackPass maxColors (1–256) to SixelRenderer constructor to limit palette size for terminals with limited truecolor support.
Animation supportAnimation (immutable frame sequence) + AnimationDriver (Model with tick-based frame timing). Use KittyRenderer::renderFrame() for stable per-frame ids that support targeted redraws.

Source & demos

Try the quickstart →

Key Classes

ClassMethodDescription
Mosaicprobe()Auto-detect best terminal protocol
Mosaicsixel(), kitty(), iterm2(), halfBlock()Force specific renderer
Mosaicrender(image, width, height)Render image to ANSI string
Mosaicbuilder()Create builder for fine-grained control
Rendererdelete(imageId)Remove a previously rendered image (Kitty=APC delete; iTerm2=OSC 1337 Pop; others=empty)
KittyRendererrenderWithOptions(image, width, height, KittyOptions)Render with Kitty protocol options (virtual-image a=p, compression f=1)
KittyOptionstransmit(id), place(id, x, y)Factory: transmit inline or place a previously transmitted virtual image
KittyOptionswithZIndex(z), withCompression(f), withUseVirtual(bool)Builder: z-index stacking, zlib compression (f=1), virtual-image mode (a=p)
QuarterBlockRenderernew2×2 sub-pixel renderer via Mosaic::builder()->withRenderer(new QuarterBlockRenderer())
HalfBlockRenderernew, supportsAlpha()Unicode ▀ renderer with transparent-pixel handling (null alpha skips SGR codes, letting terminal default show through). supportsAlpha() returns false — transparency is rendered by omitting SGR, not blending.
SixelRenderernew(dither, maxColors), maxColors(), dither()DEC sixel renderer with median-cut quantizer (up to 256 colors). Constructor accepts maxColors (1–256, default 256) to limit palette size for limited-truecolor terminals. Dither enum: FloydSteinberg, Stucki, Atkinson, None.
ImageSourcefromFile(path)Load image from file
ImageSourcefromGd(image)Create from GD resource
Animationfixed(frames, delayMs)Factory: uniform-delay animation from list of ImageSource
AnimationwithFrame(index, frame, delayMs)Return new Animation with one replaced frame (immutable)
AnimationframeCount(), totalDurationMs()Frame count and total duration in ms
AnimationDrivernew(animation, renderer, cellWidth, cellHeight?, index?, paused?, imageId?)Model that drives Animation onto a Renderer via tick-based frame timing
AnimationDriverwithIndex(n), withPaused(bool), withImageId(id)Fluent state changes (each returns new instance)
FrameTickMsg(internal)Internal Msg dispatched by Cmd::tick() to advance frame index
KittyRendererrenderFrame(image, width, height, imageId)Render single frame with stable id for targeted delete+redraw on Kitty terminals

Demos.

VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.

Inline Image

Inline Image

Renders a PNG to the terminal via the best available protocol.