# `Drafter.Render.Picture`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/render/picture.ex#L1)

Encodes a widget's picture into the bytes and region `Drafter.Compositor.put_image/4`
takes.

A widget exporting `image_raster/2` returns a `t:t/0`: a `FrenchCurve.Raster` covering a box
of cells, the protocol to draw it with, and where the box sits in the widget. The
widget server encodes it with `encode/3`.

On a kitty terminal that keeps images under an id, the whole raster is stored once and
the region carries `:crop`. The compositor then moves the image and shows any part of
it with placements alone, so neither a move nor a scroll re-encodes it.

Every other protocol sends the pixels themselves. Only the part of the box inside the
visible window is encoded, and the region is reduced to that part, so a change of
window needs a new encoding. `window_dependent?/1` says which case a picture is in.

    picture = %{raster: raster, protocol: :kitty, dx: 0, dy: 0, cols: 40, rows: 6}
    {paint, clear, region} = Drafter.Render.Picture.encode(picture, :my_chart, nil)

Two optional keys change how a stored kitty image is sent, each on its own:

  * `format: :png` sends it as a PNG — a palette one when it has at most 256 colours —
    instead of deflated raw pixels (`FrenchCurve.Backend.Kitty.encode/2`), usually far
    smaller for flat-coloured pictures
  * `swap: true` keeps it under two image ids, a new frame stored under the one not on
    screen, placed, and only then the old one deleted, the whole sent as one
    synchronized update, so the terminal never shows the region empty between frames

# `crop`

```elixir
@type crop() :: %{
  :image =&gt; pos_integer(),
  :width =&gt; pos_integer(),
  :height =&gt; pos_integer(),
  optional(:spare) =&gt; pos_integer(),
  optional(:encoded) =&gt; FrenchCurve.Backend.Kitty.encoded()
}
```

The stored kitty image a region shows: its kitty `image` id and its size in pixels. A
picture sent with `swap: true` also has a `spare` id and its `encoded` pixels, which the
compositor stores under whichever of the two is not on screen, after the region's own
bytes.

# `t`

```elixir
@type t() :: %{
  :raster =&gt; FrenchCurve.Raster.t(),
  :protocol =&gt; atom(),
  :dx =&gt; non_neg_integer(),
  :dy =&gt; non_neg_integer(),
  :cols =&gt; pos_integer(),
  :rows =&gt; pos_integer(),
  optional(:format) =&gt; :rgba | :png,
  optional(:swap) =&gt; boolean()
}
```

A widget's picture: `raster` drawn with `protocol` into the `cols` by `rows` cell box
whose top left is `dx`, `dy` cells from the widget's top left.

# `window`

```elixir
@type window() :: %{
  x: integer(),
  y: integer(),
  width: non_neg_integer(),
  height: non_neg_integer()
}
```

A rectangle of cells, relative to the widget's top left.

# `encode`

```elixir
@spec encode(t(), term(), window() | nil) :: {iodata(), iodata(), map()} | nil
```

Encodes `picture` for the image region `id`, showing the part inside `window`.

`window` is the widget's visible cells relative to its top left, or `nil` when all of
it is visible. It is ignored when the terminal keeps kitty images under an id.

Returns `{paint, clear, region}`, or `nil` when no part of the picture is inside
`window` or the protocol cannot draw it. With `:crop` in `region`, `paint` stores the
image without drawing it, and the compositor places it.

# `source`

```elixir
@spec source(
  {pos_integer(), pos_integer()},
  {pos_integer(), pos_integer()},
  {non_neg_integer(), non_neg_integer(), pos_integer(), pos_integer()}
) :: {non_neg_integer(), non_neg_integer(), pos_integer(), pos_integer()}
```

The pixel rectangle `{x, y, width, height}` of a `width` by `height` pixel image drawn
into `cols` by `rows` cells that covers the cells `{col, row, visible_cols,
visible_rows}`, counted from the image's top left cell.

Cell edges are mapped to pixels by rounding down, so adjacent cell ranges share their
boundary pixel row and column exactly.

    iex> Drafter.Render.Picture.source({160, 48}, {40, 6}, {0, 2, 40, 3})
    {0, 16, 160, 24}

# `visible_cells`

```elixir
@spec visible_cells(map(), window() | nil) ::
  {non_neg_integer(), non_neg_integer(), pos_integer(), pos_integer()} | nil
```

The cells of `picture`'s box inside `window`, as `{col, row, cols, rows}` counted from
the box's top left, or `nil` when none are. A `nil` window is the whole box.

    iex> picture = %{dx: 0, dy: 1, cols: 10, rows: 6}
    iex> Drafter.Render.Picture.visible_cells(picture, %{x: 0, y: 3, width: 10, height: 20})
    {0, 2, 10, 4}

# `window_dependent?`

```elixir
@spec window_dependent?(t()) :: boolean()
```

Whether the encoding of `picture` depends on the visible window.

False when the terminal keeps kitty images under an id, where the compositor crops
the stored image; true for every other protocol.

---

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