cashmere

cashmere

kitty-graphics.el v1.0.0: Document Zoom and Pan, a Doctor Command, and a Stable 1.0

kitty-graphics.el is 1.0.0. The package has been my daily driver for months now, across Kitty, foot, WezTerm, Ghostty, and tmux, and it has reached the point where I trust it for real work instead of treating it as an experiment. This release is the stable cut: a large amount of rendering, performance, and reliability work landed since v0.6.0, and 1.0.0 is where it all settles.

If you are new here: it renders images, video, scaled headings, and documents directly in terminal Emacs (emacs -nw), picking the Kitty graphics protocol or Sixel automatically depending on your terminal.

A stable 1.0

v0.6.0 added inline video, a Chromium browser, and Kitty graphics through tmux. Everything since then has been about making the package solid: a redisplay engine that does nothing when nothing changed, conversions that no longer block Emacs, daemon support for several terminals at once, and a long bug-sweep over the process, overlay, and protocol lifecycles. 1.0.0 bundles all of it, plus the new document viewer below.

Nothing in your config needs to change to upgrade. The entry point is still kitty-graphics-setup, which works the same for a plain terminal Emacs and for a daemon with several emacsclient -t clients attached.

Document zoom and pan

The doc-view integration was reworked into something I actually want to read PDFs in. Pages now render centered in the window, n and p flip pages, and + / - / 0 zoom.

img

On the Kitty backend a zoomed page is clipped to the window and panned by scrolling, the way pdf-tools behaves, so you can zoom into a figure and move around it instead of overflowing the buffer. On Sixel the page renders at fit size. PDF, DVI, PostScript, and EPUB all go through the same path.

It is also converter-agnostic now: if MuPDF's mutool is on PATH, doc-view uses it automatically, and Ghostscript still works as before. MuPDF is noticeably faster and sharper for PDFs.

Faster, lighter rendering

The biggest invisible change is that refreshes are skipped entirely when nothing on screen changed. Each visible image window stores a cheap signature (buffer, modification tick, displayed region, hscroll, pixel size) after a successful refresh, and the post-command scheduler does nothing while every signature still matches. kitty-gfx-skip-clean-refresh (default t) is the escape hatch.

Non-PNG images (jpeg, webp, svg, and so on) are now converted to PNG in a background process instead of blocking Emacs, controlled by kitty-gfx-async-conversion (default t). The overlay reserves its space immediately and the image appears once the conversion lands. Results are cached per file mtime, and base64 payloads are cached in memory (bounded by kitty-gfx-base64-cache-bytes, 64 MiB, LRU), so sending the same image to a second terminal skips both the read and the encode.

Transmitting an image to a terminal that does not hold its data yet no longer happens synchronously inside the refresh loop. Transmits go through a queue drained one image per timer tick, so keystrokes stay responsive while a freshly attached client catches up.

Daemon and multiple terminals

Graphics now work under emacs --daemon with several emacsclient -t clients at the same time. Placements, transmissions, and cleanup are routed per client terminal: each one keeps its own backend detection, cell size, text-sizing level, and transmitted-image set, so a client in Kitty and another in a Sixel terminal both render correctly and concurrently. Inline video and the casty browser stay bound to the terminal they were launched on, since each streams a single byte stream to one tty.

Sixel tuning

Sixel encoding gained real knobs. kitty-gfx-sixel-dither (nil, "none", "fs", or "atkinson") and kitty-gfx-sixel-colors (palette size, default 256) map onto img2sixel's -d / -p and ImageMagick's -dither / -colors. ImageMagick now resizes before quantizing so the palette reduction actually sticks. Dropping to 16 colors with dithering off cut the test-image payload from 2042 to 1412 bytes with img2sixel and from 4904 to 1712 with magick, which matters a lot in tmux where every refresh re-emits the whole DCS payload.

When img2sixel encodes a source larger than the target box and ImageMagick is present, the image is pre-scaled to a temp PNG first so quantization runs on the small image. tmux detection is also per terminal now: each client's own tmux server is queried for its version and allow-passthrough state, so daemon clients in different tmux servers detect independently.

Headings that stay put

OSC 66 scaled headings were reworked to feel native. Editing a scaled heading no longer makes it vanish and reappear: the overlay stays in place, the text shows at normal size while you type, and a debounced rescan re-renders it where it is. Heading widths use string-width, so CJK and emoji headings get their full footprint, and scaled headings no longer overwrite inline images. Instrumentation is lazy by default (kitty-gfx-heading-scan-visible-only), scanning only the visible region plus a margin, which keeps activation instant in files with thousands of headings.

Heading rendering is best for reading and navigating. Editing works far better than before, but only one window per buffer renders the scaled text, so it is still primarily a viewing feature.

Reliability and diagnostics

New M-x kitty-gfx-doctor prints a diagnostic report: the active backend, the queried cell size and text-sizing level, tmux state, the resolved Sixel encoder, and which of the external programs the package relies on are actually installed. When a terminal is not supported it now tells you why instead of just refusing, and a missing ImageMagick says so instead of letting images silently never appear.

Every blocking external command (ImageMagick, identify, ffmpeg, typst) now runs under a watchdog bounded by kitty-gfx-process-timeout (15s), so a corrupt or pathological file can no longer freeze Emacs. A failed or timed-out Sixel encode shows a visible marker and a one-time message naming the encoder, rather than blank cells retried on every refresh.

Two compatibility fixes worth calling out: the cell-size and text-sizing startup probes no longer eat keystrokes typed while they wait, and the probes are now deferred off the mode-enable path so they stop consuming kkp.el's keyboard-protocol reply, which could break it depending on hook order. Beyond that, 1.0.0 includes a full bug-sweep over the process, overlay, and protocol lifecycles, verified finding by finding.

Installation

(use-package kitty-graphics
  :straight (:host github :repo "cashmeredev/kitty-graphics.el")
  :if (not (display-graphic-p))
  :config
  (setq kitty-gfx-enable-video t)
  (kitty-graphics-setup))

If you run into bugs or have feature requests, open an issue on GitHub.

Release: v1.0.0 on GitHub

Tags: #emacs #terminal #kitty #release