joeyagi/tests/testAgiPic.c

191 lines
5.1 KiB
C

// Host-side test for examples/agi/agiPic.c.
//
// Loads a single PIC resource from a game directory, decodes it, and
// writes two image files: a color PPM for the visual plane (palette
// expanded to 8-bit RGB) and a grayscale PGM for the priority plane.
// Also reports the byte count and pixel coverage so a regression in
// the decoder shows up as a numeric diff.
//
// Usage:
// testAgiPic <game-directory> [pic-id]
//
// The default pic-id is the first non-empty entry in PICDIR; KQ3 has
// id 0 unused so the loader hops forward until it finds one.
//
// Exit codes:
// 0 decoded; outputs written
// 1 game directory missing, PIC slot empty, or decode failed
// 2 invalid command line
#include "agi.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 4-bit channel -> 8-bit channel scaling: x * 17 maps 0..15 to 0..255
// without bias (15 * 17 = 255 exactly).
#define CHANNEL_SCALE 17
// ----- Prototypes -----
static int16_t firstNonEmptyPic(const AgiGameT *game);
static bool writePgm(const char *path, const uint8_t *plane);
static bool writePpm(const char *path, const uint8_t *plane);
// ----- Helpers (alphabetical) -----
static int16_t firstNonEmptyPic(const AgiGameT *game) {
uint16_t count;
uint16_t i;
count = game->resCount[AGI_RES_PIC];
for (i = 0u; i < count; i++) {
if (game->resDir[AGI_RES_PIC][i].volume < AGI_MAX_VOLUMES) {
return (int16_t)i;
}
}
return -1;
}
static bool writePgm(const char *path, const uint8_t *plane) {
FILE *fp;
uint32_t i;
uint8_t gray;
fp = fopen(path, "wb");
if (fp == NULL) {
return false;
}
fprintf(fp, "P5\n%d %d\n255\n", AGI_PIC_WIDTH, AGI_PIC_HEIGHT);
for (i = 0u; i < (uint32_t)AGI_PIC_PIXELS; i++) {
gray = (uint8_t)((plane[i] & 0x0Fu) * CHANNEL_SCALE);
fputc(gray, fp);
}
fclose(fp);
return true;
}
static bool writePpm(const char *path, const uint8_t *plane) {
FILE *fp;
uint32_t i;
uint16_t rgb;
uint8_t r;
uint8_t g;
uint8_t b;
fp = fopen(path, "wb");
if (fp == NULL) {
return false;
}
fprintf(fp, "P6\n%d %d\n255\n", AGI_PIC_WIDTH, AGI_PIC_HEIGHT);
for (i = 0u; i < (uint32_t)AGI_PIC_PIXELS; i++) {
rgb = kAgiPalette[plane[i] & 0x0Fu];
r = (uint8_t)(((rgb >> 8) & 0x0Fu) * CHANNEL_SCALE);
g = (uint8_t)(((rgb >> 4) & 0x0Fu) * CHANNEL_SCALE);
b = (uint8_t)((rgb & 0x0Fu) * CHANNEL_SCALE);
fputc(r, fp);
fputc(g, fp);
fputc(b, fp);
}
fclose(fp);
return true;
}
// ----- main -----
int main(int argc, char **argv) {
AgiGameT game;
AgiPicT pic;
int16_t picId;
uint8_t *bytes;
uint16_t length;
uint32_t nonBgVisual;
uint32_t nonBgPriority;
uint32_t i;
char visPath[256];
char priPath[256];
if (argc < 2 || argc > 3) {
fprintf(stderr, "usage: %s <game-directory> [pic-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) {
picId = (int16_t)atoi(argv[2]);
} else {
picId = firstNonEmptyPic(&game);
}
if (picId < 0 || (uint16_t)picId >= game.resCount[AGI_RES_PIC]) {
fprintf(stderr, "FAIL: no PIC at id %d\n", (int)picId);
agiResClose(&game);
return 1;
}
bytes = agiResLoad(&game, AGI_RES_PIC, (uint16_t)picId, &length);
if (bytes == NULL) {
fprintf(stderr, "FAIL: PIC %d failed to load\n", (int)picId);
agiResClose(&game);
return 1;
}
if (!agiPicAlloc(&pic)) {
fprintf(stderr, "FAIL: agiPicAlloc out of memory\n");
free(bytes);
agiResClose(&game);
return 1;
}
agiPicClear(&pic);
if (!agiPicDecode(&pic, bytes, length)) {
fprintf(stderr, "FAIL: PIC %d decode rejected (malformed stream?)\n", (int)picId);
agiPicFree(&pic);
free(bytes);
agiResClose(&game);
return 1;
}
nonBgVisual = 0u;
nonBgPriority = 0u;
for (i = 0u; i < (uint32_t)AGI_PIC_PIXELS; i++) {
if (pic.visual[i] != AGI_VISUAL_BG) {
nonBgVisual++;
}
if (pic.priority[i] != AGI_PRIORITY_BG) {
nonBgPriority++;
}
}
snprintf(visPath, sizeof(visPath), "build/tests/pic%d_visual.ppm", (int)picId);
snprintf(priPath, sizeof(priPath), "build/tests/pic%d_priority.pgm", (int)picId);
if (!writePpm(visPath, pic.visual) || !writePgm(priPath, pic.priority)) {
fprintf(stderr, "FAIL: could not write output images\n");
agiPicFree(&pic);
free(bytes);
agiResClose(&game);
return 1;
}
printf("PIC %d: %u bytes, visual %u/%u px painted, priority %u/%u px painted\n",
(int)picId, (unsigned)length,
(unsigned)nonBgVisual, (unsigned)AGI_PIC_PIXELS,
(unsigned)nonBgPriority, (unsigned)AGI_PIC_PIXELS);
printf(" wrote %s\n", visPath);
printf(" wrote %s\n", priPath);
agiPicFree(&pic);
free(bytes);
agiResClose(&game);
return 0;
}