joeylib2/tests/gate/iconFind.c
2026-10-19 15:46:47 -05:00

246 lines
8.4 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// iconFind.c - whether a screenshot shows a program's baked icon: the icon
// gates' check (scripts/verify-be-icon.sh, verify-dvx-icon.sh, and
// verify-win16-icon.sh).
//
// iconFind be PNG ICONDIR PALETTE.h LABEL
// iconFind dvx PNG ICON.bmp LABEL
// iconFind win16 PNG ICON.ico LABEL
//
// The screenshot must hold every opaque pixel of the icon, each in its
// color give or take TOLERANCE a gun: BeOS R5's and DVX's 16-bit screens
// keep 5 or 6 bits a gun, and the 6-bit DAC behind DOSBox's capture moves
// a color by a few levels. The search anchors on the opaque pixel whose
// color the screen holds least and tries the icon at every place that
// color is (gateIconFind).
//
// be: iconBake's raw icons in ICONDIR, icon32.raw then icon16.raw (32x32
// and 16x16 indices into the BeOS palette, whose numbers are read from
// PALETTE.h's kBePalette table; index 255 is transparent); either will do.
// dvx: a BMP whose DVX gray (192, 192, 192) is the ground. win16: an icon
// whose picture is opaque where its alpha is at least half.
//
// Prints LABEL: PASS or LABEL: FAIL as the gate's verdict; exits 0 or 1,
// or 2 on a bad command line.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gateArg.h"
#include "gateImage.h"
#include "toolFile.h"
#define TOOL_NAME "iconFind"
#define FAIL_EXIT 1
#define TOLERANCE 8u
#define ALPHA_OPAQUE_MIN 128u
#define BE_ICON_COUNT 2u
#define BE_TRANSPARENT 255u
#define BE_PALETTE_NAME "kBePalette"
#define BE_PALETTE_SIZE 256u
#define PATH_BYTES 4096u
#define DECIMAL_BASE 10u
// Whether an icon's pixel is opaque.
typedef bool (*OpaqueFnT)(const uint8_t *pixel);
// A Be icon iconBake bakes: its side and its file.
typedef struct BeIconT {
uint32_t side;
const char *file;
} BeIconT;
static void bePalette(const char *path, uint8_t palette[BE_PALETTE_SIZE][GATE_RGB_BYTES]);
static int findBe(char **args);
static int findDvx(char **args);
static int findWin16(char **args);
static bool halfOpaque(const uint8_t *pixel);
static void iconFromImage(const GateImageT *img, OpaqueFnT opaque, GateIconT *icon);
static bool notGray(const uint8_t *pixel);
static const GateModeT kModes[] = {
{ "be", 4, findBe },
{ "dvx", 3, findDvx },
{ "win16", 3, findWin16 }
};
static const BeIconT kBeIcons[BE_ICON_COUNT] = { { 32u, "icon32.raw" }, { 16u, "icon16.raw" } };
static const uint8_t kDvxGray[GATE_RGB_BYTES] = { 192, 192, 192 };
static const char *const kUsage[] = {
"usage:",
" iconFind be PNG ICONDIR PALETTE.h LABEL Tracker shows icon32.raw or",
" icon16.raw (BeOS palette)",
" iconFind dvx PNG ICON.bmp LABEL Program Manager shows the BMP",
" (all but DVX's gray)",
" iconFind win16 PNG ICON.ico LABEL Windows 3.1 shows the icon",
" (its opaque pixels)"
};
// The BeOS palette iconBake bakes with: the numbers in the table that
// follows kBePalette in its header, three a color.
static void bePalette(const char *path, uint8_t palette[BE_PALETTE_SIZE][GATE_RGB_BYTES]) {
uint8_t *text;
size_t len;
const char *at;
uint32_t count;
uint32_t value;
if (!toolReadFile(path, &text, &len)) {
toolFail("cannot read %s", path);
}
at = strstr((const char *)text, BE_PALETTE_NAME);
at = (at != NULL) ? strchr(at, '{') : NULL;
if (at == NULL) {
toolFail("%s has no %s table", path, BE_PALETTE_NAME);
}
count = 0u;
while (*at != '\0' && count < BE_PALETTE_SIZE * GATE_RGB_BYTES) {
if (*at < '0' || *at > '9') {
at++;
continue;
}
value = 0u;
while (*at >= '0' && *at <= '9') {
value = value * DECIMAL_BASE + (uint32_t)(*at - '0');
at++;
}
palette[count / GATE_RGB_BYTES][count % GATE_RGB_BYTES] = (uint8_t)value;
count++;
}
if (count < BE_PALETTE_SIZE * GATE_RGB_BYTES) {
toolFail("%s's %s table has %u numbers, not %u", path, BE_PALETTE_NAME, (unsigned)count, (unsigned)(BE_PALETTE_SIZE * GATE_RGB_BYTES));
}
free(text);
}
// Tracker shows DRAW by one of its baked icons, either size.
static int findBe(char **args) {
GateImageT screen;
GateIconT icon;
uint8_t palette[BE_PALETTE_SIZE][GATE_RGB_BYTES];
char path[PATH_BYTES];
uint8_t *indices;
size_t len;
size_t i;
uint32_t b;
uint32_t left;
uint32_t top;
gateImageLoad(args[0], &screen);
bePalette(args[2], palette);
for (b = 0; b < BE_ICON_COUNT; b++) {
snprintf(path, sizeof(path), "%s/%s", args[1], kBeIcons[b].file);
if (!toolReadFile(path, &indices, &len)) {
toolFail("cannot read %s", path);
}
memset(&icon, 0, sizeof(icon));
icon.width = kBeIcons[b].side;
icon.height = kBeIcons[b].side;
for (i = 0; i < len; i++) {
if (indices[i] != BE_TRANSPARENT) {
gateIconAdd(&icon, (uint32_t)(i % kBeIcons[b].side), (uint32_t)(i / kBeIcons[b].side), palette[indices[i]]);
}
}
free(indices);
if (gateIconFind(&screen, &icon, TOLERANCE, &left, &top)) {
printf("%s: PASS (Tracker shows DRAW by its icon at %u,%u: all %u opaque pixels of the baked %ux%u)\n", args[3], (unsigned)left, (unsigned)top, (unsigned)icon.count, (unsigned)icon.width, (unsigned)icon.height);
return 0;
}
gateIconFree(&icon);
}
printf("%s: FAIL - neither of DRAW's icons is on the screen\n", args[3]);
return FAIL_EXIT;
}
// Program Manager shows DRAW by its icon: every pixel not the gray.
static int findDvx(char **args) {
GateImageT bmp;
GateImageT screen;
GateIconT icon;
uint32_t left;
uint32_t top;
gateImageLoad(args[1], &bmp);
iconFromImage(&bmp, notGray, &icon);
gateImageLoad(args[0], &screen);
if (!gateIconFind(&screen, &icon, TOLERANCE, &left, &top)) {
printf("%s: FAIL - DRAW's icon is not on the screen (%u pixels looked for)\n", args[2], (unsigned)icon.count);
return FAIL_EXIT;
}
printf("%s: PASS (Program Manager shows DRAW by its icon at %u,%u: all %u pixels of the baked %ux%u that are not the gray)\n", args[2], (unsigned)left, (unsigned)top, (unsigned)icon.count, (unsigned)icon.width, (unsigned)icon.height);
return 0;
}
// DRAW minimized: Windows 3.1 shows the icon bound into it.
static int findWin16(char **args) {
GateImageT ico;
GateImageT screen;
GateIconT icon;
uint32_t left;
uint32_t top;
gateImageLoad(args[0], &screen);
gateImageLoad(args[1], &ico);
iconFromImage(&ico, halfOpaque, &icon);
if (icon.count == 0u) {
printf("%s: FAIL - the icon file has no opaque pixel\n", args[2]);
return FAIL_EXIT;
}
if (!gateIconFind(&screen, &icon, TOLERANCE, &left, &top)) {
printf("%s: FAIL - the program's icon is not on the screen (%u opaque pixels looked for)\n", args[2], (unsigned)icon.count);
return FAIL_EXIT;
}
printf("%s: PASS (DRAW minimized shows its bound icon at %u,%u: all %u opaque pixels of the baked 32x32 in place)\n", args[2], (unsigned)left, (unsigned)top, (unsigned)icon.count);
return 0;
}
static bool halfOpaque(const uint8_t *pixel) {
return pixel[GATE_ALPHA] >= ALPHA_OPAQUE_MIN;
}
// An icon from a picture: its size, and its opaque pixels row by row.
static void iconFromImage(const GateImageT *img, OpaqueFnT opaque, GateIconT *icon) {
const uint8_t *pixel;
uint32_t x;
uint32_t y;
memset(icon, 0, sizeof(*icon));
icon->width = img->width;
icon->height = img->height;
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++) {
pixel = gateImagePixel(img, x, y);
if (opaque(pixel)) {
gateIconAdd(icon, x, y, pixel);
}
}
}
}
static bool notGray(const uint8_t *pixel) {
return memcmp(pixel, kDvxGray, GATE_RGB_BYTES) != 0;
}
int main(int argc, char **argv) {
int exitCode;
toolFileName(TOOL_NAME);
if (!gateModeRun(kModes, GATE_COUNT_OF(kModes), argc, argv, &exitCode)) {
return gateUsage(kUsage, GATE_COUNT_OF(kUsage));
}
return exitCode;
}