246 lines
8.4 KiB
C
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
|
|
// 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 <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 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;
|
|
}
|