feat: image rendering — kitty graphics, sixel, and ASCII art backends

This commit is contained in:
Mortdecai
2026-03-29 19:13:45 -04:00
parent f16f16ef77
commit 707e8b7e31
2 changed files with 80 additions and 2 deletions
+76
View File
@@ -0,0 +1,76 @@
"""Image rendering abstraction — kitty graphics, sixel, or ASCII art."""
from __future__ import annotations
import base64
import shutil
import subprocess
import sys
from pathlib import Path
def render_image_kitty(path: str) -> str:
"""Return kitty graphics protocol escape sequence for the image."""
data = Path(path).read_bytes()
b64 = base64.standard_b64encode(data).decode()
chunks = [b64[i:i + 4096] for i in range(0, len(b64), 4096)]
escape = ""
for i, chunk in enumerate(chunks):
m = 1 if i < len(chunks) - 1 else 0
if i == 0:
escape += f"\033_Ga=T,f=100,m={m};{chunk}\033\\"
else:
escape += f"\033_Gm={m};{chunk}\033\\"
return escape
def render_image_sixel(path: str) -> str:
"""Convert image to sixel using chafa or img2sixel."""
if shutil.which("chafa"):
result = subprocess.run(
["chafa", "--format=sixel", "--size=80x40", str(path)],
capture_output=True, text=True,
)
if result.returncode == 0:
return result.stdout
if shutil.which("img2sixel"):
result = subprocess.run(
["img2sixel", "-w", "640", str(path)],
capture_output=True, text=True,
)
if result.returncode == 0:
return result.stdout
return f"[Sixel unavailable — install chafa or libsixel. Image: {path}]"
def render_image_ascii(path: str) -> str:
"""Convert image to ASCII/Unicode block art using chafa."""
if shutil.which("chafa"):
result = subprocess.run(
["chafa", "--size=60x30", str(path)],
capture_output=True, text=True,
)
if result.returncode == 0:
return result.stdout
return f"[Image: {path}]"
def render_image(path: str, protocol: str) -> str:
"""Render an image using the specified protocol. Returns terminal-ready string."""
p = Path(path)
if p.suffix.lower() == ".svg":
try:
import cairosvg
png_path = p.with_suffix(".png")
cairosvg.svg2png(url=str(p), write_to=str(png_path))
path = str(png_path)
except ImportError:
return f"[SVG display requires cairosvg: pip install cairosvg. File: {path}]"
if protocol == "kitty":
return render_image_kitty(path)
elif protocol == "sixel":
return render_image_sixel(path)
else:
return render_image_ascii(path)
+4 -2
View File
@@ -155,8 +155,10 @@ class KittWorkbenchApp(App):
pane = self._get_pane(cmd.pane) pane = self._get_pane(cmd.pane)
if cmd.clear and not isinstance(pane, RichLog): if cmd.clear and not isinstance(pane, RichLog):
pane.remove_children() pane.remove_children()
# Placeholder — image rendering added in Task 7
pane.mount(Static(f"[Image: {cmd.path}]")) from kitty_workbench.image_renderer import render_image
rendered = render_image(cmd.path, self._image_protocol)
pane.mount(Static(rendered, markup=False))
def _handle_log(self, cmd: LogCmd) -> None: def _handle_log(self, cmd: LogCmd) -> None:
try: try: