Skip to content

terminal/kitty: accept first-frame GIF images via Wuffs#381

Merged
deblasis merged 1 commit into
windowsfrom
kitten-into-wintty/iterm2-gif
May 19, 2026
Merged

terminal/kitty: accept first-frame GIF images via Wuffs#381
deblasis merged 1 commit into
windowsfrom
kitten-into-wintty/iterm2-gif

Conversation

@deblasis
Copy link
Copy Markdown
Owner

Summary

Extend the Kitty graphics image decoder to handle GIF payloads, rendering the first frame only. Animation requires a separate design pass (renderer-side clock, disposal compositing, repaint hooks); this PR establishes the still-image plumbing so emitters using iTerm2 OSC 1337 File= can ship GIF logos and screenshots.

Seventh PR in the iTerm2 OSC 1337 series (after #375 #376 #377 #378 #379 #380). Closes the image-format gap on the iTerm2 synth path: PNG + JPEG + GIF.

Architecture

Mirrors PNG/JPEG plumbing end-to-end:

  • pkg/wuffs/src/c.zig enables WUFFS_CONFIG__MODULE__GIF (and the WUFFS_CONFIG__MODULE__LZW module the GIF decoder depends on). The build flag flows through to the Wuffs amalgamation via the existing defines -> flags pipeline in pkg/wuffs/build.zig.
  • New pkg/wuffs/src/gif.zig wrapper mirrors jpeg.zig with two GIF-specific additions:
    • The decode_frame_config step that GIF requires before decode_frame (PNG/JPEG short-circuit because they are single-frame).
    • A @memset on the destination buffer so sub-canvas frames keep transparent pixels rather than carrying debug-allocator poison bytes.
  • After decoding the first frame, a second decode_frame_config call detects multi-frame sources and emits a log.debug so dropped frames are visible at --log-level=debug.
  • decode_gif sys slot in src/terminal/sys.zig, GhosttySysDecodeGifFn typedef + GHOSTTY_SYS_OPT_DECODE_GIF=4 enum entry in include/ghostty/vt/sys.h, new .gif Format variant + GHOSTTY_KITTY_IMAGE_FORMAT_GIF=6 in the kitty graphics C ABI, init guard + shared-memory size + complete() dispatch arms in src/terminal/kitty/graphics_image.zig, renderer switch arm in src/renderer/image.zig, and iTerm2 OSC 1337 synth sniff for the "GIF8" magic (47 49 46 38) covering both GIF87a and GIF89a.

Tests

  • 2 new wuffs unit tests (gif_decode_000000, gif_decode_FFFFFF).
  • 2 new C ABI tests (set decode_gif with null clears, set decode_gif installs wrapper) mirroring PNG/JPEG coverage.
  • 1 new iTerm2 synth test using a GIF89a header payload.
  • Rename of the existing "non-PNG non-JPEG bytes" test to "unknown bytes".
  • Full zig build test green on Windows and macOS.

Out of scope

  • GIF animation — needs a renderer clock, disposal-aware compositing, and repaint hooks. Separate design pass.
  • Windows C# decode callbackswindows/Ghostty/Interop/NativeMethods.cs doesn't install any of the PNG/JPEG/GIF callbacks today. This PR inherits the gap; a separate PR can add the C# bindings.

Test plan

  • zig build test passes on Windows
  • zig build test passes on macOS (via SSH bundle)
  • Interactive smoke with imgcat logo.gif on Wintty.exe

Extend the Kitty graphics image decoder to handle GIF payloads,
rendering the first frame only. Animation requires a separate design
pass (renderer-side clock, disposal compositing, repaint hooks); this
PR establishes the still-image plumbing so emitters using iTerm2 OSC
1337 File= can ship GIF logos and screenshots.

- pkg/wuffs/src/c.zig enables WUFFS_CONFIG__MODULE__GIF (and the LZW
  module that the GIF decoder depends on); the build flag flows
  through to the wuffs amalgamation via the existing defines->flags
  pipeline in pkg/wuffs/build.zig.
- New pkg/wuffs/src/gif.zig wrapper mirrors jpeg.zig with the extra
  decode_frame_config step that GIF requires before decode_frame, and
  a destination-buffer @Memset so sub-canvas frames keep transparent
  pixels rather than carrying debug-allocator poison bytes. After
  decoding the first frame, a second decode_frame_config call detects
  multi-frame sources and emits a log.debug so the dropped frames are
  visible at --log-level=debug.
- Mirrors PNG/JPEG plumbing end-to-end: decode_gif sys slot,
  GhosttySysDecodeGifFn C ABI typedef + GHOSTTY_SYS_OPT_DECODE_GIF=4
  enum entry, new .gif Format variant on the kitty graphics
  Transmission enum (Ghostty-internal; no Kitty wire f= code) with
  the matching GHOSTTY_KITTY_IMAGE_FORMAT_GIF=6 C ABI entry, init
  guard / shared-memory size / complete() dispatch arms, renderer
  switch arm, and iTerm2 OSC 1337 synth sniff for the "GIF8" magic
  (47 49 46 38) covering both GIF87a and GIF89a.

Verified end-to-end on Windows and macOS.
@deblasis deblasis marked this pull request as ready for review May 19, 2026 08:50
@deblasis deblasis merged commit a4fc9fa into windows May 19, 2026
99 checks passed
@deblasis deblasis deleted the kitten-into-wintty/iterm2-gif branch May 19, 2026 08:50
deblasis added a commit that referenced this pull request May 21, 2026
Extend the Kitty graphics image decoder to handle GIF payloads,
rendering the first frame only. Animation requires a separate design
pass (renderer-side clock, disposal compositing, repaint hooks); this
PR establishes the still-image plumbing so emitters using iTerm2 OSC
1337 File= can ship GIF logos and screenshots.

- pkg/wuffs/src/c.zig enables WUFFS_CONFIG__MODULE__GIF (and the LZW
  module that the GIF decoder depends on); the build flag flows
  through to the wuffs amalgamation via the existing defines->flags
  pipeline in pkg/wuffs/build.zig.
- New pkg/wuffs/src/gif.zig wrapper mirrors jpeg.zig with the extra
  decode_frame_config step that GIF requires before decode_frame, and
  a destination-buffer @Memset so sub-canvas frames keep transparent
  pixels rather than carrying debug-allocator poison bytes. After
  decoding the first frame, a second decode_frame_config call detects
  multi-frame sources and emits a log.debug so the dropped frames are
  visible at --log-level=debug.
- Mirrors PNG/JPEG plumbing end-to-end: decode_gif sys slot,
  GhosttySysDecodeGifFn C ABI typedef + GHOSTTY_SYS_OPT_DECODE_GIF=4
  enum entry, new .gif Format variant on the kitty graphics
  Transmission enum (Ghostty-internal; no Kitty wire f= code) with
  the matching GHOSTTY_KITTY_IMAGE_FORMAT_GIF=6 C ABI entry, init
  guard / shared-memory size / complete() dispatch arms, renderer
  switch arm, and iTerm2 OSC 1337 synth sniff for the "GIF8" magic
  (47 49 46 38) covering both GIF87a and GIF89a.

Verified end-to-end on Windows and macOS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant