Sprites Image Media Storage
An image lives in the data store as one record with just two fields: its id and a data object. None of the metadata, the name, media, platform, times, thumbnail or info summary, is here; that is the meta record, kept beside it under the same id. The data object holds the whole picture: its width, height, tile side and mode, and an array of layers, back to front, each layer a grid of tiles of packed RGBA pixels. An image has layers but no animation frames, so where a sprite is frames of layers, an image is layers only. Attribute machines, the Spectrum, the C64 hires and the Oric, store a different shape, a one bit mask over ink and paper cell grids; see Attribute images below.
{
"id": "7c1b93a5e2f04d6790a1c8b4d3e6f102",
"data": {
"media": "image",
"w": 16,
"h": 16,
"tile": 16,
"mode": "Mode 13h",
"layers": [
{
"tiles": [ [ /* 256 packed RGBA ints */ ] ],
"visible": true
}
]
}
}
id and the data object. The sections below walk the object down through its layers, tiles and pixels. A single 16×16 image is one tile per layer, so tiles holds one array of 256 packed colours.Contents
The record in the data store is just the id and the data object. Everything a listing shows, the name, size and mode summary, is the meta record, so this store carries only the picture.
| Field | Type | Description |
|---|---|---|
id | String | The item id, 32 hex characters. The key, shared with the item's meta record. |
data | Object | The picture: its size, tile side, mode and layers. Detailed in Data below. |
Data
The data object carries the size and mode the whole image is drawn at, the tile side the pixels are cut into, and its layers. Width, height and mode live here, on the picture itself; the meta record's info summary is a copy for listings.
| Field | Type | Description |
|---|---|---|
media | String | Always the string "image", naming the media this record holds. |
w | Number | The width in pixels, a whole number from 1 to 4096. Every layer covers this width. |
h | Number | The height in pixels, a whole number from 1 to 4096. Every layer covers this height. |
tile | Number | The tile side the pixels are cut into, a power of two, 16. The grid is ceil(w / tile) tiles across and ceil(h / tile) down. |
mode | String | The platform mode the image is drawn in, by name, for example "Mode 13h". Null, or omitted, for a freeform image that names no mode. The mode fixes the screen size and the palette the picker offers; the pixels are always full colour. |
layers | Array | The layers, back to front. One or more. Detailed in Layers below. |
Layers
layers is an array of layer objects, back to front, flattened bottom to top to make the picture. A flat image has a single layer. There are no frames: an image is layers only. Each layer holds the whole picture as a grid of tiles and a visible flag.
| Field | Type | Description |
|---|---|---|
tiles | Array | The layer's tiles, in row order over the tile grid, one Uint32Array per tile. Detailed in Tiles below. |
visible | Boolean | Whether the layer is currently shown. A hidden layer, false, stays in the data but is left out of the flattened view and the thumbnail. |
The editor adds, clones, reorders, hides and deletes layers. A transparent pixel on an upper layer, alpha zero, lets the layer below show through when the layers are flattened bottom to top.
Tiles and pixel encoding
Each layer cuts the picture into a grid of square tiles of side tile, so the pixels sit in a high-resolution tiled grid rather than one flat array. tiles holds those tiles in row order, ceil(w / tile) columns by ceil(h / tile) rows, and the tile at tile column tc, tile row tr is at index tr × ceil(w / tile) + tc. An edge tile holds a few unused cells past the pixel size, so a 20-wide image over a tile of 16 keeps two tiles across, the second half used.
Each tile is one Uint32Array of tile × tile packed RGBA colours, in row order within the tile. The pixel at tile-local column c, row r is at index r × tile + c.
| Value | Meaning |
|---|---|
| A packed colour | A 32 bit RGBA colour packed into one integer, red the high byte and alpha the low byte. Opaque white is 4294967295. The same packing Sprites.Colour uses. |
| Zero | A fully transparent pixel, alpha zero, the integer 0. This is the absent pixel, since a typed array cannot hold undefined. Transparent lets the layer below show through when layers are flattened bottom to top. |
The pixels are always full colour, whatever the platform. The mode fixes the screen size and the palette the picker offers, but each stored pixel is its own packed RGBA value, not a slot index.
Attribute images
The Spectrum, the C64 hires and the Oric store an attribute image, a different data shape. Where a direct image holds a packed RGBA colour for every pixel in a tiled Uint32Array grid, an attribute image holds a one bit ink or paper mask at full resolution over an ink and a paper colour grid at attribute cell resolution, each cell a flat palette index. The data object is { media, platform, mode, w, h, cellWidth, cellHeight, inline, pixels, ink, paper }.
| Field | Type | Description |
|---|---|---|
media | String | Always "image". |
platform | String | The machine the image is drawn for, for example "spectrum". |
mode | String | The attribute mode by name, for example "Standard". |
w | Number | The width in pixels. |
h | Number | The height in pixels. |
cellWidth | Number | The attribute cell width in pixels, 8 on the Spectrum and the C64, 6 on the Oric. |
cellHeight | Number | The attribute cell height in pixels, 8 on the Spectrum and the C64, 1 on the Oric. |
inline | Boolean | False for per cell attributes, where each cell holds its own colour; true for inline attributes, where a cell reads the nearest set colour to its left along its row. |
pixels | Array | The one bit mask, w × h values in row order, 1 for ink and 0 for paper. |
ink | Array | The ink colour grid at cell resolution, ceil(w / cellWidth) by ceil(h / cellHeight), each cell a palette index or undefined for the default. |
paper | Array | The paper colour grid at cell resolution, the same shape as ink. |
The mask reads full resolution, so a pixel is ink or paper wherever it falls; the ink and paper grids are coarse, one entry per attribute cell, so two colours meet inside a per cell block as colour clash. An undefined colour cell reads the mode's default ink or paper. This is the shape Sprites.Media.Image.Cells reads and writes.
Persistence
The record is structured-clone persisted in IndexedDB, the data store, keyed by id. The meta record, the name, size, mode summary and thumbnail, lives beside it under the same id in the meta store. Because the tiles are typed arrays, structured clone stores them directly, with no JSON stringify: a Uint32Array is written as a typed array, not turned into text. A JSON export is a separate path, and there a Uint32Array serialises as a plain array of numbers, which is why the examples on this page show tiles as arrays of integers. See Sprites.Media.Image for the editor that reads and writes this format.
Full example
A tiny 16×16 freeform image, one tile per layer, a single layer. The tile side is 16, so the whole picture is one tile of 256 packed RGBA colours in row order. Here the corners are opaque white, 4294967295, and the rest is transparent, 0.
{
"media": "image",
"w": 16,
"h": 16,
"tile": 16,
"mode": null,
"layers": [
{
"tiles": [
[ 4294967295, 0, /* ... 252 more ... */ 0, 4294967295 ]
],
"visible": true
}
]
}
data object of a freeform image. It sits in the data store beside the id. In store the one tile is a Uint32Array of 256 values; in a JSON export it serialises as this plain array of numbers.A platform image has the same shape, with its mode named in data.mode, for example "Mode 13h". The pixels stay full-colour packed RGBA; the mode only fixes the screen size and the palette the picker offers.
{
"media": "image",
"w": 320,
"h": 200,
"tile": 16,
"mode": "Mode 13h",
"layers": [
{
"tiles": [ /* 20 × 13 tiles of 256 packed RGBA ints */ ],
"visible": true
}
]
}
ceil(320 / 16) = 20 tiles across and ceil(200 / 16) = 13 down, so 260 tiles in row order, the bottom row's tiles partly unused.