joeylib2/docs/ApiTile.adoc
2026-09-26 21:02:41 -05:00

328 lines
10 KiB
Text

== Tiles and Text
Declared in `<joey/tile.h>`.
A tile is not a separate object. It is the 8x8 region of a surface at block
coordinates `(bx, by)`, where `bx` runs 0..39 and `by` runs 0..24 -- 40x25
blocks across a 320x200 surface. The tile API moves 32-byte blocks between
surfaces or fills them.
The consequence is that everything else works on tiles too. You can paint tile
content with `jlDrawPixel` and `jlFillRect`, generate it procedurally, or edit
it at run time, because a "tileset" is just a surface you agree to treat as
one. Fonts, terrain sets and spritesheets are all the same thing.
=== Constants and Types
[cols="3,1,4", options="header"]
|===
|Name |Value |Meaning
|`TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|`TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp).
|`TILE_BYTES` |32 |Bytes in one tile.
|`TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|`TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|`TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|`TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone".
|===
[source,c]
----
typedef struct jlTileT {
uint8_t pixels[TILE_BYTES];
} jlTileT;
----
A stack-allocatable 32-byte tile snapshot, so save-under style work does not
need a whole scratch surface.
[IMPORTANT]
====
A `jlTileT` is *target-native*: plane-major on the planar ports, chunky on the
others. Treat it as opaque. In particular, do not feed a tile captured by
`jlTileSnap` to `jlTilePasteMono`, which wants a chunky silhouette -- that is a
format mismatch and draws the wrong shape. Use `jlTilePasteGlyph` for 1-bit
glyph data instead.
====
=== jlTileCopy
[source,c]
----
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
----
Copies one 8x8 block between surfaces, opaquely.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates.
|`src` |`const jlSurfaceT *` |Source surface. May be the same as `dst`.
|`srcBx`, `srcBy` |`uint8_t` |Source block coordinates.
|===
*Returns:* nothing. Out-of-range block coordinates on either side are silent
no-ops.
=== jlTileCopyMasked
[source,c]
----
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy,
uint8_t transparentIndex);
----
As `jlTileCopy`, but source pixels equal to `transparentIndex` are skipped and
the destination keeps its original value. This is the call for fonts and for
any tileset with see-through pixels.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates.
|`src` |`const jlSurfaceT *` |Source surface.
|`srcBx`, `srcBy` |`uint8_t` |Source block coordinates.
|`transparentIndex` |`uint8_t` |Source colour index treated as transparent.
|===
*Returns:* nothing.
=== jlTileFill
[source,c]
----
void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
----
Fills one block with a solid colour. Equivalent to
`jlFillRect(s, bx*8, by*8, 8, 8, colorIndex)` but skips the clipping maths,
since block coordinates are already known to be in range.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`s` |`jlSurfaceT *` |Target surface.
|`bx`, `by` |`uint8_t` |Block coordinates.
|`colorIndex` |`uint8_t` |Colour index 0..15.
|===
*Returns:* nothing.
=== jlTileBankLoad
[source,c]
----
uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles,
bool *outValid, uint16_t *outPalette);
----
Loads tiles from a baked `.tbk` file. The file's per-target byte is checked
against the build platform and mismatches are refused.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`path` |`const char *` |File path.
|`outTiles` |`jlTileT *` |Receives the tiles. Caller-owned.
|`maxTiles` |`uint16_t` |Capacity of `outTiles`.
|`outValid` |`bool *` |Optional, may be `NULL`. Set `true` per loaded slot, so
loaded tiles can be told from leftovers in a fixed-size array.
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded
16-entry `$0RGB` palette if the file has one.
|===
*Returns:* the number of tiles written. Returns 0 if the file is missing, the
magic or target byte is wrong, or `maxTiles` is 0.
=== jlTileBankLoadToSurface
[source,c]
----
uint16_t jlTileBankLoadToSurface(const char *path, jlSurfaceT *dst,
uint16_t *outPalette);
----
Streams a baked `.tbk` straight onto a surface's block grid: tile `i` lands at
block `(i % 40, i / 40)`, the row-major layout the bake tools author against.
Use this instead of `jlTileBankLoad` for anything large. A 1000-tile font sheet
is 32000 bytes, which no IIgs program can hold in an array or in bank-0 BSS;
this needs only a small internal scratch buffer and one pass over the file.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`path` |`const char *` |File path.
|`dst` |`jlSurfaceT *` |Destination surface.
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. As `jlTileBankLoad`.
|===
*Returns:* the number of tiles pasted. Blocks beyond the file's tile count keep
their previous content.
=== jlTileSnap
[source,c]
----
void jlTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, jlTileT *out);
----
Captures one block into a caller-supplied `jlTileT`.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`src` |`const jlSurfaceT *` |Source surface.
|`bx`, `by` |`uint8_t` |Block coordinates.
|`out` |`jlTileT *` |Receives the 32-byte snapshot.
|===
*Returns:* nothing.
=== jlTilePaste
[source,c]
----
void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in);
----
Pastes a `jlTileT` back onto a surface, always opaquely. Use
`jlTileCopyMasked` if you need transparency.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Block coordinates.
|`in` |`const jlTileT *` |Tile to paste.
|===
*Returns:* nothing.
=== jlTilePasteMono
[source,c]
----
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
const jlTileT *in, uint8_t fgColor, uint8_t bgColor);
----
Pastes a tile as a silhouette: any non-zero source pixel draws `fgColor`, zero
pixels draw `bgColor`. Useful when the bitmap is a fixed glyph but the colour
comes from a separate map, as in C64-style colour RAM. Opaque.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Block coordinates.
|`in` |`const jlTileT *` |Source tile, read as a silhouette.
|`fgColor` |`uint8_t` |Colour for non-zero pixels. Masked to 4 bits.
|`bgColor` |`uint8_t` |Colour for zero pixels. Masked to 4 bits.
|===
*Returns:* nothing.
[WARNING]
====
This call wants a *chunky* silhouette, but a `jlTileT` from `jlTileSnap` is
target-native. Feeding one to this function on a planar port is a format
mismatch. For glyphs, use `jlTilePasteGlyph`, which takes unambiguous 1-bit
input.
====
=== jlTilePasteGlyph
[source,c]
----
void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by,
const uint8_t *bits, uint8_t fgColor, uint8_t bgColor);
----
Pastes an 8x8 one-bit-per-pixel glyph: `bits[row]` is that row, most
significant bit leftmost. A set bit draws `fgColor`, a clear bit `bgColor`.
Opaque.
This is the shape a character generator already has -- a font ROM, a C64
charset, anything 1bpp -- so nothing needs expanding into a `jlTileT` first.
*Prefer this over `jlTilePasteMono` for glyphs.*
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Block coordinates.
|`bits` |`const uint8_t *` |8 bytes, one per glyph row, MSB leftmost.
|`fgColor` |`uint8_t` |Set-bit colour. Masked to 4 bits.
|`bgColor` |`uint8_t` |Clear-bit colour. Masked to 4 bits.
|===
*Returns:* nothing.
=== jlDrawText
[source,c]
----
void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
const jlSurfaceT *fontSurface, const uint16_t *asciiMap,
const char *str);
----
Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph
colour 0 is transparent, so the background shows through. The cursor wraps at
the right edge and truncates at the bottom.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Starting block coordinates.
|`fontSurface` |`const jlSurfaceT *` |Surface holding the glyphs.
|`asciiMap` |`const uint16_t *` |256 entries mapping ASCII code to glyph
position: low byte is source block X, high byte is source block Y. An entry of
`TILE_NO_GLYPH` skips that character, advancing the cursor without drawing.
|`str` |`const char *` |NUL-terminated string.
|===
*Returns:* nothing.
=== jlTileMapPaste
[source,c]
----
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by,
uint8_t wTiles, uint8_t hTiles,
const jlTileT *tiles, const uint8_t *map);
----
Pastes a whole rectangle of tiles in one call, so validation, dispatch and the
dirty-rectangle union are paid once for the rectangle instead of once per tile.
For anything larger than a few tiles this is substantially faster than looping
over `jlTilePaste`.
[cols="1,1,4", options="header"]
|===
|Parameter |Type |Description
|`dst` |`jlSurfaceT *` |Destination surface.
|`bx`, `by` |`uint8_t` |Top-left block coordinates.
|`wTiles`, `hTiles` |`uint8_t` |Rectangle size in tiles. Zero is a no-op.
|`tiles` |`const jlTileT *` |The tileset.
|`map` |`const uint8_t *` |`wTiles * hTiles` tileset indices, row-major, so
`map[row * wTiles + col]`. `TILE_MAP_SKIP` leaves that cell untouched.
|===
*Returns:* nothing. A rectangle whose edge crosses the surface boundary is
rejected as a no-op.
[WARNING]
====
Any map index other than `TILE_MAP_SKIP` must be a valid tileset index. The
batch does not know the tileset length and does *not* range-check it -- an
out-of-range index reads past the end of your array.
====