# Remove chafa/libglib C Dependencies — Pure Rust Image Preview

## Date: 2026-03-30

## Key Finding

**ratatui-image already has a pure-Rust halfblocks fallback.** Chafa is only used when the
`chafa-dyn` default feature is enabled. Disabling it activates `primitive.rs` instead — a
built-in pure-Rust renderer using unicode half-block characters.

All three real graphics protocols are **already pure Rust**:

| Protocol | Implementation | C deps? |
|---|---|---|
| **Kitty** | Built-in to ratatui-image (base64-simd) | No |
| **iTerm2** | Built-in to ratatui-image (base64-simd + PNG) | No |
| **Sixel** | `icy_sixel` 0.5.0 (pure Rust, SIMD-accelerated) | No |
| **Halfblocks + chafa** | FFI calls to libchafa.so | **Yes — this is the only C dep** |
| **Halfblocks primitive** | Built-in pure Rust (`primitive.rs`) | No |

The conditional compilation in ratatui-image source (`halfblocks.rs`):
```rust
#[cfg(any(feature = "chafa-dyn", feature = "chafa-static"))]
mod chafa;

#[cfg(not(any(feature = "chafa-dyn", feature = "chafa-static")))]
mod primitive;
```

## The Fix: One Line in Cargo.toml

```toml
# Before:
ratatui-image = "10.0"

# After:
ratatui-image = { version = "10.0", default-features = false, features = ["image-defaults", "crossterm"] }
```

## What This Eliminates

### Build dependencies removed:
- `libchafa-dev` (Linux)
- `libglib2.0-dev` (Linux)
- `pkg-config` (Linux)
- `brew install chafa` (macOS)
- `pkg install chafa pkgconf` (FreeBSD)

### Runtime dependencies removed:
- `libchafa0` / `libchafa0t64` (Linux)
- `libglib-2.0` + transitive deps

### CI simplification:
- Remove chafa install from all 4 platform build jobs in `.github/workflows/release.yml`
- `cargo install repartee` works without ANY system libraries

### Docker simplification:
- Can use `scratch` or `distroless/static` base image (~20MB total)
- No need for `debian:trixie-slim` + apt-get (~100MB)

## What Stays the Same

- Kitty graphics protocol — works, pure Rust
- iTerm2 inline images — works, pure Rust
- Sixel graphics — works, pure Rust via `icy_sixel` 0.5.0
- Halfblocks fallback — works, pure Rust via `primitive.rs`
- Image decoding via `image` crate — unchanged
- URL detection, HTTP fetch, disk cache — unchanged
- All config options — unchanged
- `/preview` command — unchanged

## Trade-off

The primitive halfblocks renderer uses simple unicode half-block characters (▀▄) instead of
chafa's wider symbol set and advanced color matching. Quality comparison:

- **chafa**: Uses many Unicode symbols (braille, box-drawing, block elements), better dithering
- **primitive.rs**: Uses only ▀ (upper half-block) with fg/bg color per cell, simpler output

**In practice this barely matters** because:
1. Most modern terminals support Kitty, iTerm2, or Sixel — halfblocks is a last-resort fallback
2. The halfblocks path is only used on very basic terminals (e.g., plain xterm, Linux console)
3. Image preview is a nice-to-have feature, not core IRC functionality

## ratatui-image Usage in Codebase (Full Trace)

### Load-bearing call sites (only 2):
1. `src/image_preview/mod.rs:243` — `picker.new_resize_protocol(dyn_img)` — encodes image
2. `src/ui/image_overlay.rs:166` — `StatefulImage::default()` + `render_stateful_widget()` — renders

### Support infrastructure:
- `src/app/mod.rs:382` — `picker: ratatui_image::picker::Picker` field on App struct
- `src/app/mod.rs:484` — `Picker::from_query_stdio()` — terminal capability detection at startup
- `src/app/mod.rs:492` — `Picker::halfblocks()` — fallback when detection fails
- `src/app/image.rs` — `ProtocolType` matching for cleanup, direct-write, dismiss
- `src/app/session.rs:130` — `Picker::from_fontsize()` on shim reattach
- `src/ui/image_overlay.rs:24` — `ProtocolType::Halfblocks` check for tmux path

### Independent of ratatui-image (reusable with any backend):
- `src/image_preview/detect.rs` — URL extraction and classification (regex only)
- `src/image_preview/fetch.rs` — HTTP fetch with og:image scraping (reqwest only)
- `src/image_preview/cache.rs` — SHA-256 disk cache (std::fs + sha2 only)

### `image` crate usage (minimal, confined to mod.rs):
- `ImageReader::new(Cursor::new(data)).with_guessed_format()?.decode()` — decode bytes
- `dyn_img.write_to(png_buf, ImageFormat::Png)` — re-encode for tmux direct-write

## Files to Update

| File | Change |
|------|--------|
| `Cargo.toml` | Disable `chafa-dyn` default feature |
| `Cargo.lock` | Regenerate (removes chafa-related deps) |
| `.github/workflows/release.yml` | Remove chafa/glib/pkg-config install from all jobs |
| `README.md` | Remove libchafa runtime dependency mention |
| `docs/plans/2026-03-29-docker-image-plan.md` | Simplify — no chafa runtime layer needed |

## Alternative Crates Evaluated (not needed)

| Crate | Version | Pure Rust? | Verdict |
|-------|---------|-----------|---------|
| `icy_sixel` 0.5.0 | Dec 2025 | Yes | Already used by ratatui-image for Sixel |
| `kitty-graphics-protocol` 0.1.3 | Feb 2026 | Yes | Not needed — ratatui-image has built-in Kitty |
| `viuer` 0.11.0 | Dec 2025 | Yes (core) | Wrong paradigm — writes to stdout, not a ratatui widget |
| `rasteroid` 0.1.8 | Jul 2025 | Yes | Early stage, not a ratatui widget |
| `termwiz` 0.23.3 | Mar 2025 | Yes | Overkill — full terminal abstraction from WezTerm |

**Conclusion**: No crate replacement needed. Just disable the chafa feature flag.
