joeylib2/docs/ApiTile.adoc

335 lines
11 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
|`JL_TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|`JL_TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp).
|`JL_TILE_BYTES` |32 |Bytes in one tile.
|`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone".
|===
[source,c]
----
typedef struct jlTileT {
uint8_t pixels[JL_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 Amiga and X68000, row by row
with each row's four plane bytes together on the ST, and chunky on the others.
Treat it as opaque. In particular, do not feed a tile from `jlTileSnap` or
`jlTileBankLoad` to `jlTilePasteMono`, which wants a chunky silhouette -- on the
Amiga, ST, and X68000 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`. One flag per slot up to
`maxTiles`: `true` for each loaded tile, `false` for the rest (all of them when
the file is refused), 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, and tiles past the surface's 1000 blocks are ignored.
=== 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` or
`jlTileBankLoad` is target-native. Feeding one to this function on the Amiga,
ST, or X68000 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. A start off the 40x25 grid uses up
characters without drawing them as the cursor moves on: a start column past
39 drops the first character and carries on at the start of the next row, and
a start row past 24 draws nothing.
[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
`JL_TILE_NO_GLYPH`, or one off the font surface's 40x25 grid, 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]`. `JL_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 `JL_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.
====