// 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 // colour 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 colour by a few levels. The search anchors on the opaque pixel whose // colour the screen holds least and tries the icon at every place that // colour 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 grey (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 #include #include #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 notGrey(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 kDvxGrey[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 grey)", " 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 colour. 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 grey. static int findDvx(char **args) { GateImageT bmp; GateImageT screen; GateIconT icon; uint32_t left; uint32_t top; gateImageLoad(args[1], &bmp); iconFromImage(&bmp, notGrey, &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 grey)\n", args[2], (unsigned)left, (unsigned)top, (unsigned)icon.count, (unsigned)icon.width, (unsigned)icon.height); return 0; } // DRAW minimised: 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 minimised 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 notGrey(const uint8_t *pixel) { return memcmp(pixel, kDvxGrey, 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; }