File Formats
The Block’Ed project, the Block’Ed PNG byte for byte, the map exports, and the palette files Block’Ed reads and writes. The screen, tile and sprite exports are described on Exporting.
Every multi-byte integer is big-endian unless stated otherwise. A u32 is a 4-byte unsigned integer and a u8 a single byte.
Block’Ed project
A .blked file is a zip archive (PKWARE APPNOTE) holding a manifest, project.json, and one Block’Ed PNG per part. Block’Ed compresses each entry with deflate.
Platformer.blked
├── project.json
├── screens/Title.png
├── sprites/Player.png
└── tiles/Level.png
project.json
project.json is a UTF-8 JSON object:
| Key | Holds |
|---|---|
format | 1; a higher format is refused |
parts | the parts in order, each {"name": name, "file": path}, with properties and metatiles where the part has them |
maps | optional: the maps in order, each {"name": name, "file": path} |
A part’s properties is a list of tile properties, each {"name", "kind", "choices", "values"}: kind is flag, choice or number, choices the named values of a choice, and values each tile’s value by tile number, with tiles past the end holding 0. A part’s metatiles is a list of {"name", "width", "height", "entries"}, the entries row by row as described under Map files.
{
"format" : 1,
"parts" : [
{ "file" : "screens/Title.png", "name" : "Title" },
{ "file" : "sprites/Player.png", "name" : "Player" },
{ "file" : "tiles/Level.png", "name" : "Level" }
]
}
A part’s name is the name the sidebar shows. Names are unique within a project, ignoring case.
A part’s file is the archive path of its Block’Ed PNG. Block’Ed writes it as the part’s kind folder, screens, sprites or tiles, then the part’s name with each /, : and \ replaced by -, then .png. Where that path is already taken, ignoring case, a number from 2 is added before .png: parts named a/b and a:b are written as screens/a-b.png and screens/a-b 2.png. A reader takes the path from file and does not derive it.
The part’s kind follows from its mode, read from its PNG; the folder a part is stored in is not read.
Map files
A map is stored as UTF-8 JSON at maps/<name>.json, its path made as a part’s is. The map’s name is taken from the manifest.
| Key | Holds |
|---|---|
width, height | the size in cells |
tilePart, spritePart | optional: the names of the parts the map draws from |
target | optional: tilemap or layer2; absent, the target follows the tile part |
tiles512 | optional: true for 512-tile mode |
tilemapColumns | optional: 80 for the 80-column tilemap |
layers | the layers, back to front |
Each layer holds name, kind (tiles, values, objects or sprites), visible and locked, and one of:
cells, for a Tiles layer: an entry ornullper cell, row by rowvalues, for a Values layer: a painted value ornullper cell, withpropertynaming the tile property andsourcenaming the Tiles layer it reads, where setitems, for an Objects or Sprites layer: each{"name", "type", "graphic", "x", "y", "properties"}, withxandyin pixels andpropertiesan object of text values
A tile entry is one integer: the tile number in bits 0 to 15, the palette offset in bits 16 to 19, X mirror in bit 20, Y mirror in bit 21, rotate in bit 22 and below-ULA in bit 23. Tile 300 drawn in bank 2 mirrored in X is 300 + 2 × 65536 + 1048576, 1179948.
Reading rules
A file beginning with the PNG signature is read as a Block’Ed PNG and opens as a project of one part, named after the file.
Any other file must be a zip archive holding project.json. Block’Ed refuses a project:
- that is not a readable zip archive, or holds no readable
project.json - whose
formatis above 1 - whose manifest names a file the archive does not hold
- with no parts, or with two parts whose names differ only in case
Each part’s PNG is then read by the rules below.
Block’Ed PNG
A Block’Ed PNG is a part of a project, or a .png Block’Ed wrote. It is a PNG (W3C PNG specification) with these properties:
- colour type 3, indexed colour
- bit depth 8 where the mode has more than 16 colours, and 4 otherwise
- compression method 0, filter method 0, no interlacing
- a private chunk,
bkED, holding everything the image cannot
Any PNG reader shows the artwork. Block’Ed writes its chunks in this order: IHDR, PLTE, sBIT, pHYs, tRNS, bkED, IDAT, IEND. sBIT, pHYs and tRNS appear only where described below.
Image
The image is a sheet. Each asset is one band of rows, in asset order from the top, and each band holds the asset’s frames side by side, from the left. The sheet is as wide as the widest band and as tall as all the bands together. Where a band is narrower than the sheet, the rest of its rows are index 0.
If a document has an asset of three 16 × 16 frames and an asset of one 32 × 32 frame, its sheet is 48 × 48: the first band 48 × 16, the second 32 × 32 followed by 16 columns of index 0.
Each pixel is a colour index:
| Mode | Pixel value |
|---|---|
| ULA, Hi-Colour | the colour of the pixel’s INK or PAPER, plus 8 where its cell is BRIGHT: 0 to 15 |
| ULA, Hi-Colour with ULA+ | the ULA+ register: FLASH × 32 + BRIGHT × 16 + PAPER × 8 + colour, where PAPER is 1 for a PAPER pixel: 0 to 63 |
| Hi-Res | 0 for paper, 1 for ink |
| LoRes 16 colours, 4-bit sprites, 4-bit tiles | bank × 16 + the stored value |
| every other mode | the palette index |
PLTE
PLTE holds the colour each index displays.
| Mode | Entries | Levels |
|---|---|---|
| ULA, Hi-Colour | 16 | 0, 205 and 255: a colour’s channels are 205, or 255 with BRIGHT |
| ULA, Hi-Colour with ULA+ | 64 | each register’s 3-bit channels as below |
| Hi-Res | 2 | paper, then ink, as Spectrum colours |
| every other mode | the palette’s size | each 3-bit channel v as ⌊v × 255 ÷ 7⌋ |
sBIT, pHYs and tRNS
sBIT is written in the modes with an editable palette, as three bytes of 3: three significant bits a channel.
pHYs is written in Layer 2 16 colours and Hi-Res, with unit 0. Pixels per unit are 2 across and 1 down for half-width pixels, and 1 and 1 for square pixels. On reading Layer 2 16 colours, unequal values mean half-width pixels.
tRNS is written where the document has transparency:
- In a sprite or tile mode, one alpha byte for every palette entry: 0 where the entry’s stored value is the transparent value, 255 otherwise.
- In a mode with a transparent index, alpha 255 for every entry below the index and 0 for the index itself.
bkED
The bkED chunk’s body is two bytes followed by a zlib stream.
| Offset | Size | Holds |
|---|---|---|
| 0 | 1 | format version: 2, 3 or 4 |
| 1 | 1 | mode code |
| 2 | rest | the payload, compressed with zlib |
| Mode | Code |
|---|---|
| ULA | 0 |
| Layer 2 256 colours | 1 |
| Layer 2 16 colours | 2 |
| Hi-Colour | 3 |
| Hi-Res | 4 |
| LoRes 256 colours | 5 |
| LoRes 16 colours | 6 |
| 4-bit sprites | 7 |
| 8-bit sprites | 8 |
| 4-bit tiles | 9 |
Block’Ed writes version 4 for a bank, a document with more than one asset or whose only asset is not named Asset 1. It writes version 3 for one asset of more than one frame, and version 2 for a single image. A file whose version is above 4 is refused.
Payload
The payload is four sections in order: layout, mode data, palette and painting options.
Layout. In version 4:
| Size | Holds |
|---|---|
| u32 | asset count, at least 1 |
then for each asset:
| Size | Holds |
|---|---|
| u32 | name length in bytes |
| that length | name, UTF-8, not empty |
| u32 | width |
| u32 | height |
| u32 | frame count, at least 1 |
| u32 | frame delay numerator, seconds |
| u32 | frame delay denominator |
| u8 | loop mode: 0 Once, 1 Loop, 2 Ping-pong |
In version 3, one asset fills the sheet:
| Size | Holds |
|---|---|
| u32 | frame count, at least 2; the sheet’s width divided by it is the frame width |
| u32 | frame delay numerator |
| u32 | frame delay denominator |
| u8 | loop mode |
In version 2 there is no layout section: the sheet is one asset of one frame, named Asset 1.
A frame delay is the numerator divided by the denominator, in seconds; both must be 1 to 4294967295. A delay of 1/10 s is stored as 1 and 10, and 40 ms as 40 and 1000.
Mode data. By mode:
| Mode | Holds |
|---|---|
| Hi-Res | u8: the ink, 0 to 7 |
| LoRes 16 colours | u8: the bank, 0 to 15 |
| 8-bit sprites | u8: the transparent value |
| 4-bit sprites, 4-bit tiles | u8: the transparent value, 0 to 15; then for every frame, one byte per pattern: its bank, 0 to 15 |
| ULA, Hi-Colour | for every frame: its bitmap, then its attributes |
| every other mode | nothing |
Frames are taken asset by asset, and each asset’s frames in order. Patterns and cells are taken left to right, then top to bottom.
A ULA frame of exactly 256 × 192 stores its bitmap as 6144 bytes in the order the ULA reads screen memory, followed by its 768 attribute bytes: the 6912-byte .scr layout. Every other attribute frame stores its bitmap as rows from the top, eight pixels to a byte with the leftmost pixel in bit 7, followed by one attribute byte per cell. An attribute byte holds INK in bits 0 to 2, PAPER in bits 3 to 5, BRIGHT in bit 6 and FLASH in bit 7.
Older tile files: a 4-bit tiles payload may follow its banks with a tile map whose first byte is 40 or 80. Block’Ed skips 1 + that byte × 64 bytes and keeps no map.
Palette.
| Size | Holds |
|---|---|
| u32 | length |
| that length | a .bpal file |
The modes with an editable palette store their palette here. ULA and Hi-Colour with ULA+ store their 64 registers, with target ulaplus-g3r3b2. Otherwise the length is 0.
Painting options. The rest of the payload is the document’s painting options as UTF-8 JSON: painting mode, colour ramps, perspective, drawing helpers, and whether the frame bar and asset list are showing. A reader may ignore it. If it is absent or unreadable, Block’Ed opens the document with its default painting options.
Reading rules
Block’Ed refuses a file whose image disagrees with its bkED data:
- In ULA and Hi-Colour, every pixel must equal the index the bitmap and attributes give.
- In the banked modes, every pixel’s upper four bits must equal its pattern’s bank.
- The padding beside a narrow band must be index 0.
- The bands’ sizes must add up to the sheet’s, and each asset’s size must be a whole number of the mode’s size steps.
| Error | Means |
|---|---|
| The file is not a PNG. | the signature is missing |
| The file’s type chunk is damaged. | a CRC fails, or a chunk cannot be read |
| The file has no type chunk. | IHDR, IDAT or IEND is missing |
| The image is not a Block’Ed indexed-colour image. | not colour type 3 at depth 4 or 8 |
| Interlaced PNG files are not supported. | interlace method 1 |
| The file was not saved by Block’Ed. | no bkED chunk |
| The file was saved by a newer Block’Ed (format N). | version above 4 |
| The file’s Block’Ed data does not match its image size. | the layout does not fit the sheet, or the depth is wrong for the mode |
| The file’s image was changed outside Block’Ed. | a pixel disagrees with the bkED data |
A file of version 1 or 0 has no palette section; its palette is read from PLTE.
Worked example
This complete 206-byte file is an 8 × 8 ULA document, one cell of red INK on white PAPER drawing a diamond. Its zlib streams use stored blocks, so the payload and pixels read directly.
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
00 00 00 08 00 00 00 08 04 03 00 00 00 36 21 A3
B8 00 00 00 30 50 4C 54 45 00 00 00 00 00 CD CD
00 00 CD 00 CD 00 CD 00 00 CD CD CD CD 00 CD CD
CD 00 00 00 00 00 FF FF 00 00 FF 00 FF 00 FF 00
00 FF FF FF FF 00 FF FF FF 75 7F C0 AB 00 00 00
1A 62 6B 45 44 02 00 78 01 01 0D 00 F2 FF 18 3C
7E FF FF 7E 3C 18 3A 00 00 00 00 23 B2 03 DD 49
50 E0 12 00 00 00 33 49 44 41 54 78 01 01 28 00
D7 FF 00 77 72 27 77 00 77 22 22 77 00 72 22 22
27 00 22 22 22 22 00 22 22 22 22 00 72 22 22 27
00 77 22 22 77 00 77 72 27 77 A6 04 08 3D CF A6
B5 5F 00 00 00 00 49 45 4E 44 AE 42 60 82
IHDR: 8 × 8, depth 4, colour type 3.PLTE: the 16 Spectrum colours.bkED: version 2, mode 0. The 13-byte payload is the bitmap18 3C 7E FF FF 7E 3C 18, the attribute3A(INK 2, PAPER 7), and a palette length of 0. There are no painting options.IDAT: eight rows, each a filter byte of 0 and four bytes of two pixels: 7 for PAPER, 2 for INK.
A test in Block’Ed’s source decodes these bytes and checks the document they describe.
Map exports
Tilemap
One entry per cell, row by row, two bytes each:
| Byte | Bits | Holds |
|---|---|---|
| 0 | 7–0 | tile number, bits 7–0 |
| 1 | 7–4 | palette offset |
| 1 | 3 | X mirror |
| 1 | 2 | Y mirror |
| 1 | 1 | rotate |
| 1 | 0 | tile number bit 8 in 512-tile mode, below-ULA otherwise |
An empty cell is written as tile 0. Tilemap without Attributes writes byte 0 alone.
If tile 300 is drawn in bank 2 mirrored in X in 512-tile mode, its entry is 2C 29.
Value layer
One byte per cell, row by row: each cell’s value as it resolves, clamped to 0 to 255. If a 3 × 2 layer’s top row follows tiles whose property is None and its bottom row is brick, Solid, it is written 00 00 00 01 01 01.
Tile properties
One byte per tile of the tile part, in tile order. The properties are packed into it in order from bit 0, each taking the bits its largest value needs: one for on or off, enough for the last choice, and eight for a number. Properties needing more than eight bits together are refused.
With Collision a choice of five values, in bits 0–2, and Object on or off, in bit 3, a ladder tile, Collision 4, is 04, and a solid object, Collision 1 and Object on, is 09.
Tiled map
A folder holding <map>.tmj, a Tiled JSON map (Tiled JSON map format), and a PNG for each tileset it uses: the tile part, the sprite part where a Sprites layer places items, and Values.png where a Values layer holds values.
- Tiles layers are tile layers. A tile’s orientation is written as Tiled’s flip flags, diagonal first.
- Values layers are tile layers of class
valuesdrawn withValues.png, one coloured tile per value, with apropertystring property naming the tile property. - Objects and Sprites layers are object groups of tile objects, each item’s properties as
stringproperties. - Tile properties are
intproperties of the tile part’s tiles.
LDtk project
A folder holding <map>.ldtk, an LDtk 1.5.3 project of one level (LDtk JSON), and a PNG for each tileset it uses.
- Tiles layers are Tiles layers; Values layers are IntGrid layers, each value named after the property’s choice; Objects and Sprites layers are Entities layers.
- Each item type is an entity, with a String field for the item’s name and one for each property.
- Tile properties are the tileset’s custom data, a JSON object of each tile’s non-zero values.
LDtk mirrors tiles but does not rotate them; a map with a rotated tile is refused.
Block’Ed map JSON
A UTF-8 JSON object with format "blocked-map" and version 1:
| Key | Holds |
|---|---|
name, width, height | the map’s name and size in cells |
tileWidth, tileHeight | the tile part’s pattern size in pixels |
target, tiles512 | the target in effect, and whether 512-tile mode is on |
tilePart, spritePart | the parts’ names |
layers | the layers, back to front |
tileProperties | each {"name", "kind", "choices", "values"}, with a value for every tile of the tile part |
metatiles | each {"name", "width", "height", "tiles"} |
Each layer holds name, kind, visible and locked, and one of tiles, a tile entry or null per cell; values, each cell’s value as it resolves, with property; or items, as in a map file. A tile entry is an object: {"tile", "paletteOffset", "mirrorX", "mirrorY", "rotate", "belowULA"}.
Palette files
Block’Ed palette (.bpal)
A .bpal file is a UTF-8 JSON object:
| Key | Holds |
|---|---|
format | "blocked-palette" |
version | 1; a higher version is refused |
name | the palette’s name |
bits | {"red": r, "green": g, "blue": b}: bits per channel, each 1 to 8 |
colours | 1 to 256 entries, each [red, green, blue] within its channel’s bits |
flags | optional: flag names, each listing the entries carrying it; priority marks Layer 2 priority |
target | optional: the register encoding the colours were made for, next-rrrgggbb or ulaplus-g3r3b2 |
{"format": "blocked-palette", "version": 1, "name": "Fire",
"bits": {"red": 3, "green": 3, "blue": 3},
"colours": [[0, 0, 0], [7, 2, 0], [7, 7, 3]],
"flags": {"priority": [2]}}
Next palette (.nxp)
32 bytes for 16 colours, or 512 bytes for 256. Each entry is two bytes:
| Byte | Bits |
|---|---|
| first | red in bits 7 to 5, green in bits 4 to 2, the blue’s upper two bits in bits 1 and 0 |
| second | the blue’s lowest bit in bit 0, priority in bit 7; bits 1 to 6 must be 0 |
Writing needs 16 or 256 colours, fitted to 3 bits a channel.
Photoshop colour table (.act)
768 bytes of red, green and blue for 256 colours, or 772 bytes: the 768, then a 2-byte colour count and two more bytes. Block’Ed writes 772 bytes with the count and FF FF, filling unused entries with 0.
GIMP palette (.gpl)
Text. The first line is GIMP Palette. A Name: line names the palette, and Columns:, comment and blank lines are skipped. Every other line starts with red, green and blue, 0 to 255.
JASC palette (.pal)
Text: JASC-PAL, 0100, the colour count, then one line of red, green and blue, 0 to 255, for each colour. Block’Ed writes it with CRLF line endings.
ULA+ registers (.upl)
64 bytes, one per register, with green in bits 7 to 5, red in bits 4 to 2 and blue in bits 1 and 0. On reading, two-bit blue b becomes three-bit b << 1 | (b >> 1 | b & 1): 0, 3, 5 and 7. Writing needs exactly 64 colours.
← Reference · Next: Hardware Limits »