# `FrenchCurve.Raster`
[🔗](https://github.com/jaman/french_curve/blob/v0.1.4/lib/french_curve/raster.ex#L1)

A fixed-size RGBA pixel buffer, the value every drawing and backend function operates on.

A raster has a `width`, a `height`, a `background` colour returned for any pixel never
written, and two interchangeable representations of its contents:

  * `pixels` — a sparse map of `{x, y}` to colour, cheap to draw into
  * `rgba` — a dense row-major `width * height * 4` byte buffer, what the backends send

`get_pixel/3` reads from whichever is populated, so the representation is not something
a caller has to track. `finalize/1` caches the dense buffer on a raster that is about to
be sent repeatedly; drawing into a finalized raster discards the cache.

Coordinates are zero-based with `{0, 0}` at the top left. Writes outside the bounds are
dropped rather than raising.

    FrenchCurve.Raster.new(4, 2, background: {0, 0, 0, 255})
    |> FrenchCurve.Raster.put_pixel(1, 0, {255, 0, 0, 255})
    |> FrenchCurve.Raster.to_rgba_binary()

# `channel`

```elixir
@type channel() :: 0..255
```

# `color`

```elixir
@type color() :: {channel(), channel(), channel(), channel()}
```

# `color_rgb`

```elixir
@type color_rgb() :: {channel(), channel(), channel()}
```

# `point`

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

# `t`

```elixir
@type t() :: %FrenchCurve.Raster{
  background: color(),
  height: pos_integer(),
  pixels: %{optional(point()) =&gt; color()},
  rgba: binary() | nil,
  width: pos_integer()
}
```

# `crop`

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

Returns the `{width, height}` rectangle of `raster` whose top left is at `{x, y}`.

The rectangle must lie inside the raster. The result carries the same background and
a dense buffer.

# `dimensions`

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

Returns the raster's `{width, height}` in pixels.

# `finalize`

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

Returns `raster` with its dense RGBA buffer computed and cached in the `rgba` field.

Pixel values are unchanged. Call this on a raster that will be sent to a backend more
than once; a raster that already carries a cache is returned as is. `put_pixel/4` after
`finalize/1` drops the cache, so finalize last.

# `from_rgba`

```elixir
@spec from_rgba(pos_integer(), pos_integer(), binary(), keyword()) :: t()
```

Builds a raster from an existing dense RGBA buffer.

`binary` must be exactly `width * height * 4` bytes, four bytes per pixel in red,
green, blue, alpha order and rows top to bottom; a mismatched size raises
`FunctionClauseError`. The buffer is kept as the raster's cache rather than expanded,
so this is the cheap way to load pixels produced elsewhere.

## Options

  * `:background` — the `t:color/0` returned for out-of-bounds reads, default
    `{0, 0, 0, 0}`

# `get_pixel`

```elixir
@spec get_pixel(t(), integer(), integer()) :: color()
```

Returns the `t:color/0` at `{x, y}`.

Reads a written pixel first, then the cached dense buffer, and falls back to the
raster's `background`. Out-of-bounds coordinates return the background rather than
raising.

# `in_bounds?`

```elixir
@spec in_bounds?(t(), integer(), integer()) :: boolean()
```

Returns whether `{x, y}` falls inside the raster.

True when `x` and `y` are both non-negative and below `width` and `height` respectively.

# `new`

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

Builds an empty raster `width` by `height` pixels.

Both dimensions must be positive integers; anything else raises `FunctionClauseError`.

## Options

  * `:background` — the `t:color/0` `get_pixel/3` returns for unwritten pixels, default
    `{0, 0, 0, 0}` (transparent black)

# `put_pixel`

```elixir
@spec put_pixel(t(), integer(), integer(), color()) :: t()
```

Returns `raster` with the pixel at `{x, y}` set to `color`.

`color` is an `{r, g, b, a}` tuple of bytes and replaces whatever was there; there is no
alpha blending. Coordinates outside the bounds are ignored and the raster is returned
unchanged. A raster holding a cached dense buffer loses that cache here.

# `rows`

```elixir
@spec rows(t()) :: [[color()]]
```

Returns every pixel as `height` lists of `width` colours, top row first.

Each element is a `t:color/0`; unwritten positions come back as the background.

# `to_rgba_binary`

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

Returns the raster as a dense row-major RGBA buffer.

The result is exactly `width * height * 4` bytes, four bytes per pixel in red, green,
blue, alpha order, rows top to bottom. Returns the cached buffer when one is present.

---

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