198 lines
5.6 KiB
C
198 lines
5.6 KiB
C
// Host-side test for examples/agi/agiView.c.
|
|
//
|
|
// Loads a single VIEW resource, parses it, dumps the loop/cel
|
|
// structure, and writes cel 0 of every loop as a PPM image. The
|
|
// transparent color is rendered as bright magenta so the cel's
|
|
// silhouette stands out from the actual painted pixels.
|
|
//
|
|
// Usage:
|
|
// testAgiView <game-directory> [view-id]
|
|
//
|
|
// Exit codes:
|
|
// 0 parsed; outputs written
|
|
// 1 game directory missing, view slot empty, or parse failed
|
|
// 2 invalid command line
|
|
|
|
#include "agi.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
#define CHANNEL_SCALE 17u
|
|
|
|
// Pseudo-color used for "transparent" pixels in the output image.
|
|
#define TRANS_R 0xFFu
|
|
#define TRANS_G 0x00u
|
|
#define TRANS_B 0xFFu
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static int16_t firstNonEmptyView(const AgiGameT *game);
|
|
static bool writeCelPpm(const char *path, const AgiCelInfoT *cel);
|
|
|
|
|
|
// ----- Helpers (alphabetical) -----
|
|
|
|
static int16_t firstNonEmptyView(const AgiGameT *game) {
|
|
uint16_t count;
|
|
uint16_t i;
|
|
|
|
count = game->resCount[AGI_RES_VIEW];
|
|
for (i = 0u; i < count; i++) {
|
|
if (game->resDir[AGI_RES_VIEW][i].volume < AGI_MAX_VOLUMES) {
|
|
return (int16_t)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
// Walk the cel's RLE data, paint into a chunky 8bpp buffer, then
|
|
// emit a PPM with transparent pixels mapped to magenta. Same RLE
|
|
// rules as agiViewDraw: each byte = (color << 4) | runLen, runLen == 0
|
|
// ends the row.
|
|
static bool writeCelPpm(const char *path, const AgiCelInfoT *cel) {
|
|
FILE *fp;
|
|
uint8_t *buf;
|
|
uint16_t w;
|
|
uint16_t h;
|
|
uint16_t cx;
|
|
uint16_t cy;
|
|
uint16_t i;
|
|
const uint8_t *rle;
|
|
uint8_t rleByte;
|
|
uint8_t color;
|
|
uint8_t runLen;
|
|
uint8_t r;
|
|
uint8_t transparent;
|
|
uint16_t rgb;
|
|
|
|
w = (uint16_t)cel->width;
|
|
h = (uint16_t)cel->height;
|
|
if (w == 0u || h == 0u) {
|
|
return false;
|
|
}
|
|
|
|
buf = (uint8_t *)malloc((size_t)w * (size_t)h);
|
|
if (buf == NULL) {
|
|
return false;
|
|
}
|
|
transparent = cel->transparentColor;
|
|
memset(buf, transparent, (size_t)w * (size_t)h);
|
|
|
|
rle = cel->rleData;
|
|
for (cy = 0u; cy < h; cy++) {
|
|
cx = 0u;
|
|
for (;;) {
|
|
rleByte = *rle++;
|
|
if (rleByte == 0u) {
|
|
break;
|
|
}
|
|
color = (uint8_t)(rleByte >> 4);
|
|
runLen = (uint8_t)(rleByte & 0x0Fu);
|
|
for (r = 0u; r < runLen; r++) {
|
|
if (cx >= w) {
|
|
break;
|
|
}
|
|
buf[cy * w + cx] = color;
|
|
cx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
fp = fopen(path, "wb");
|
|
if (fp == NULL) {
|
|
free(buf);
|
|
return false;
|
|
}
|
|
fprintf(fp, "P6\n%u %u\n255\n", (unsigned)w, (unsigned)h);
|
|
for (i = 0u; i < (uint16_t)(w * h); i++) {
|
|
if (buf[i] == transparent) {
|
|
fputc(TRANS_R, fp);
|
|
fputc(TRANS_G, fp);
|
|
fputc(TRANS_B, fp);
|
|
} else {
|
|
rgb = kAgiPalette[buf[i] & 0x0Fu];
|
|
fputc((int)(((rgb >> 8) & 0x0Fu) * CHANNEL_SCALE), fp);
|
|
fputc((int)(((rgb >> 4) & 0x0Fu) * CHANNEL_SCALE), fp);
|
|
fputc((int)((rgb & 0x0Fu) * CHANNEL_SCALE), fp);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
free(buf);
|
|
return true;
|
|
}
|
|
|
|
|
|
// ----- main -----
|
|
|
|
int main(int argc, char **argv) {
|
|
AgiGameT game;
|
|
AgiViewT view;
|
|
int16_t viewId;
|
|
uint8_t *bytes;
|
|
uint16_t length;
|
|
uint8_t li;
|
|
uint8_t ci;
|
|
const AgiCelInfoT *cel;
|
|
char path[256];
|
|
|
|
if (argc < 2 || argc > 3) {
|
|
fprintf(stderr, "usage: %s <game-directory> [view-id]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
|
|
if (!agiResOpen(&game, argv[1])) {
|
|
fprintf(stderr, "FAIL: agiResOpen rejected '%s'\n", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
if (argc == 3) {
|
|
viewId = (int16_t)atoi(argv[2]);
|
|
} else {
|
|
viewId = firstNonEmptyView(&game);
|
|
}
|
|
if (viewId < 0 || (uint16_t)viewId >= game.resCount[AGI_RES_VIEW]) {
|
|
fprintf(stderr, "FAIL: no VIEW at id %d\n", (int)viewId);
|
|
agiResClose(&game);
|
|
return 1;
|
|
}
|
|
|
|
bytes = agiResLoad(&game, AGI_RES_VIEW, (uint16_t)viewId, &length);
|
|
if (bytes == NULL) {
|
|
fprintf(stderr, "FAIL: VIEW %d failed to load\n", (int)viewId);
|
|
agiResClose(&game);
|
|
return 1;
|
|
}
|
|
|
|
if (!agiViewParse(&view, bytes, length)) {
|
|
fprintf(stderr, "FAIL: VIEW %d parse rejected\n", (int)viewId);
|
|
agiResClose(&game);
|
|
return 1;
|
|
}
|
|
|
|
printf("VIEW %d: %u bytes, %u loops\n", (int)viewId, (unsigned)length, (unsigned)view.loopCount);
|
|
for (li = 0u; li < view.loopCount; li++) {
|
|
printf(" loop %u: %u cels\n", (unsigned)li, (unsigned)view.loops[li].celCount);
|
|
for (ci = 0u; ci < view.loops[li].celCount; ci++) {
|
|
cel = &view.loops[li].cels[ci];
|
|
printf(" cel %u: %ux%u trans=%u mirror=%u (src loop %u)\n",
|
|
(unsigned)ci, (unsigned)cel->width, (unsigned)cel->height,
|
|
(unsigned)cel->transparentColor,
|
|
(unsigned)cel->mirrored, (unsigned)cel->mirrorSourceLoop);
|
|
}
|
|
if (view.loops[li].celCount > 0u) {
|
|
snprintf(path, sizeof(path), "build/tests/view%d_loop%u_cel0.ppm", (int)viewId, (unsigned)li);
|
|
if (!writeCelPpm(path, &view.loops[li].cels[0])) {
|
|
fprintf(stderr, " warn: could not write %s\n", path);
|
|
}
|
|
}
|
|
}
|
|
|
|
agiViewFree(&view);
|
|
agiResClose(&game);
|
|
return 0;
|
|
}
|