Skip to content

image: render inline via kitty / iTerm2 protocols - #42

Merged
achille-roussel merged 4 commits into
mainfrom
images
May 27, 2026
Merged

image: render inline via kitty / iTerm2 protocols#42
achille-roussel merged 4 commits into
mainfrom
images

Conversation

@achille-roussel

Copy link
Copy Markdown
Contributor

Summary

  • stripes/image family routes PNG / JPEG / GIF / WebP / BMP / TIFF inputs through the kitty graphics or iTerm2 inline-image protocols via rasterm. Detection is pure env-var (no live TTY query). Unsupported terminals get a styled [image: PNG 1920×1080, 245 KiB] (terminal does not support inline images) placeholder.
  • One subpackage per format (image/png, image/jpeg, …) keeps decoder deps scoped — importing only image/png does not pull golang.org/x/image into your build.
  • Markdown integration auto-renders block-level images: paragraphs whose sole renderable child is an image (or a [![alt](src)](href) badge link) call stripes.Func(contentType, "") dynamically. The markdown package imports no stripes/image/* — dispatch is registry-only. Inline mid-paragraph images stay as the textual placeholder to keep surrounding text intact.
  • Styles.ImageFetcher is the seam markdown uses to fetch non-data refs. The CLI wires it to tigerblock/storage.GetObject so file://, http(s)://, s3://, gs://, r2://, memory:// all flow through. Library callers leave it nil; data: URIs still render (decoded in-process).
  • Styles.SourceName is set by the CLI to the source filename so the iTerm2 protocol carries name=<file> instead of "Unnamed file", and so markdown can resolve relative refs against the source.

Try it

KITTY_WINDOW_ID=1 go run ./cmd/stripes --paging=never README.md
KITTY_WINDOW_ID=1 go run ./cmd/stripes --paging=never assets/screenshot.png
go run ./cmd/stripes --color=never assets/screenshot.png   # → fallback placeholder line

Test plan

  • go test ./... green
  • go list -deps ./markdown/ shows no stripes/image/* deps (registry-only dispatch holds)
  • CLI renders README.md inline images on kitty (\x1b_G… emitted at the screenshot block)
  • iTerm2 path emits \x1b]1337;File=name=<base64-filename> and uses the source filename, not "Unnamed file"
  • --color=never forces the placeholder path
  • data: URIs render without a fetcher
  • Fetcher errors fall back to placeholder; no half-written escape sequences
  • Mid-paragraph images keep the existing [image] alt (dest) text

🤖 Generated with Claude Code

achille-roussel and others added 4 commits May 27, 2026 07:56
The new stripes/image package routes PNG/JPEG/GIF/WebP/BMP/TIFF inputs
through the kitty graphics or iTerm2 inline-image protocols (env-var
detection only — no live TTY query). Terminals that advertise neither
get a styled "[image: PNG 1920×1080, 245 KiB] (terminal does not
support inline images)" placeholder. PNG inputs take a zero-decode
passthrough on the kitty path; other formats decode then re-encode.

One subpackage per format (image/png, image/jpeg, …) keeps decoder
deps scoped — a binary that only imports image/png does not pull in
golang.org/x/image.

Markdown picks up where the registry leaves off: paragraphs whose
sole renderable child is an image (or a single-image link, the badge
pattern) attempt inline rendering via stripes.Func(contentType, "").
The markdown package imports no stripes/image/* — the dispatch is
purely dynamic.

Image bytes come from a new Styles.ImageFetcher callback. The CLI
wires it to tigerblock storage so file://, http(s)://, s3://, gs://,
r2://, memory:// all work for non-data refs. Library callers leave it
nil; data: URIs still render (decoded in-process). data URIs aside,
non-data refs without a fetcher fall back to the existing
"[image] alt (dest)" placeholder.

Inline images in flowing text stay as the placeholder — kitty/iTerm2
escapes move the cursor below the image, which would corrupt
surrounding paragraph text.

Styles also gains a SourceName field so the image renderer can
populate the iTerm2 name= header instead of "Unnamed file", and so
markdown can resolve relative refs against the source. Path semantics
for plain filesystem sources (avoids Go's URL.ResolveReference
prepending a spurious leading slash for path-only bases), URL
semantics when the source has a scheme.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-up fixes for the inline-image renderer:

1. markdown: surface HTML <img> tags from raw HTML blocks. The README
   logo lives inside a `<p align="center"><img ...></p>` HTML block
   that isRenderableBlock previously dropped. isRenderableBlock now
   keeps HTML blocks that wrap an <img>, and renderMarkdownBlock
   extracts the src via golang.org/x/net/html and dispatches through
   tryRenderInlineImageRef — the same registry-lookup path used for
   markdown ![]() syntax.

2. image: left-align small images instead of stretching them. The
   kitty / iTerm2 width constraint was always set to styles.Width,
   which upscaled small images (logo, badges) across the whole
   terminal. New scaleToFit decodes the image config first, returns 0
   (no constraint, native pixel size) when the image fits the
   terminal's estimated pixel width, and returns styles.Width only
   when the image needs to shrink.

3. cli: drop auto-paging when any file resolves to an image content
   type. less -R doesn't preserve OSC/APC graphics escapes, so the
   pager turned multi-image runs into base64 ASCII soup. The override
   only fires for --paging=auto; --paging=always still respects the
   explicit user choice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
less -R doesn't preserve kitty/iTerm2 graphics escapes, so any image
rendered inline inside markdown turns into base64 garbage as soon as
the auto-pager spawns. Disabling the pager for markdown isn't an
option — pager-driven readability is what makes the markdown renderer
useful in the first place.

Reverts on top of the inline-image PR:
- Drop tryRenderInlineImage, paragraphImage, decodeDataURI,
  resolveImageRef, firstImgSrc from stripes/markdown — paragraphs
  containing images go back to the "[image] alt (dest)" placeholder.
- Drop the Styles.ImageFetcher seam and the CLI's tigerblock-backed
  fetcher closure (with its ctx threading through renderOne).
- Drop golang.org/x/net/html, net/url, bufio, encoding/base64, path
  imports that supported the dropped code paths.

Kept:
- The stripes/image/* sub-packages — direct image files still render
  inline via the registry, with the auto-pager-disable safeguard
  added alongside.
- Styles.SourceName — the image renderer still uses it to populate
  the iTerm2 name= header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@achille-roussel
achille-roussel merged commit ced7f77 into main May 27, 2026
1 check passed
@achille-roussel
achille-roussel deleted the images branch May 27, 2026 15:39
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