// 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 [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 #include #include #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 [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; }