"""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)