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
      }
    ]
  }
}
The whole data record: the 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.

FieldTypeDescription
idStringThe item id, 32 hex characters. The key, shared with the item's meta record.
dataObjectThe 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.

FieldTypeDescription
mediaStringAlways the string "image", naming the media this record holds.
wNumberThe width in pixels, a whole number from 1 to 4096. Every layer covers this width.
hNumberThe height in pixels, a whole number from 1 to 4096. Every layer covers this height.
tileNumberThe 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.
modeStringThe 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.
layersArrayThe 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.

FieldTypeDescription
tilesArrayThe layer's tiles, in row order over the tile grid, one Uint32Array per tile. Detailed in Tiles below.
visibleBooleanWhether 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.

ValueMeaning
A packed colourA 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.
ZeroA 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 }.

FieldTypeDescription
mediaStringAlways "image".
platformStringThe machine the image is drawn for, for example "spectrum".
modeStringThe attribute mode by name, for example "Standard".
wNumberThe width in pixels.
hNumberThe height in pixels.
cellWidthNumberThe attribute cell width in pixels, 8 on the Spectrum and the C64, 6 on the Oric.
cellHeightNumberThe attribute cell height in pixels, 8 on the Spectrum and the C64, 1 on the Oric.
inlineBooleanFalse 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.
pixelsArrayThe one bit mask, w × h values in row order, 1 for ink and 0 for paper.
inkArrayThe ink colour grid at cell resolution, ceil(w / cellWidth) by ceil(h / cellHeight), each cell a palette index or undefined for the default.
paperArrayThe 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
    }
  ]
}
The 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
    }
  ]
}
A VGA Mode 13h image, 320×200. Over a tile of 16 the grid is 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.