Tags: isene/glow
Tags
v0.1.13: Phase 1 disk-persisted PNG cache The in-RAM `PngCache` is wiped on every process restart. Phase 1 of the image-speedup plan: tier the cache on top of an on-disk PNG store under `~/.kastrup/image_cache/`, keyed by an FNV-1a hash of the same `path:WxH:mtime` cache key already used in RAM. * `cache_get` checks RAM first, falls back to disk (and populates RAM on a disk hit, so subsequent lookups are HashMap-fast). * `cache_put` writes the padded PNG to disk before inserting into RAM, so a crash between paint and the next launch still leaves the bytes available. * `cache_contains` powers `preconvert_images`' "already cached?" skip: if either tier has it, `convert` doesn't fork. The disk side has no soft cap — the directory is the user's to prune. RAM keeps its 256-entry LRU-ish eviction. `mtime` in the key invalidates stale disk entries when the source image changes. Net effect: the SECOND launch of an app using glow + the same image set skips `convert` entirely for everything it touched in the previous session. Co-Authored-By: Claude <noreply@anthropic.com>
v0.1.12: padded-cache + safe place + bigger LRU + cancellable preconvert
Three fixes that together cure the "image sometimes doesn't show
until I press Enter" race and the slow-on-revisit problem in
pointer (and apply equally to scroll / kastrup / watchit / astro).
1. Cache the cell-aligned PNG. The pad subprocess used to run on
every show even on a png_cache hit. Now the cached entry IS the
padded data, so re-shows skip both `convert` calls — they're
just base64-encode + place. Snappy revisits.
2. Drop the spurious `a=d,d=i,i={id}` before the place on the
cache-miss path. We just transmitted fresh data with zero
placements, so a delete-by-image-id told kitty "no refs to this
data" — and depending on timing kitty would free it before the
following place could attach. The 1/20 silent-fail race lived
here. Delete now fires only when we're moving an existing
placement (active_ids already contains the id).
3. preconvert_images now produces padded data itself, takes cell
dims + an AtomicBool cancel handle, and uses a 256-entry cap
with random-eviction down to 200 (was 32 with full clear, which
wiped the cache mid-precache on any modest gallery).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.11: prefer \`magick\` over legacy \`convert\` binary IM7's legacy \`convert\` symlink prints a deprecation warning on several distros (Arch, EndeavourOS), which then bleeds onto the host TUI during every image render. Resolve the IM CLI once per process: \`magick\` if present, else fall back to \`convert\`. No behaviour change on systems where only \`convert\` exists. Affects every Fe2O3 caller (kastrup, pointer, scroll, watchit, hyper) via the glow lib. Fixes scroll #1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.10: forget_path() + show_clipped() for per-image diff updates scroll's per-line scrolling was burning fresh kitty image_ids on every step (3 visible images × ~85 scrolls = 256 IMG_SLOTS exhausted in glass). Two new APIs let callers do per-image diff updates that reuse cached ids: - forget_path(path): emit per-id deletes for every active placement matching that path. Lets the caller tear down a single image without nuking every placement, which preserved cached ids for images still on screen. - show_clipped(path, x, y, w, full_h, src_top, src_visible): place a vertical slice of an image using kitty source-rect crop (lowercase x/y/w/h in the place command). Cache key uses the full natural rendered size so scrolling an image into / out of the viewport reuses the same image_id — no re-transmission, no fresh IMG_SLOT churn per scroll line. Plus: kitty_ensure() refactor splits cache+transmit from placement so both show() and show_clipped() share it; on cache miss we now forget_path() any stale ids for the same path before generating a new one (fixes "ghost duplicate at scrolled-from row" caused by DEC scroll regions carrying old placements). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.9: braille fallback for graphics-free terminals
Add Protocol::Braille — universal text-only fallback that works over
SSH, in tmux without passthrough, and on any terminal that renders
Unicode + truecolor. Each character cell holds a 2x4 dot grid via
U+2800-U+28FF; each cell colored with the average of its 8 source
pixels via SGR truecolor.
Pipeline: convert resizes the image preserving aspect (snapping fit
dims to multiples of 2x4 so the dot grid tiles cleanly), dumps raw
RGBA, we walk 2x4 blocks and emit braille glyphs.
detect_protocol() falls back to Braille after Chafa, so any terminal
with `convert` on PATH gets working image preview. with_mode("braille")
lets apps force this mode regardless of detection.
Inspired by the Tuitter announcement (heathrow:7944541) — same
philosophy: TUI tools that work everywhere, not just on graphics-
capable terminals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pad images to cell-aligned size + clamp by both axes (v0.1.8)
Two related kitty placement bugs:
1. Specifying c=N,r=M in kitty's place command stretches the image to
fill that cell area; "no c/r natural placement" is not universally
honored across terminals. Wezterm, glass, and tiled-WM compositions
were stretching short images vertically (e.g. a 286×12 row of
color squares ballooning to fill ~50px of vertical pane).
Fix: pad each PNG with `convert -background "rgba(0,0,0,0)"
-gravity NorthWest -extent {pad_w}x{pad_h}` to a multiple of
(cell_w × cell_h), then place with c=pad_w/cell_w, r=pad_h/cell_h.
Image content keeps its native pixel size; transparent padding
fills the rest of the cell box.
2. The convert resize argument was width-only (`-resize "{pixel_w}>"`),
so a square 1254×1254 image scaled to fit a wide pane's width
could still overflow vertically into the next pane / status bar.
Now `-resize "{pixel_w}x{pixel_h}>"` clamps both dimensions, and
preconvert_images takes a pixel_height parameter to match.
Cache key now includes pixel_h alongside pixel_w, so a tall image
that was height-clamped on a short pane and width-clamped on a wide
pane gets distinct entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preserve image aspect ratio in kitty placements (v0.1.7) Old logic only used natural_rows and assumed natural_cols == max_width, so a tall narrow image (e.g. 90×1167) was stretched horizontally to fill the pane. Same problem in reverse for short wide images. Now the placement command computes scaling in pixel space using both PNG dimensions and the terminal cell size: scale = min(max_w_px / nat_w_px, max_h_px / nat_h_px) The output cell counts are derived from the scaled pixel dims and ceiled, so the image fits inside the pane on both axes without being distorted on either. If the image already fits at natural size, kitty draws it 1:1 (no c/r parameters). Added png_width helper alongside the existing png_height; widened image_cache value from (id, rows) to (id, cols, rows). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cache synchronously-converted PNGs in png_cache (v0.1.6) png_cache was only populated by the preconvert background thread, which targets adjacent image indices, never the current one. Every clear → show round-trip on the same image (e.g. after a dir-watch reload in pointer) forced a fresh `convert` subprocess that took hundreds of ms for large images, manifesting as the image being absent from the right pane for "a few seconds" until the convert finished. Fix: when show() falls back to running convert synchronously, store the resulting PNG in png_cache. Subsequent re-shows of the same image (same path, width, mtime) reuse the cached PNG and only re-transmit base64 — milliseconds instead of seconds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Place kitty images at z=1 above pane text (v0.1.5) Default kitty placement z=0 sits below text. When a tiled WM delivers expose / redraw events to neighbouring windows, kitty in the host window sometimes redraws cell text without re-rendering the placement, leaving the image hidden behind text until a workspace switch forces a full re-render. z=1 anchors the placement above text, so cell-level redraws preserve the visible image regardless of expose timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-transmit kitty image data when placement was deleted (v0.1.4) When clear() runs the kitty 'a=d,d=i' command, kitty deletes the matched placements and frees the image data once no placements reference it. Our image_cache still mapped (path,width,mtime) → (image_id, rows), so the next show() would only place an image id whose data had been freed — silently failing. The user had to restart the host app to wipe the in-memory cache. Fix: on cache hit, only count it as "live" if the image_id is still in active_ids. After clear() empties active_ids, the next show naturally re-transmits. Also: stop consuming entries in png_cache (use get().cloned() instead of remove()) so re-shows of recently navigated images skip the synchronous `convert` call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PreviousNext