# `Cauldron2D.Easel`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.0/cauldron_2d_easel/lib/cauldron_2d/easel.ex#L1)

A picture being edited: a palette, frames of palette keys, the frame in hand, and a
history to undo through. Every function is pure; a front end holds the easel and
draws it.

    easel = Cauldron2D.Easel.new(16, 16, palette: [{"R", "#c33"}, {"W", :white}])
    easel = easel |> Cauldron2D.Easel.paint({3, 4}, "R") |> Cauldron2D.Easel.fill({0, 0}, "W")
    easel = Cauldron2D.Easel.undo(easel)
    :ok = Cauldron2D.Easel.save(easel, "priv/art/ship.pic")

A key is one character; `.` is transparent and cannot be a colour. Frames are all the
same size. A change that changes nothing — painting outside the picture, an unknown
key — leaves the easel as it was, with nothing to undo.

The document is what `.pic` holds (`Linocut.Pic`): palette lines, a blank line, the
frames separated by `---`. A `.png` is the frames side by side as a strip; reading one
gives every distinct colour a key of its own, and refuses a picture with more colours
than there are keys.

# `frame`

```elixir
@type frame() :: tuple()
```

# `key`

```elixir
@type key() :: String.t()
```

# `point`

```elixir
@type point() :: {integer(), integer()}
```

# `rgba`

```elixir
@type rgba() :: {byte(), byte(), byte(), byte()}
```

# `t`

```elixir
@type t() :: %Cauldron2D.Easel{
  fps: pos_integer(),
  frame: non_neg_integer(),
  frames: [frame()],
  height: pos_integer(),
  palette: [{key(), rgba()}],
  redo: [term()],
  undo: [term()],
  width: pos_integer()
}
```

# `add_colour`

```elixir
@spec add_colour(t(), key(), Linocut.Palette.spec()) :: t()
```

Add `key` with `colour` to the palette; a key already there, or `.`, changes nothing.

# `add_frame`

```elixir
@spec add_frame(t()) :: t()
```

Add a transparent frame after the one in hand and take it in hand.

# `colour`

```elixir
@spec colour(t(), key()) :: rgba() | nil
```

The colour under `key`, or `nil`.

# `delete_frame`

```elixir
@spec delete_frame(t()) :: t()
```

Drop the frame in hand; the last frame stays.

# `derive`

```elixir
@spec derive(t(), String.t(), Linocut.Derive.lookup()) ::
  {:ok, t()} | {:error, term()}
```

The easel with `line`'s operations applied — `Linocut.Derive`'s, without its `from`:
`colour`, `swap`, `flip`, `turn`, `shift`, `frame`, `reverse`, `over`. `lookup` gives
the pictures `over` names. The result is a new easel with no history, at this one's
rate.

    {:ok, red} = Cauldron2D.Easel.derive(knight, "colour C #c02020; flip h")

# `duplicate_frame`

```elixir
@spec duplicate_frame(t()) :: t()
```

Add a copy of the frame in hand after it and take it in hand.

# `erase`

```elixir
@spec erase(t(), point()) :: t()
```

Make one pixel of the frame in hand transparent.

# `fill`

```elixir
@spec fill(t(), point(), key()) :: t()
```

Flood the area of one key around `point` with `key`.

# `flip`

```elixir
@spec flip(t(), :horizontal | :vertical) :: t()
```

Mirror the frame in hand left to right (`:horizontal`) or top to bottom (`:vertical`).

# `from_pic`

```elixir
@spec from_pic(String.t()) :: {:ok, t()} | {:error, term()}
```

A document from `.pic` text.

# `from_png`

```elixir
@spec from_png(binary(), keyword()) :: {:ok, t()} | {:error, term()}
```

A document from a PNG, every distinct opaque colour given a key.

`frame: {width, height}` cuts the picture into frames of that size, left to right;
without it the picture is one frame. More colours than there are keys is
`{:error, {:too_many_colours, count}}`.

# `key_at`

```elixir
@spec key_at(t(), point(), non_neg_integer()) :: key() | nil
```

The key at `{x, y}` of frame `index`; `.` for transparent, `nil` outside the picture or the frames.

# `keys`

```elixir
@spec keys(t()) :: [key()]
```

The palette's keys, in order.

# `line`

```elixir
@spec line(t(), point(), point(), key()) :: t()
```

Paint a line from `from` to `to`.

# `load`

```elixir
@spec load(Path.t(), keyword()) :: {:ok, t()} | {:error, term()}
```

A document from a `.pic` or `.png` file; `from_png/2`'s options apply to a png.

# `move_frame`

```elixir
@spec move_frame(t(), non_neg_integer(), non_neg_integer()) :: t()
```

Move the frame at `from` to `to` and take it in hand.

# `new`

```elixir
@spec new(pos_integer(), pos_integer(), keyword()) :: t()
```

A transparent picture `width` by `height` with one frame.

## Options

  * `:palette` — `[{key, colour}]`, colours as `Linocut.Palette` reads them. Default `[]`
  * `:fps` — frames a second. Default `8`

# `paint`

```elixir
@spec paint(t(), point(), key()) :: t()
```

Paint one pixel of the frame in hand.

# `raster`

```elixir
@spec raster(t(), non_neg_integer()) :: FrenchCurve.Raster.t()
```

Frame `index` as a raster.

# `rasters`

```elixir
@spec rasters(t()) :: [FrenchCurve.Raster.t()]
```

Every frame as a raster.

# `rect`

```elixir
@spec rect(t(), point(), point(), key(), keyword()) :: t()
```

Paint a rectangle with corners `from` and `to`, its edges only unless `fill: true`.

# `redo`

```elixir
@spec redo(t()) :: t()
```

Forward one undone change; nothing when there is none.

# `remove_colour`

```elixir
@spec remove_colour(t(), key()) :: t()
```

Drop `key` from the palette; its pixels go transparent.

# `rename_key`

```elixir
@spec rename_key(t(), key(), key()) :: t()
```

Call the colour under `from` `to`, in the palette and in every frame.

# `rotate`

```elixir
@spec rotate(t(), integer()) :: t()
```

Turn every frame `degrees` counter-clockwise, a multiple of 90; the picture's size turns with it.

# `save`

```elixir
@spec save(t(), Path.t()) :: :ok | {:error, term()}
```

Write the document as `.pic` or `.png`, by the path's extension.

# `select_frame`

```elixir
@spec select_frame(t(), non_neg_integer()) :: t()
```

Take frame `index` in hand; an index the frames lack changes nothing. Not undone.

# `set_colour`

```elixir
@spec set_colour(t(), key(), Linocut.Palette.spec()) :: t()
```

Change the colour under `key`.

# `set_fps`

```elixir
@spec set_fps(t(), pos_integer()) :: t()
```

Set the frames a second.

# `shift`

```elixir
@spec shift(t(), integer(), integer()) :: t()
```

Move the frame in hand `dx` right and `dy` down, what leaves the picture lost and what comes in transparent.

# `size`

```elixir
@spec size(t()) :: {pos_integer(), pos_integer()}
```

The picture's `{width, height}`.

# `strip`

```elixir
@spec strip(t()) :: FrenchCurve.Raster.t()
```

The frames side by side as one raster.

# `to_pic`

```elixir
@spec to_pic(t()) :: String.t()
```

The document as `.pic` text.

# `to_png`

```elixir
@spec to_png(t()) :: binary()
```

The document as a PNG of its frames side by side.

# `undo`

```elixir
@spec undo(t()) :: t()
```

Back one change; nothing when there is none.

---

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