PixelWeaver v0.5.0 /server.src.pixelweaver.storage
On this page

Project file I/O for PixelWeaver.

#server.src.pixelweaver.storage

#server.src.pixelweaver.storage

Project file I/O for PixelWeaver.

Saves/loads projects as directory structures: // project.json -- project metadata canvases// canvas.json -- canvas metadata (dimensions, layer tree) frames.json -- frame manifest (IDs, durations, playback state) frames// layer-.png -- raw pixel data as PNG per layer history.json -- command history for this canvas

#CanvasNotFoundError

Requested canvas does not exist (a client error, not an invariant violation) -- HTTP handlers map this to 404, while other ValueErrors from export paths (corrupt role planes, size mismatches) are server-side invariant violations and map to 500.

#_assert_within_base

python
def _assert_within_base(project_dir: Path, base_dir: Path) -> None

Raise ValueError if project_dir escapes base_dir (path traversal guard).

#save_project

python
def save_project(project: ProjectState, base_dir: str) -> None

Save a project to disk as a directory structure.

#_save_canvas

python
def _save_canvas(canvas: CanvasState, canvas_dir: Path, manifest: dict[str, Any] | None) -> None

Save a single canvas to its directory.

#_save_layer_png

python
def _save_layer_png(pixel_bytes: bytes, width: int, height: int, path: Path) -> None

Write raw RGBA bytes as a PNG file.

#hex_to_rgb

python
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]

Parse a #rrggbb hex color into an (r, g, b) tuple.

#role_class_ramp

python
def role_class_ramp(manifest: dict[str, Any] | None, layer: dict[str, Any]) -> list[tuple[int, int, int]]

Resolve a role layer's class ramp (list of RGB tuples) from the manifest.

Hard error when the manifest is absent or the class does not resolve -- creation-time validation makes this impossible, so hitting it means corrupted state.

#_save_role_plane_png

python
def _save_role_plane_png(role_bytes: bytes, width: int, height: int, path: Path, ramp: list[tuple[int, int, int]]) -> None

Write role bytes as an indexed PNG (PIL mode "P").

One byte per pixel; the layer's class ramp becomes the PNG palette (padded to 256 entries so the 0xFF sentinel index exists). Transparency for the sentinel uses a tRNS chunk (transparency=255), the standard-compliant palette-alpha mechanism.

Size mismatches and out-of-range role values are hard errors (matching the export path): silently dropping or persisting corrupt role planes is forbidden degradation. (The rgba path's warn-and-skip behavior is long-standing and intentionally untouched.)

#_load_role_plane_png

python
def _load_role_plane_png(path: Path) -> bytes

Read an indexed PNG back into raw role bytes (palette indices).

#load_project

python
def load_project(project_dir: str) -> ProjectState

Load a project from its directory on disk.

#_load_canvas

python
def _load_canvas(canvas_dir: Path) -> CanvasState

Load a single canvas from its directory.

Requires frames.json to exist. Old pre-UUID format (frames/0/) is not supported -- use the migration script (Step 11) to convert old data.

#_load_layer_png

python
def _load_layer_png(path: Path) -> bytes

Read a PNG file and return raw RGBA bytes.

#list_projects

python
def list_projects(base_dir: str) -> list[str]

List all project directories that contain a project.json.

#save_active_project_marker

python
def save_active_project_marker(base_dir: str, name: str) -> None

Persist the active project's name to a marker file in the data dir.

Best-effort: a failure to write the marker must never break navigation, so write errors are logged and swallowed. The marker is a tiny JSON file, not project content -- it records only the pointer.

#load_active_project_marker

python
def load_active_project_marker(base_dir: str) -> str | None

Read the active project's name from the marker file, or None if absent.

A missing or malformed marker is not an error -- it just means "no persisted choice"; callers fall back to their default.

#_resolve_role_rgba

python
def _resolve_role_rgba(role_bytes: bytes, width: int, height: int, manifest: dict[str, Any] | None, layer: dict[str, Any]) -> bytes

Resolve a role plane into raw RGBA bytes via the manifest LUT.

Mirrors the frontend compositor: role -> class(start+role) -> palette hex -> RGBA; sentinel 0xFF transparent; all other values fully opaque. Out-of-range role values are a hard error.

#_effective_visible_leaves

python
def _effective_visible_leaves(layers: list[dict[str, Any]]) -> list[dict[str, Any]]

Leaf layers that are visible AND have every ancestor group visible, bottom-to-top. A flat list yields its visible root leaves (unchanged behavior); nested groups gate their descendants' visibility.

#export_frame_png

python
def export_frame_png(project: ProjectState, canvas_name: str, frame: int=0) -> bytes

Composite all visible layers of a canvas frame into a single PNG.

Returns PNG bytes suitable for an HTTP response.

The frame parameter selects which frame to composite by index. canvas.frame_at() performs bounds checking and raises IndexError for out-of-range indices.

#make_thumbnail_base64

python
def make_thumbnail_base64(png_bytes: bytes, max_size: int=128) -> str

Return a base64-encoded PNG thumbnail of the given PNG bytes.

Used by MCP tool handlers to attach a preview of the active canvas to mutation results without sending the full composited image. The image is only downscaled when one of its dimensions exceeds max_size; smaller images pass through untouched. Nearest-neighbor resampling is used to preserve pixel-art edges.

This helper replaces the previously inlined Image.thumbnail calls in mcp_registry.py and supersedes the deleted thumbnails.py module, which worked from raw RGBA data rather than composited PNG bytes.

Search