feat(tui): image rendering — ImageRenderer trait and Kitty backend #55

Merged
charles merged 1 commit from tui/kitty-12 into main 2026-04-11 20:42:02 +00:00
Owner

Summary

Eighth PR in the loom-tui stack. First of four image-rendering backends called for by spec §2.2.

Stacks on #54. Closes charles/loom#12.

What's in

  • image::ImageRenderer trait (render, delete, protocol_name)
  • image::ImageSource::{Path, Bytes} with cache_key(width, height) hashing path or a bounded byte sample
  • image::RenderHandle(u32) opaque
  • image::kitty::KittyRenderer:
    • a=T chunked base64 transmission at CHUNK_SIZE = 4096, f=100 (PNG), U=1 virtual placement, c/r for cells
    • a=p re-placement when the (source, w, h) hash is already in the cache
    • a=d,d=i,i=<id> delete
    • Monotonic u32 id allocator that wraps at MAX back to 1 so id == 0 is never emitted

Tests (7 new, 51 total)

  • ImageSource::cache_key is stable across equal sources and differs on size / bytes
  • chunk_payload splits exactly at CHUNK_SIZE and above
  • Cache hit does not reallocate the id counter
  • Id allocator wraps cleanly at u32::MAX

Notes

  • Escape-sequence emission isn't unit-testable without a fake tty; the tests cover the logic that decides which sequence to emit. A pty-wrapped integration test comes with #46
  • Today's renderer expects PNG input. Transcoding other formats via the image crate lands in #14 alongside HalfBlockRenderer
  • image::mod.rs is #![allow(dead_code)] at module level because the trait/source surface is consumed by gallery / generate preview in later screen tickets
## Summary Eighth PR in the loom-tui stack. First of four image-rendering backends called for by spec §2.2. Stacks on #54. Closes charles/loom#12. ## What's in - **`image::ImageRenderer`** trait (`render`, `delete`, `protocol_name`) - **`image::ImageSource::{Path, Bytes}`** with `cache_key(width, height)` hashing path or a bounded byte sample - **`image::RenderHandle(u32)`** opaque - **`image::kitty::KittyRenderer`**: - `a=T` chunked base64 transmission at `CHUNK_SIZE = 4096`, `f=100` (PNG), `U=1` virtual placement, `c`/`r` for cells - `a=p` re-placement when the `(source, w, h)` hash is already in the cache - `a=d,d=i,i=<id>` delete - Monotonic `u32` id allocator that wraps at MAX back to 1 so `id == 0` is never emitted ## Tests (7 new, 51 total) - `ImageSource::cache_key` is stable across equal sources and differs on size / bytes - `chunk_payload` splits exactly at `CHUNK_SIZE` and above - Cache hit does not reallocate the id counter - Id allocator wraps cleanly at `u32::MAX` ## Notes - Escape-sequence emission isn't unit-testable without a fake tty; the tests cover the logic that decides which sequence to emit. A pty-wrapped integration test comes with #46 - Today's renderer expects PNG input. Transcoding other formats via the `image` crate lands in #14 alongside `HalfBlockRenderer` - `image::mod.rs` is `#![allow(dead_code)]` at module level because the trait/source surface is consumed by gallery / generate preview in later screen tickets
Adds the first of four image backends called for by spec §2.2:

- image::ImageRenderer trait with render/delete/protocol_name
- ImageSource (Path or Bytes) with a stable cache_key(width, height)
- image::RenderHandle opaque id
- image::kitty::KittyRenderer implementing the trait:
  - Transmits PNG via APC with f=100, chunked base64 (m=1/m=0)
  - Uses a=T on first transmission and a=p for cache hits
  - U=1 virtual placement so images ride reflow
  - Per-session cache keyed by (source, width_cells, height_cells)
    with incrementing u32 ids wrapping at MAX back to 1
  - Emits a=d,d=i on delete to clear a specific id
- Chunk splitter helper with tests covering the 4096-byte boundary

Tests cover the pure logic (chunk splitter, cache-key stability,
id allocator wrap-around). Escape sequences to stdout aren't unit-
testable without a fake tty; they're exercised manually.

Closes charles/loom#12

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
charles changed target branch from tui/notifications-42 to main 2026-04-11 20:41:55 +00:00
charles deleted branch tui/kitty-12 2026-04-11 20:42:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/loom!55
No description provided.