Skip to content

feat: Sixel グラフィックスプロトコル対応#24

Closed
flexphere wants to merge 5 commits intomainfrom
feat/sixel-renderer
Closed

feat: Sixel グラフィックスプロトコル対応#24
flexphere wants to merge 5 commits intomainfrom
feat/sixel-renderer

Conversation

@flexphere
Copy link
Copy Markdown
Owner

概要

Sixel グラフィックスプロトコルに対応し、--renderer / -r フラグで kitty(デフォルト)または sixel を選択できるようにした。これにより Kitty Graphics Protocol 非対応のターミナル(foot, mlterm, xterm 等)でも画像表示が可能になる。

変更内容

  • SixelRenderer を新規作成(RendererPort インターフェースの実装)
    • Floyd-Steinberg ディザリング + 216色均一パレットによる色量子化
    • RLE 圧縮による効率的な Sixel エスケープシーケンス生成
    • ミニマップ表示(インジケータキャッシュ付き)
  • --renderer / -r CLI フラグを追加(kitty | sixel、デフォルト: kitty
  • createRenderer ファクトリ関数で DI 配線を抽象化
  • README にSixel 関連の情報を追加(対応ターミナル表、使用例)

テスト計画

  • 既存テスト全パス (make ci)
  • 新規テスト追加: internal/adapter/renderer/sixel_renderer_test.go(20テストケース)
    • Display / Upload / Clear の基本動作
    • ミニマップ操作(表示、キャッシュヒット、キャッシュ無効化)
    • Sixel エンコード(パレット生成、RLE圧縮、空画像)
    • ゼロサイズ・未アップロード等のエッジケース
  • 手動確認: Sixel 対応ターミナルでの表示確認

備考

  • RendererPort インターフェースへの変更なし(既存の Kitty レンダリングパスに影響なし)
  • Sixel は毎フレーム再エンコードのため Kitty より描画パフォーマンスが劣る(将来の最適化候補)
  • renderer パッケージのテストカバレッジ: 42% → 83%

🤖 Generated with Claude Code

flexphere and others added 4 commits March 27, 2026 22:13
Implement SixelRenderer as a new RendererPort implementation that outputs
Sixel escape sequences. Unlike Kitty (upload once, display by ID), Sixel
re-encodes each frame inline using Floyd-Steinberg dithering with a
216-color uniform palette.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CLI flag to choose between kitty (default) and sixel renderers.
Introduce createRenderer factory function for DI wiring. Both runStatic
and runViewer now accept and propagate the renderer type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover Display, Upload, Clear, minimap operations, cache behavior,
Sixel encoding, RLE compression, and palette generation. Renderer
package coverage increased from 42% to 83%.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Sixel-capable terminals to compatibility table, document -r/--renderer
flag usage, and update architecture description.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 27, 2026 13:14
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Kitty Graphics Protocol 非対応のターミナルでも画像表示できるように、Sixel グラフィックスプロトコル対応を追加し、CLI からレンダラを選択可能にする PR です。renderer 実装を RendererPort の差し替えで吸収し、既存のユースケース構造に統合しています。

Changes:

  • SixelRenderer(Sixel エンコード、ミニマップ、キャッシュ)を新規追加
  • --renderer / -r フラグと createRenderer ファクトリで kitty|sixel を選択可能に
  • README の対応ターミナル表・使用例を更新、Sixel のテストを追加

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
internal/adapter/renderer/sixel_renderer.go Sixel 描画・ミニマップ描画・Sixel エンコード処理を追加
internal/adapter/renderer/sixel_renderer_test.go SixelRenderer の基本動作、ミニマップ、エンコード周りのテストを追加
cmd/gaze/main.go --renderer/-r 追加とレンダラ生成の DI 抽象化(kitty/sixel 切替)
README.md Sixel 対応の説明・対応ターミナル表・CLI 使用例を追加

Comment on lines +221 to +224
func (r *SixelRenderer) ClearMinimap() error {
r.prevCached = false
r.prevSixel = ""
return nil
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SixelRenderer の ClearMinimap がキャッシュ無効化だけで端末上のミニマップ表示を消していないため、RenderFrameUseCase の「表示→非表示」遷移時にミニマップが画面に残る可能性があります(KittyRenderer は端末側画像を削除して見た目も消える)。Sixel でも最後に描画した位置・サイズを保持して、その領域を空白で上書きする(または ERASE 系 CSI を併用する)など、画面上のミニマップを確実に消す処理を入れてください。

Copilot uses AI. Check for mistakes.
Comment on lines +305 to +318
// Collect colors used in this band
usedInBand := make(map[uint8]bool)
for dy := 0; dy < bandH; dy++ {
rowOff := (bandY + dy) * paletted.Stride
for x := 0; x < w; x++ {
usedInBand[paletted.Pix[rowOff+x]] = true
}
}

sortedColors := make([]uint8, 0, len(usedInBand))
for ci := range usedInBand {
sortedColors = append(sortedColors, ci)
}
sort.Slice(sortedColors, func(i, j int) bool { return sortedColors[i] < sortedColors[j] })
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encodeSixel のバンド処理で usedInBand を map で作成→keys を sort しており、フレーム毎にバンド数分の割り当てとソートが発生します(Sixel は毎フレーム再エンコードなので GC/CPU コストが積み上がりやすいです)。パレットが 216 色で固定なので、[216]bool のフラグ配列 + インデックス配列の再利用などで map/sort を避ける形にすると負荷を下げられます。

Copilot uses AI. Check for mistakes.
Bubbletea's line-based diff renderer corrupts DCS escape sequences when
they are returned via View(). Write Sixel image data directly to stdout
(like Kitty's Upload does) and return only cursor positioning from
Display/DisplayMinimap for Bubbletea to handle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@flexphere flexphere closed this Mar 27, 2026
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.

2 participants