#+TITLE: kitty-graphics.el #+AUTHOR: cashmere Display images in terminal Emacs (~emacs -nw~) using the [[https://sw.kovidgoyal.net/kitty/graphics-protocol/][Kitty graphics protocol]] or [[https://en.wikipedia.org/wiki/Sixel][Sixel]]. * Demo *Kitty backend (truecolor):* [[file:kitty.gif]] *Sixel backend (foot):* [[file:foot.gif]] * Foreword I got the opportunity to subscribe to the highest Claude level. I'm allowed to use it outside of work. I was able to extend Emacs in terminal mode with the kitty protocol. Out of curiosity I wanted to know if a thought I had for months was possible or not, which was basically to extend libvterm with the kitty image protocol and then utilize vterm to display those images inside Emacs Terminal. Besides, I don't even have the guts to release it, even though my intention of such a release would be first and foremost for the people who have been asking for such a feature specifically for years, or for other developers to use it as some kind of reference. So idk, while I totally agree that the current AI slop is getting to be exhausting, we as humans should judge projects on a project by project basis. * Overview ~kitty-graphics.el~ renders images directly in terminal Emacs using either the Kitty graphics protocol or Sixel, selected automatically at startup. The backend dispatch tries Kitty first (fast ~KITTY_PID~ env check), then probes for Sixel support, so the package works across ~20+ terminals without configuration. With the Kitty backend, images are transmitted once and positioned via direct placements at overlay screen coordinates after each redisplay. The Sixel backend encodes images via ImageMagick and re-emits them on scroll. Both backends integrate with Emacs's overlay display engine --- images scroll with text, survive buffer switches, and work in split windows. Integrations are provided for: - *org-mode* --- inline images via =C-c C-x C-v= (both =org-toggle-inline-images= and =org-link-preview= for org 10.0+) - *org LaTeX preview* --- render LaTeX fragments as images via =C-c C-x C-l= - *doc-view* --- PDF/DVI/PS viewing with page navigation and zoom - *image-mode* --- terminal-aware image file viewing - *shr/eww* --- inline images in HTML rendering (eww, mu4e, gnus) - *dired* --- image preview in side window (=P= key) - *dirvish* --- native preview dispatcher for image files * Requirements - Emacs >= 27.1 - A supported terminal: - *Kitty backend*: [[https://sw.kovidgoyal.net/kitty/][Kitty]] >= 0.20.0, WezTerm, or Ghostty - *Sixel backend*: foot, Konsole, xterm (with ~+sixel~), mlterm, mintty, VS Code Terminal, and others with Sixel support - A Sixel encoder on ~PATH~ --- /required/ for the Sixel backend. - *Strongly recommended*: ~img2sixel~ from [[https://github.com/saitoha/libsixel][libsixel]] (Nix: ~nixpkgs#libsixel~). Roughly =2.2x= smaller payloads and =~10x= faster than ImageMagick, which matters a lot in tmux where every refresh re-emits the full DCS payload. - *Fallback*: ImageMagick 7 (~magick~) or 6 (~convert~). Works, but slower; on long sessions and in tmux you may notice flicker on redraw. - Override the auto-detect order via ~kitty-gfx-sixel-encoder-program~. If neither is on ~PATH~ the Sixel backend logs ~no encoder found~ and silently produces no image, so make sure one is installed before launching Emacs. - ImageMagick (~magick~ / ~identify~) --- still needed for non-PNG formats on the Kitty backend (JPEG/WebP/SVG/TIFF/BMP -> PNG conversion) and for the dimension probe - For LaTeX preview: a TeX distribution with ~dvipng~ (e.g. ~texlive~) - For doc-view: ~ghostscript~ (for PDF), ~dvipng~ (for DVI) --- same tools as GUI Emacs - Launch Emacs with ~TERM=xterm-256color~ (Emacs often can't find the ~xterm-kitty~ terminfo) #+begin_src sh TERM=xterm-256color emacsclient -nw #+end_src * Installation ** straight.el #+begin_src emacs-lisp (use-package kitty-graphics :straight (:local-repo "~/projects/kitty-graphics") :if (not (display-graphic-p)) :config (kitty-graphics-mode 1)) #+end_src ** Manual #+begin_src emacs-lisp (add-to-list 'load-path "~/projects/kitty-graphics") (require 'kitty-graphics) (when (not (display-graphic-p)) (kitty-graphics-mode 1)) #+end_src * Usage Enable the global minor mode: #+begin_src emacs-lisp (kitty-graphics-mode 1) #+end_src Then: - In org-mode: toggle inline images with =C-c C-x C-v= - In org-mode: preview LaTeX fragments with =C-c C-x C-l= (requires a LaTeX installation and =dvipng=) - Open a PDF: =doc-view-mode= renders pages via Kitty (=n= / =p= to navigate, =+= / =-= / =0= to zoom) - In dired: press =P= on an image file for a side-window preview - In dirvish: image previews work automatically (no extra config) - Open an image file: =image-mode= displays it via Kitty - In eww/mu4e/gnus: HTML images render inline ** Commands | Command | Description | |-------------------------+------------------------------------| | ~kitty-graphics-mode~ | Toggle the global minor mode | | ~kitty-gfx-display-image~ | Display a single image at point | | ~kitty-gfx-remove-images~ | Remove images in region or buffer | | ~kitty-gfx-clear-all~ | Remove all images from all buffers | | ~kitty-gfx-dired-preview~ | Preview image at point in dired | ** Customization | Variable | Default | Description | |--------------------------------+---------+------------------------------------------| | ~kitty-gfx-preferred-protocol~ | auto | Graphics protocol: auto, kitty, or sixel | | ~kitty-gfx-max-width~ | 120 | Maximum inline image width in columns | | ~kitty-gfx-max-height~ | 40 | Maximum inline image height in rows | | ~kitty-gfx-render-delay~ | 0.016 | Debounce delay for re-rendering (s) | | ~kitty-gfx-debug~ | nil | Log debug info to ~/tmp/kitty-gfx.log~ | These defaults control inline image sizing (org-mode, eww, dired previews). Doc-view ignores these and fills the window, with =+= / =-= / =0= for zoom. * How It Works 1. On startup, backend dispatch selects a protocol: if ~kitty-gfx-preferred-protocol~ is ~auto~, it checks for Kitty (via ~KITTY_PID~), then probes Sixel via a DA1 query. The mode-line shows ~[K]~, ~[S]~, or ~[?]~ to indicate the active backend. 2. Cell pixel size is queried via ~CSI 16 t~ (XTWINOPS) for accurate image scaling. Falls back to 8x16 if the query times out. 3. *Kitty path*: image data is transmitted once via APC escape sequences (~a=t~, store only). Direct placements (~a=p~) position the image at overlay screen coordinates after each redisplay, using unique placement IDs (~p=PID~). 4. *Sixel path*: images are encoded to Sixel via ImageMagick and emitted directly at overlay screen coordinates. Since Sixel is stateless, images are re-emitted whenever they scroll to a new position. 5. Overlays with a ~display~ property reserve blank space in Emacs buffers 6. All output is wrapped in synchronized output (~BSU/ESU~, =DEC mode 2026=) to prevent partial rendering and flicker 7. Position caching skips redundant re-placements when nothing moved 8. Placements are deleted when overlays scroll out of view * Supported Formats PNG is sent directly to the terminal. Other formats (JPEG, GIF, WebP, SVG, TIFF, BMP) are converted to PNG via ImageMagick before transmission. * Known Limitations - Partial tmux support: Sixel works inside tmux >= 3.4 (native sixel rendering, foot/Konsole/mintty/mlterm/WezTerm as outer terminal); set ~kitty-gfx-tmux-allow-sixel~ to ~nil~ to opt out. Images may stick to old positions after scrolling because tmux's cell buffer is not pixel-aware (upstream tmux limitation). Kitty graphics passthrough inside tmux is still unimplemented - Animated GIFs display only the first frame - Sixel is limited to 256 colors (vs truecolor on Kitty) and is stateless --- images are re-emitted on every scroll, which may be slower on large images - The Sixel backend needs a Sixel encoder on ~PATH~. ~img2sixel~ (libsixel) is strongly recommended; ~magick~ / ~convert~ work as fallback but are noticeably slower. With no encoder on ~PATH~, the backend silently produces no output and only logs ~no encoder found~ to ~/tmp/kitty-gfx.log~ when ~kitty-gfx-debug~ is non-nil - Without ImageMagick, only PNG files work on the Kitty backend - Doc-view page may briefly flash at wrong position before centering on initial load * License GPL-2.0-or-later