# `Linocut`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.3/linocut/lib/linocut.ex#L1)

Pictures written in code: a sprite as text with a palette, a shape as an outline, and
the derivations a game needs — headings, recolouring, tinting, scaling, flipping,
turning, cutting into frames, strips; and the same read from `.png` and `.pic` files.

Every function is pure and returns a `FrenchCurve.Raster`.

    ship = Linocut.sprite("""
    ..RR..
    .RWWR.
    RRWWRR
    """, R: "#c33", W: :white)

    headings = Linocut.headings([{15, 0}, {-8, 8}, {-8, -8}], 64, size: {16, 16}, fill: :white)
    team_ship = Linocut.recolour(ship, %{{204, 51, 51, 255} => {51, 51, 204, 255}})

## Colours

A colour in a palette or an option is `"#rgb"`, `"#rrggbb"`, `"#rrggbbaa"`, one of the
names `Linocut.Palette` knows, or an `{r, g, b}` or `{r, g, b, a}` tuple. `.` and space
in a picture are transparent.

## Shapes

An outline is a list of `{x, y}` points with `x` to the right and `y` upward, closed
from the last point back to the first. Heading `0` is the shape as written, pointing
right; headings turn counter-clockwise on screen.

# `color_spec`

```elixir
@type color_spec() :: Linocut.Palette.spec()
```

# `point`

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

# `flip`

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

The raster mirrored left to right (`:horizontal`) or top to bottom (`:vertical`).

# `frames`

```elixir
@spec frames(FrenchCurve.Raster.t(), {pos_integer(), pos_integer()}) :: [
  FrenchCurve.Raster.t()
]
```

The raster cut into frames `{width, height}` pixels each, left to right then top to
bottom. A remainder narrower or shorter than a frame is left out.

# `headings`

```elixir
@spec headings([point()], pos_integer(), keyword()) :: [FrenchCurve.Raster.t()]
```

The shape at each of `count` headings, heading `0` first.

Takes the options of `outline/2`; `:rotate` is added to each heading's turn.

# `load`

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

The raster in a file, by its extension: `.png` through `Linocut.Png`, `.pic` through
`Linocut.Pic` with `opts` as `sprite/3` takes them.

# `outline`

```elixir
@spec outline([point()], keyword()) :: FrenchCurve.Raster.t()
```

A shape drawn from its outline.

## Options

  * `:size` — `{width, height}` of the raster. Required
  * `:fill` — the colour inside. Default: none
  * `:stroke` — the colour of the outline. Default: the fill
  * `:scale` — pixels per shape unit. Default: whatever fits the shape's farthest point
    inside the raster with a one-pixel margin
  * `:rotate` — radians to turn the shape counter-clockwise on screen. Default `0`

# `pic`

```elixir
@spec pic(String.t(), keyword()) :: {:ok, FrenchCurve.Raster.t()} | {:error, term()}
```

A picture in the `.pic` text form, as `Linocut.Pic.parse/2` reads it.

# `recolour`

```elixir
@spec recolour(
  FrenchCurve.Raster.t(),
  %{required(FrenchCurve.Raster.color()) =&gt; color_spec()}
  | (FrenchCurve.Raster.color() -&gt; FrenchCurve.Raster.color())
) :: FrenchCurve.Raster.t()
```

Every pixel passed through `mapping`: a map from colour to colour, or a function of a colour.

# `rotate`

```elixir
@spec rotate(FrenchCurve.Raster.t(), integer()) :: FrenchCurve.Raster.t()
```

The raster turned `degrees` counter-clockwise on screen, a multiple of 90.

# `scale`

```elixir
@spec scale(FrenchCurve.Raster.t(), pos_integer()) :: FrenchCurve.Raster.t()
```

The raster enlarged `factor` times, each pixel becoming a square block.

# `sprite`

```elixir
@spec sprite(String.t(), keyword() | map(), keyword()) :: FrenchCurve.Raster.t()
```

A sprite from a picture and a palette.

## Options

  * `:size` — `{width, height}` of the raster, with the picture centred in it. Default:
    the picture's own size
  * `:background` — the colour of `.` and space. Default: transparent

# `tint`

```elixir
@spec tint(FrenchCurve.Raster.t(), color_spec(), number()) :: FrenchCurve.Raster.t()
```

Every opaque pixel mixed `amount` (0.0 to 1.0) of the way toward `colour`; transparent ones untouched.

# `transform`

```elixir
@spec transform(FrenchCurve.Raster.t(), {pos_integer(), pos_integer()}, (point() -&gt;
                                                                     point())) ::
  FrenchCurve.Raster.t()
```

A raster of `size` with every pixel of `raster` put where `fun` sends its position.

A pixel sent outside the new raster is dropped; a position nothing is sent to keeps
the background.

---

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