Skip to content

终端图片渲染(Kitty 图形协议)

Vue TUI 通过终端图形协议把像素内容(PNG 图片、数学公式、mermaid 图、视频帧)直接画进真实终端。协议由终端模拟器提供,渲染链路内置在包里:组件把图片 base64 编码进 Kitty / iTerm2 / Sixel escape sequence,在帧末尾由 stdout renderer 写入。终端不支持图形协议时自动降级为文本/ASCII,不会报错。

bash
# 可运行示例
bun run run:image-showcase:terminal     # Markdown 图片(data/http/file/blob/降级)
bun run run:katex-showcase:terminal     # 数学公式(公式图片 + 诊断状态栏)

支持的协议与终端

协议代表终端说明
kittykitty、WezTerm、foot、Ghostty、Konsole推荐;支持像素精确渲染、placement 增量更新与删除
iterm2iTerm2、WezTermPNG inline image protocol
sixelxterm、mlterm、Windows Terminal(需开启)TAgentTerminalGraphic 消费;需要宿主提供 toSixel encoder

协议选择按 kitty > iterm2 > sixel 优先级自动检测:

  • kittyKITTY_WINDOW_IDGHOSTTY_RESOURCES_DIR,或 TERM / TERM_PROGRAMkitty / ghostty
  • iterm2TERM_PROGRAMiterm / wezterm,或设置了 WEZTERM_PANE / WEZTERM_EXECUTABLE
  • sixel:设置了 VUE_TUI_SIXEL / VUE_TUI_GRAPHICS_SIXELTERMINAL_GRAPHICSsixel,或 TERMsixel

没有命中任何候选时,preferredProtocolnull,所有像素组件走降级路径。

依赖安装

终端图片本身零额外依赖。 PNG/JPEG/GIF/WebP 的 base64 编码、尺寸解析(读文件头)全部内置,宿主只需要提供图片字节(base64 字符串)。可选依赖只在你用到对应能力时才需要:

bash
# 核心库
pnpm add @simon_he/vue-tui vue

# 可选:数学公式 → 图片(懒加载,按需安装)
pnpm add mathjax-full @resvg/resvg-js

# 可选:Mermaid 图 → 图片(懒加载,按需安装;复用 TMermaidText 的 beautiful-mermaid 依赖)
pnpm add beautiful-mermaid @resvg/resvg-js

# 可选:Sixel 终端上渲染图片 —— 宿主提供 toSixel encoder
# (例如 libsixel binding、img2sixel 包装,或自定义 renderer)

katex 仍是可选 peer,但只用于旧的文本预览路径;公式图片走 mathjax-full + @resvg/resvg-js,详见 Markdown 数学公式渲染

快速开始 A:Markdown 图片

TMarkdownText / TVirtualMarkdown 支持标准 Markdown 图片语法 ![alt](src)data: URL 内嵌 base64 时完全零配置http(s) / file: / blob: URL 需要传 imageRenderer 把 URL 解析成 base64。

ts
import { TMarkdownText, type TuiMarkdownImageResolver } from "@simon_he/vue-tui/markdown";
import { h } from "vue";

// 1) data URL —— 零配置直接显示
const dataUrl = "data:image/png;base64,iVBORw0KGgo...";

// 2) http / file / blob URL —— 用 imageRenderer 提供 base64
const imageBase64Cache = new Map<string, string | null>(); // URL → base64
const resolveImage: TuiMarkdownImageResolver = (image) => {
  const base64 = imageBase64Cache.get(image.src);
  if (!base64) return null; // 返回 null → 显示 alt 文本
  return { base64, originalBase64: base64, mime: "image/png", originalMime: "image/png" };
};

const content = [
  `Hero image: ![terminal fashion showcase](${dataUrl})`,
  `Remote image: ![showcase http](https://example.com/showcase.png)`,
  `Broken image: ![fallback alt text](https://example.com/missing.png)`,
].join("\n");

h(TMarkdownText, {
  x: 1,
  y: 1,
  w: 96,
  content,
  imageRenderer: resolveImage,
  imageActions: true, // 点击图片触发 imageAction(可做下载/放大等)
  imageMinWidth: 24,
  imageMaxWidth: 72,
  imageMinHeight: 12, // 不设 minHeight 时默认只有 1 行高,务必设置
  imageMaxHeight: 36,
  imagePreserveAspectRatio: true,
});

要点:

  • data: URL 支持 png / jpeg / gif / webp,base64 由解析器自动提取,不需要 imageRenderer
  • imageRenderer 返回值:base64 字符串,或 { base64, originalBase64?, mime?, originalMime? },或 null / undefined(显示 alt 文本)。
  • imageActions 开启后点击图片触发 imageAction,payload 带 image(含 base64 / originalBase64,可用于下载保存)和 rect(图片所在 cell 区域,可定位菜单)。
  • 尺寸 props 作用于该块内所有图片,做等比缩放;imagePreserveAspectRatio: false 时按格子拉伸。

快速开始 B:TAgentTerminalGraphic(通用图形组件)

需要直接控制一张图片/一个图形的渲染时用 TAgentTerminalGraphic(从 @simon_he/vue-tui/agent 引入)。它在 TUI buffer 里占用指定 cell rect,通过 createStdoutRenderer() 注册的 terminal graphics output 在帧末尾写入 raw escape payload。

ts
import { TAgentTerminalGraphic, createPngTerminalGraphicRenderer } from "@simon_he/vue-tui/agent";

// 组合 Kitty / iTerm2 序列的 PNG renderer;sixel 需要额外 toSixel encoder
const pngRenderer = createPngTerminalGraphicRenderer({
  async toPngBase64(content, ctx) {
    // content:kind="image" 时是图片数据/引用;kind="math" 时是 TeX
    // ctx 里有 preferredProtocol、capabilities、imageId、placementId、signal
    return { base64: "iVBORw0KGgo...", cols: 72, rows: 12 };
  },
  fallback: (content) => "[image]", // 无图形协议时显示
});
vue
<TAgentTerminalGraphic
  :x="0"
  :y="2"
  :w="72"
  :h="12"
  kind="image"
  content="..."
  fallback="[image]"
  :renderer="pngRenderer"
/>

要点:

  • renderer(content, context) 返回 { type: "sequence", protocol, sequence, fallback?, clearSequence?, resizeSequence?, rows?, cols? } 作为可信 escape,或 { type: "text", text } 作为降级文本;返回 null / undefined / 抛错都显示 fallback(不进入错误态)。
  • 安全边界:组件只信任 renderer 返回的 sequence;bare string 一律按普通文本 fallback 处理,不会作为 raw escape 写入 stdout。
  • kind="math" 可配合 KaTeX/LaTeX 渲染器(见 Markdown 数学公式渲染)。
  • 滚动/懒渲染场景:deferRenderUntilVisiblesuspendRenderWhileScrolling / suspendedcreateTerminalGraphicRenderQueue() 控制并发、缓存与取消。

快速开始 C:Mermaid 图片(TMermaidImage)

TMermaidImage 把 mermaid 源码渲染成 PNG 并通过图形协议显示,实现方式与 KaTeX 数学公式图片一致:beautiful-mermaidrenderMermaidSVGAsync,零 DOM 依赖)→ @resvg/resvg-js → PNG → Kitty / iTerm2 sequence。依赖都是 optional peer,懒加载。

ts
import { TMermaidImage } from "@simon_he/vue-tui/agent/mermaid";
import { h } from "vue";

h(TMermaidImage, {
  x: 0,
  y: 0,
  w: 72,
  content: `graph TD
  Prompt --> Plan
  Plan --> ToolCall
  ToolCall --> Answer`,
  // 可选:h 省略时按图片宽高比自适应;maxWidthCells / maxHeightCells 限制最大格数
});

要点:

  • 终端不支持图形协议、stdout 非 TTY、tmux/screen/zellij 未开 passthrough、或 rasterizer 缺失时,自动降级为显示原始 mermaid 源码(不会报错)。
  • 点击图片区域或 header 的 copy 按钮,复制完整 mermaid raw content(触发 copy 事件,payload 带 text)。
  • 自定义渲染管线:传 rendererTuiMermaidImageRasterizer),返回 { base64, widthCells, heightCells, naturalWidth?, naturalHeight? }null
  • 模块级缓存/门控 API(getMermaidImage / getCachedMermaidImage / loadMermaidImageRenderer / isMermaidImageRendererReady / setMermaidImageRasterizer / clearMermaidImageCache / subscribeMermaidImage)与 math-image 对齐,便于在 markdown 等宿主里复用同一张缓存图。
  • 仅支持 kitty / iterm2;sixel 终端会走源码降级(createTerminalGraphicPngSequence 不产 sixel)。

能力检测与诊断

ts
import { detectTerminalGraphicsCapabilities } from "@simon_he/vue-tui/agent";

const caps = detectTerminalGraphicsCapabilities();
console.log(caps.protocol); // "kitty" | "iterm2" | "sixel" | "unicode" | "none"
console.log(caps.supported); // boolean
console.log(caps.preferredProtocol);
console.log(caps.reason); // 为什么走了当前路径
console.log(caps.stdoutIsTTY, caps.multiplexer, caps.passthrough);

一条命令输出完整诊断(协议、原因、环境变量、以及数学公式渲染栈状态):

bash
npx tsx scripts/check-math-graphics.ts

环境变量

变量说明
VUE_TUI_TERMINAL_GRAPHICS / VUE_TUI_GRAPHICS_PROTOCOLauto / off / kitty / iterm2 / sixel / unicode手动指定协议;off 禁用,unicode 强制文本
VUE_TUI_GRAPHICS_FORCE1强制启用(跳过 TTY / CI / 复用器检查,用于调试)
VUE_TUI_GRAPHICS_TMUX_PASSTHROUGH / VUE_TUI_TERMINAL_GRAPHICS_PASSTHROUGH1在 tmux 里开启 passthrough 转发
VUE_TUI_SIXEL / VUE_TUI_GRAPHICS_SIXEL1手动声明 sixel 能力

降级与多路复用器

像素组件在以下情况自动降级为文本,不会报错:

场景行为
终端不支持图形协议TAgentTerminalGraphic / Markdown 内嵌图形显示 fallback / alt 文本
stdout 不是 TTY(管道/CI)同上
在 tmux / screen / zellij 中图形协议默认关闭(复用器不转发)。tmux 可开 passthrough:VUE_TUI_GRAPHICS_TMUX_PASSTHROUGH=1;screen/zellij 退出复用器或使用支持转发的终端
只有 sixel 终端TVideo / T3DViewport 像素帧路径不支持 sixel,降级为 gray8 ASCII art;TAgentTerminalGraphic 仍可用(需 toSixel encoder)

生产环境建议同时 smoke test 有/无图形协议两种终端(Kitty 原生、iTerm2 inline image、tmux passthrough、CI/非 TTY)。

Markdown 图片 Props

TMarkdownTextTVirtualMarkdown 共用以下图片相关 props:

Prop类型默认说明
imageRendererTuiMarkdownImageResolverundefinedhttp(s) / file: / blob: 图片 URL 解析为 base64
imageMinWidth / imageMaxWidthnumberundefined图片显示宽度范围(格子),超范围等比缩放
imageMinHeight / imageMaxHeightnumberundefined图片显示高度范围(格子);建议 imageMinHeight ≥ 3
imagePreserveAspectRatiobooleantrue按原始宽高比缩放;false 时拉伸到限制范围
imageActionsbooleanfalse开启后点击图片触发 imageAction
imageOcclusionRectsRect[]undefined遮盖区域(如悬浮菜单),图片点击命中会跳过这些区域

TAgentTerminalGraphic 关键 Props

Prop类型默认说明
x / y / w / hnumber—(x/y/w 必填)图片占用的 cell rect;h 省略时用 renderer 返回的 rows 推导
kind"image" / "math""image"图形类型
contentstring—(必填)传给 renderer 的内容(图片数据 / TeX)
rendererTAgentTerminalGraphicRendererundefined返回可信 escape 序列或降级文本
fallbackstringundefined无协议 / renderer 返回空 / 抛错时显示;kind="image" 默认空文本
deferRenderUntilVisiblebooleantrue滚动/隐藏时不渲染
suspendRenderWhileScrolling / suspendRawWhileScrollingbooleantrue滚动期间暂停渲染/暂停 raw 重绘
zIndexnumber0Kitty placement z-index
trace(event) => voidundefined渲染过程 trace 事件(调试用)

常见问题

终端里只显示 alt 文本 / fallback,没有图片

按优先级检查:

  1. 终端不支持图形协议:Kitty、Ghostty、WezTerm、foot、iTerm2 支持;纯 xterm、CI 输出不支持。用 detectTerminalGraphicsCapabilities()reason
  2. 在 tmux / screen / zellij 里运行:退出复用器,或 tmux 开 VUE_TUI_GRAPHICS_TMUX_PASSTHROUGH=1
  3. stdout 不是 TTY:管道 / 重定向 / CI 下不会启用。
  4. imageRenderer 返回了 null(Markdown 图片):确认 URL 在缓存里、字节读取成功、返回的是合法 base64。
  5. Sixel 终端缺 encoderTAgentTerminalGraphic 需要 toSixelTVideo / T3DViewport 不支持 sixel 像素帧。
  6. 图片高为 1 格(Markdown):没有设置 imageMinHeight,默认高度只有 1 行,设置 imageMinHeight ≥ 3。

图片太大 / 太小 / 变形

  • 设置 imageMinWidth / imageMaxWidth / imageMinHeight / imageMaxHeight 约束格子范围。
  • 变形时确认 imagePreserveAspectRatiotrue(默认),并确认 PNG 文件头可读(内置尺寸解析失败会回退为 1:1 格子)。
  • TAgentTerminalGraphic 场景调 createPngTerminalGraphicRenderertoPngBase64 返回的 cols / rows,或传 h 显式指定占用高度。

图片是旧的 / 不更新

  • PNG 转换、renderer 结果有缓存:createPngTerminalGraphicRenderer 的默认 cache key 覆盖 kind、尺寸、final、content/组件 cacheKey 和 renderer cacheSalt。内容变化请更新组件 cacheKey;fallback 依赖协议/主题/字体等时传 cacheSalt 或自定义 cacheKey()

安全

  • 组件只把 renderer 返回的 { type: "sequence" } 当作 raw escape 写入 stdout;bare string 只作为文本 fallback,杜绝 escape injection。
  • 图片 URL 会经过 sanitizeMarkdownImageSource 校验(仅 png / jpeg / gif / webpdata: URL 被接受,blob: / file: 走 URL 规范化),并限制 base64 与序列长度。

Bug reports, feature requests, and documentation issues are tracked on GitHub Issues.