Skip to content

Tags: isene/glow

Tags

v0.1.13

Toggle v0.1.13's commit message
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

Toggle v0.1.12's commit message
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

Toggle v0.1.11's commit message
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

Toggle v0.1.10's commit message
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

Toggle v0.1.9's commit message
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>

v0.1.8

Toggle v0.1.8's commit message
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>

v0.1.7

Toggle v0.1.7's commit message
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>

v0.1.6

Toggle v0.1.6's commit message
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>

v0.1.5

Toggle v0.1.5's commit message
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>

v0.1.4

Toggle v0.1.4's commit message
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>