Cauldron2D.Easel (Cauldron2D.Easel v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

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

The colour under key, or nil.

Drop the frame in hand; the last frame stays.

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.

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

Make one pixel of the frame in hand transparent.

Flood the area of one key around point with key.

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

A document from .pic text.

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

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

The palette's keys, in order.

Paint a line from from to to.

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

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

A transparent picture width by height with one frame.

Paint one pixel of the frame in hand.

Frame index as a raster.

Every frame as a raster.

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

Forward one undone change; nothing when there is none.

Drop key from the palette; its pixels go transparent.

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

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

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

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

Change the colour under key.

Set the frames a second.

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

The picture's {width, height}.

The frames side by side as one raster.

The document as .pic text.

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

Back one change; nothing when there is none.

Types

frame()

@type frame() :: tuple()

key()

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

point()

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

rgba()

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

t()

@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()
}

Functions

add_colour(easel, key, colour)

@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(easel)

@spec add_frame(t()) :: t()

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

colour(easel, key)

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

The colour under key, or nil.

delete_frame(easel)

@spec delete_frame(t()) :: t()

Drop the frame in hand; the last frame stays.

derive(easel, line, lookup \\ fn _name -> :error end)

@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(easel)

@spec duplicate_frame(t()) :: t()

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

erase(easel, point)

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

Make one pixel of the frame in hand transparent.

fill(easel, point, key)

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

Flood the area of one key around point with key.

flip(easel, atom)

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

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

from_pic(text)

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

A document from .pic text.

from_png(bytes, opts \\ [])

@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(easel, arg, index)

@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(easel)

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

The palette's keys, in order.

line(easel, from, to, key)

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

Paint a line from from to to.

load(path, opts \\ [])

@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(easel, from, to)

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

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

new(width, height, opts \\ [])

@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(easel, point, key)

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

Paint one pixel of the frame in hand.

raster(easel, index)

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

Frame index as a raster.

rasters(easel)

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

Every frame as a raster.

rect(easel, arg1, arg2, key, opts \\ [])

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

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

redo(easel)

@spec redo(t()) :: t()

Forward one undone change; nothing when there is none.

remove_colour(easel, key)

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

Drop key from the palette; its pixels go transparent.

rename_key(easel, from, to)

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

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

rotate(easel, degrees)

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

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

save(easel, path)

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

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

select_frame(easel, index)

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

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

set_colour(easel, key, colour)

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

Change the colour under key.

set_fps(easel, fps)

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

Set the frames a second.

shift(easel, dx, dy)

@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(easel)

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

The picture's {width, height}.

strip(easel)

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

The frames side by side as one raster.

to_pic(easel)

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

The document as .pic text.

to_png(easel)

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

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

undo(easel)

@spec undo(t()) :: t()

Back one change; nothing when there is none.