image: render inline via kitty / iTerm2 protocols - #42
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stripes/imagefamily 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.image/png,image/jpeg, …) keeps decoder deps scoped — importing onlyimage/pngdoes not pullgolang.org/x/imageinto your build.[](href)badge link) callstripes.Func(contentType, "")dynamically. The markdown package imports nostripes/image/*— dispatch is registry-only. Inline mid-paragraph images stay as the textual placeholder to keep surrounding text intact.Styles.ImageFetcheris the seam markdown uses to fetch non-data refs. The CLI wires it totigerblock/storage.GetObjectsofile://,http(s)://,s3://,gs://,r2://,memory://all flow through. Library callers leave it nil;data:URIs still render (decoded in-process).Styles.SourceNameis set by the CLI to the source filename so the iTerm2 protocol carriesname=<file>instead of "Unnamed file", and so markdown can resolve relative refs against the source.Try it
Test plan
go test ./...greengo list -deps ./markdown/shows nostripes/image/*deps (registry-only dispatch holds)\x1b_G…emitted at the screenshot block)\x1b]1337;File=name=<base64-filename>and uses the source filename, not "Unnamed file"--color=neverforces the placeholder path[image] alt (dest)text🤖 Generated with Claude Code