# `ExRatatui.Image`
[🔗](https://github.com/mcass19/ex_ratatui/blob/v0.10.0/lib/ex_ratatui/image.ex#L1)

Construct image widgets from raw image bytes.

Decodes PNG/JPEG/GIF/WebP/BMP binaries into a stateful widget handle
backed by [ratatui-image](https://github.com/ratatui/ratatui-image). The
same widget renders across every ExRatatui transport: in a Kitty-graphics
capable local terminal it uses the Kitty protocol; over `CellSession`
(Livebook / Kino) it falls back to Unicode halfblocks automatically.

```elixir
{:ok, picture} = ExRatatui.Image.new(File.read!("priv/slides/cover.png"))

# Or pick explicit options
{:ok, picture} =
  ExRatatui.Image.new(bytes, resize: :crop, protocol: :kitty)
```

## Options

  * `:protocol` - which terminal image protocol to render with. One of
    `:auto` (default), `:halfblocks`, `:kitty`, `:sixel`, `:iterm2`.
    `:auto` resolves at render time using the transport's capabilities
    (see the [Images guide](images.md) for the resolution table).
    Explicit protocols are honored except over `CellSession`-style
    transports where `:halfblocks` is forced.
  * `:resize` - resize strategy. `:fit` (default, preserve aspect ratio
    inside the rect), `:crop` (preserve aspect, fill the rect, crop the
    overflow), or `:scale` (stretch to fill).
  * `:background` - background color used to fill transparency / unused
    area. Accepts the full `t:ExRatatui.Style.color/0` shape: `nil`
    (default, transparent), a named color atom (`:red`, `:dark_gray`,
    …), an `{:rgb, r, g, b}` tuple, an `{:indexed, n}` xterm 256-color
    code, or a raw `{r, g, b}` tuple. Named and indexed values are
    converted to RGB at the Elixir boundary using the standard ANSI
    palette.

## Errors

`new/2` returns `{:ok, widget}` on success, or
`{:error, {:decode_failed, message}}` when the bytes can't be decoded
as a supported image format.

## Telemetry

Each `new/2` call emits a `[:ex_ratatui, :image, :decode]` span:

  * `:start` metadata — `%{format: atom, bytes: non_neg_integer}`.
    `:format` is one of `:png`, `:jpeg`, `:gif`, `:webp`, `:bmp`, or
    `:unknown` (sniffed from the magic bytes).
  * `:stop` metadata — adds `:width` and `:height` on success, or
    `:error` (reason) on failure.

Per-render encode timing (Kitty / Sixel / iTerm2 payload generation)
isn't emitted as its own event — it happens inside the Rust render
NIF; the existing `[:ex_ratatui, :render, :frame]` span covers total
frame time, which includes image encode.

# `background`

```elixir
@type background() :: nil | ExRatatui.Style.color()
```

# `new_opts`

```elixir
@type new_opts() :: [protocol: protocol(), resize: resize(), background: background()]
```

# `probe_result`

```elixir
@type probe_result() :: %{
  protocol: protocol(),
  font_size: {pos_integer(), pos_integer()}
}
```

# `protocol`

```elixir
@type protocol() :: :auto | :halfblocks | :kitty | :sixel | :iterm2
```

# `resize`

```elixir
@type resize() :: :fit | :crop | :scale
```

# `auto_local_protocol`

```elixir
@spec auto_local_protocol(reference()) :: :ok | {:error, term()}
```

Probes the local terminal and caches the result on `terminal_ref`.

When the probe succeeds, the cached protocol and font size are used for
every `protocol: :auto` image rendered through `terminal_ref` — Kitty
on a Kitty terminal, halfblocks on a basic terminal, etc. When it
fails (no TTY, no response), the cache stays empty and `:auto` images
fall back to halfblocks. Either way this is a one-shot opt-in: call it
once at app start, typically right after acquiring the terminal
reference.

    ExRatatui.run(fn terminal ->
      ExRatatui.Image.auto_local_protocol(terminal)
      # ...
    end)

Returns `:ok` on success, `{:error, reason}` if the probe failed (the
cache is untouched in that case). Per-image explicit protocol choices
at `ExRatatui.Image.new/2` are always honored regardless of the probe.

# `dimensions`

```elixir
@spec dimensions(ExRatatui.Widgets.Image.t() | reference()) ::
  {non_neg_integer(), non_neg_integer()}
```

Return the `{width, height}` of the decoded source image in pixels.

This is the original image's pixel size, not its rendered cell size.
Useful for laying out around an image of known aspect ratio.

# `new`

```elixir
@spec new(binary(), new_opts()) ::
  {:ok, ExRatatui.Widgets.Image.t()} | {:error, {:decode_failed, String.t()}}
```

Decode image `bytes` into a stateful widget.

Returns `{:ok, %ExRatatui.Widgets.Image{}}` on success, or
`{:error, {:decode_failed, message}}` if `bytes` is not a valid
PNG/JPEG/GIF/WebP/BMP payload. The format is auto-detected from the
bytes — no extension or content-type hint is required.

# `probe_terminal`

```elixir
@spec probe_terminal() :: {:ok, probe_result()} | {:error, term()}
```

Queries the local terminal for image-protocol capabilities and font size.

Sends a small escape-sequence probe to stdout and waits for the
terminal's reply on stdin (ratatui-image's `Picker::from_query_stdio`).
Runs on a dirty IO scheduler so it doesn't block the BEAM main run
queue. Returns the detected protocol and cell pixel size on success, or
`{:error, reason}` if the terminal didn't respond, isn't a TTY, or the
probe timed out.

Use this when you want to decide your own fallback policy. Most apps
should call `auto_local_protocol/1` instead, which caches the result
on a terminal reference so `protocol: :auto` images render with the
detected protocol automatically.

# `render_size`

```elixir
@spec render_size(
  {pos_integer(), pos_integer()},
  {pos_integer(), pos_integer()},
  {pos_integer(), pos_integer()},
  resize()
) :: {pos_integer(), pos_integer()}
```

Predicts the rendered output pixel dimensions for an image, given the
cell area it'll be drawn into.

Mirrors ratatui-image's `Resize::needs_resize_pixels` + the
`fit_area_proportionally` helper byte-for-byte (no drift). Useful for
status panels in demos, layout decisions where you want to size sibling
widgets relative to where the image will actually render, or
understanding *why* `:fit` and `:crop` produce identical output when the
source image is smaller than the target area on both axes.

## Inputs

  * `source` — the source image's pixel dimensions as `{width, height}`
    (same as `dimensions/1` returns).
  * `cell_area` — the render area in **cells**, as `{cols, rows}`.
  * `font_size` — the terminal's cell pixel size, as `{width, height}`.
    Get this from `probe_terminal/0` or default to `{10, 20}` (which
    matches `Picker::halfblocks`).
  * `resize` — one of `:fit` / `:crop` / `:scale`.

Returns `{width_px, height_px}` for the rendered pixel size.

## The Fit/Crop no-upscale clamp

Both `:fit` and `:crop` clamp output to the source image's natural
pixel size — they never upscale. This means a 400×300 source rendered
into an 800×500 target stays at 400×300 anchored at the corner. Only
`:scale` upscales aspect-preservingly to fill the area. See the
[Images guide](images.md) for the full rationale.

    iex> ExRatatui.Image.render_size({400, 300}, {80, 24}, {10, 20}, :fit)
    {400, 300}
    iex> ExRatatui.Image.render_size({400, 300}, {80, 24}, {10, 20}, :scale)
    {640, 480}
    iex> ExRatatui.Image.render_size({2000, 1000}, {80, 24}, {10, 20}, :fit)
    {800, 400}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
