joeyagi/tests/testAgiView.c

198 lines
5.5 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)) {
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;
}