an.stage.raster#

Raster art: what a PNG, JPEG or WebP is, read from its header (an#211).

Every piece of art the cutout renderer drew was SVG, and the size probe said so: svg_raster_size parses its input as XML, so a PNG plate died in the compiler with ParseError: not well-formed (invalid token): line 1, column 0 — the error a user sees for “I gave it a picture”. Art carved out of footage, a scanned drawing, a photographed paper cut-out: those are raster, and tracing them to SVG throws away the shading that made them worth carving.

PixiJS loads PNG, JPEG and WebP natively (its loadTextures parser, chosen by the file’s extension), so the renderer needed nothing new. What the COMPILER needed was three answers it previously got only from an SVG:

  • how big is it — image_size(), read from the header (no decode, no dependency: the four formats state their pixel size in the first few dozen bytes, and a header parse is the same cost as the SVG probe it sits beside);

  • what size does this art draw at, whatever it is — art_size(), the one probe the compiler, the fidelity check and an validate call;

  • what exactly is it — content_digest(), because a raster texture is addressed by its bytes: a re-carved part is a different texture (the runtime’s loader ignores a re-added alias on hot reload, an#155) and a different compiled contract, so the contract hash covers the pixels it will draw.

EXIF orientation (an#218). A JPEG whose EXIF says “rotate 90°” (a phone photo) is decoded upright by Chromium, so its box is the stored size transposed: image_size() reads the orientation tag and swaps width and height for the four orientations that turn the picture a quarter (5-8).

Why a raster part is never recoloured. A StylePack recolours SVG art by rewriting the literal colours its descriptor tags (colour_roles). A raster has no literals — its colours are pixels, and inferring a role from a pixel is exactly what caused an#99’s wrong-tone lid — so it renders as drawn, and the compiler says so once.

>>> is_raster("parts/head.png"), is_raster("parts/head.svg")
(True, False)

Module Attributes

RASTER_SUFFIXES

The raster formats a plate or a part may be, by file suffix.

Functions

art_size(source)

The size a piece of art rasterises at, whatever format it is.

content_digest(path)

The hex sha256 of a file's bytes, cached by (path, mtime, size).

has_alpha(source)

Whether the image can be transparent anywhere; None if unknown.

image_size(source)

(width, height) in pixels of a PNG, JPEG or WebP, from its header.

is_raster(src)

Whether src names raster art, by its suffix (case-insensitive).

strip_version(src)

The file path a (possibly versioned) texture src names.

versioned_src(src, digest)

src with its content digest as a query string.

Exceptions

RasterFormatError

A file named as raster art whose header this module cannot read.

an.stage.raster.RASTER_SUFFIXES: tuple[str, ...] = ('.png', '.jpg', '.jpeg', '.webp')#

The raster formats a plate or a part may be, by file suffix. PixiJS 7’s loadTextures picks its parser by extension and accepts exactly these (plus AVIF, left out because Chromium’s AVIF decode is the one of the four whose output is not specified bit-exactly and the render is a contract).

exception an.stage.raster.RasterFormatError[source]#

Bases: ValueError

A file named as raster art whose header this module cannot read.

A ValueError, so the compiler’s size probe treats it exactly like a malformed SVG: the art is declared (and fails loudly at load) rather than silently dropped.

an.stage.raster.art_size(source)[source]#

The size a piece of art rasterises at, whatever format it is.

SVG: its declared width/height (else its viewBox), as the browser does — svg_raster_size(). Raster: its pixel size. The ONE probe the compiler, the fidelity check and an validate share, so none of them can size a PNG as if it were XML again.

Return type:

tuple[float, float]

an.stage.raster.content_digest(path)[source]#

The hex sha256 of a file’s bytes, cached by (path, mtime, size).

The ENCODED bytes, not the pixels: re-saving the same image with another encoder changes the digest (and so the alias and the contract hash) — which is the conservative direction for a contract.

Cached because a rig registers every attachment of every slot and a scene compiles each shot separately; keyed on the stat so an edited file is re-read, which is what makes the digest safe to put in a texture alias.

Return type:

str

an.stage.raster.has_alpha(source)[source]#

Whether the image can be transparent anywhere; None if unknown.

A PNG says so in its header (colour type 4 or 6) or with a tRNS chunk; a JPEG never can; a WebP says so in its VP8X flags or by being lossless with an alpha bit. A cut-out part without alpha is a rectangle — the whole canvas draws, background and all — which is what an character validate uses this for.

Return type:

bool | None

an.stage.raster.image_size(source)[source]#

(width, height) in pixels of a PNG, JPEG or WebP, from its header.

source is a path or the file’s bytes.

Return type:

tuple[float, float]

>>> import struct, zlib
>>> ihdr = struct.pack(">IIBBBBB", 3, 2, 8, 6, 0, 0, 0)
>>> png = (b"\x89PNG\r\n\x1a\n" + struct.pack(">I", 13) + b"IHDR" + ihdr
...        + struct.pack(">I", zlib.crc32(b"IHDR" + ihdr)))
>>> image_size(png)
(3.0, 2.0)
an.stage.raster.is_raster(src)[source]#

Whether src names raster art, by its suffix (case-insensitive).

Return type:

bool

>>> is_raster("plates/Street.JPG")
True
>>> is_raster("data:image/svg+xml;base64,AAAA")
False
an.stage.raster.strip_version(src)[source]#

The file path a (possibly versioned) texture src names.

Return type:

str

>>> strip_version("props/lamp/parts/on.png?v=abc123")
'props/lamp/parts/on.png'
an.stage.raster.versioned_src(src, digest)[source]#

src with its content digest as a query string.

Return type:

str

>>> versioned_src("props/lamp/parts/on.png", "abc123")
'props/lamp/parts/on.png?v=abc123'