<Fresco.canvas> — a layered scene of N images positioned at absolute
coordinates on a virtual canvas, plus an open extensions map for
annotation tools (future Etcher), ML overlays, and other peer packages.
Single-image is just the N=1 case — use Fresco.Viewer when you want the
bare "pan/zoom one image" component without the scene-document overhead.
The .fresco file format
Serializing a canvas yields a JSON document keyed by:
{
"version": "1",
"canvas": { "width": 4000, "height": 3000, "background": null },
"images": [
{
"id": "img-1",
"src": "/uploads/a.jpg",
"x": 0, "y": 0,
"width": 2000,
"z_index": 0,
"natural_width": 2000,
"natural_height": 1500
}
],
"extensions": {
"etcher": { "version": "1", "annotations": [...] },
"ml-overlay": { ... }
}
}canvas.width/canvas.height— the virtual canvas extent in canvas pixels. Reset-view fits this rectangle to the viewport.- Images are positioned at absolute canvas-pixel
(x, y)withwidthin canvas pixels. Height is derived from natural aspect ratio ifnatural_widthandnatural_heightare present (and saved into the file for forward compatibility). extensionsis an open map keyed by package name. Fresco never inspects the inside — each extension owns its own shape and version. Unknown extension keys are preserved verbatim across read/write so you can load → edit → save without losing data the current version doesn't understand.- Read-time forward-compatibility: any unknown top-level or
per-image key is preserved through a private
__extra__map and re-merged on write. A v1 reader of a future v2 file keeps the v2 fields it doesn't understand and writes them back unchanged.
Building a canvas
iex> canvas =
...> Fresco.Canvas.new(width: 4000, height: 3000)
...> |> Fresco.Canvas.add_image(%{src: "/a.jpg", x: 0, y: 0, width: 2000})
...> |> Fresco.Canvas.add_image(%{src: "/b.jpg", x: 2100, y: 0, width: 1800})
...> |> Fresco.Canvas.put_extension("etcher", %{"version" => "1", "annotations" => []})
iex> Enum.map(canvas.images, & &1.id)
["img-1", "img-2"]File I/O
Fresco.Canvas.write!("/tmp/scene.fresco", canvas)
canvas = Fresco.Canvas.read!("/tmp/scene.fresco")Writes are atomic: write/2 writes to <path>.tmp then renames, so an
interrupted save can't corrupt an existing file.
Extension contract — passive Fresco
Fresco is passive with respect to extensions. The file is the source
of truth; updates flow consumer LiveView → %Fresco.Canvas{} in
assigns → re-render. A peer package like the future Etcher reads its
initial state via handle.getExtension("etcher") at mount, pushes
edits to its own LiveView, which calls
Fresco.Canvas.put_extension(canvas, "etcher", new_data) and
re-assigns. Fresco's handle is intentionally read-only for extensions
— no setExtension method exists, so save timing is never racing
with annotation updates over channels.
See Fresco.Viewer for the simpler single-image component, and
Fresco.ScrollStrip for the long-scroll reader counterpart.
Summary
Functions
Append an image to the canvas.
Renders a Fresco canvas — N images positioned at absolute coordinates
on a virtual canvas, with pan/zoom/fit/fullscreen identical to
Fresco.viewer. Hooks the FrescoCanvas JS controller.
Parse a JSON string into a canvas struct. Returns {:ok, canvas} or
{:error, %Fresco.Canvas.SchemaError{}}.
Parse a JSON string into a canvas struct. Raises on invalid input.
Build a new empty canvas.
Put or overwrite an extension blob keyed by name (binary or atom).
Read a canvas from disk. Returns {:ok, canvas} or {:error, reason}.
Read a canvas from disk. Raises on failure.
Serialize a canvas to a JSON string. Returns {:ok, json} or
{:error, reason} (only on Jason encode failure — schema is validated
at struct-construction time).
Serialize a canvas to a JSON string. Raises on encode failure.
Write a canvas to disk atomically. Writes to <path>.tmp first then
renames so an interrupted save can't corrupt the existing file.
Write a canvas to disk atomically. Raises on failure.
Types
Functions
Append an image to the canvas.
Required attrs: :src (string URL/path), :x, :y, :width (numbers,
width > 0). Optional: :id (auto-assigned img-N when omitted),
:z_index, :natural_width, :natural_height.
Raises ArgumentError on invalid attrs.
Renders a Fresco canvas — N images positioned at absolute coordinates
on a virtual canvas, with pan/zoom/fit/fullscreen identical to
Fresco.viewer. Hooks the FrescoCanvas JS controller.
Attributes
id(:string) (required) - DOM id; must be unique on the page.canvas(Fresco.Canvas) (required) - A%Fresco.Canvas{}struct describing the scene: virtual canvas extent, the list of images with their canvas-pixel positions, and an openextensionsmap. Build one viaFresco.Canvas.new/1+Fresco.Canvas.add_image/2, or load one from a.frescofile viaFresco.Canvas.read!/1.class(:string) - CSS classes for the canvas host container. Defaults to"w-full h-96".infinite_canvas(:boolean) - Whentrue, drops the default "canvas must cover viewport" clamp so the user can pan freely beyond the canvas edges and zoom out until the whole layout is a thumbnail in the middle of an empty workspace.Defaults to
false.theme(:atom) - Color scheme. Same semantics asFresco.viewer's:theme. Defaults to:system. Must be one of:system,:light,:dark, or:inherit.Global attributes are accepted.
Parse a JSON string into a canvas struct. Returns {:ok, canvas} or
{:error, %Fresco.Canvas.SchemaError{}}.
Unknown top-level and per-image keys are preserved via a private
__extra__ map; round-tripping through to_json/from_json keeps them
intact so v1 readers of a future v2 file don't lose v2-only data.
Parse a JSON string into a canvas struct. Raises on invalid input.
Build a new empty canvas.
Options:
:width— virtual canvas width in canvas pixels (default0):height— virtual canvas height (default0):background— optional CSS color string for the stage background
Put or overwrite an extension blob keyed by name (binary or atom).
Fresco never inspects the contents — each peer package owns the inner shape and self-versions inside its own blob.
Read a canvas from disk. Returns {:ok, canvas} or {:error, reason}.
Read a canvas from disk. Raises on failure.
Serialize a canvas to a JSON string. Returns {:ok, json} or
{:error, reason} (only on Jason encode failure — schema is validated
at struct-construction time).
Serialize a canvas to a JSON string. Raises on encode failure.
Write a canvas to disk atomically. Writes to <path>.tmp first then
renames so an interrupted save can't corrupt the existing file.
Returns {:ok, path} or {:error, reason}.
Write a canvas to disk atomically. Raises on failure.