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

265 lines
9.6 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// iigsIcon.c - whether the IIgs Finder shows a program by its baked icon:
// scripts/verify-iigs-icon.sh's check.
//
// iigsIcon SHR.bin ICON LABEL [KEEP.png]
//
// SHR.bin is the Super Hi-Res block MAME's memory held ($E1/2000 to
// $E1/9FFF: pixels, scan line control bytes, palettes); ICON is iconBake's
// Finder icon file. The screen is read at 640 pixels a line: a 640-mode
// line's pixel takes its color from the palette quarter its place in the
// byte gives it (bits 7-6 from entries 8-11, 5-4 from 12-15, 3-2 from 0-3,
// 1-0 from 4-7); a 320-mode line's pixel is shown twice. Each 640-mode
// screen pixel's own 2-bit value is kept (none on a 320-mode line), and
// the icon is looked for by those: in 640 mode QuickDraw draws a 4-bit icon
// pixel as two screen pixels, its high two bits then its low two, so every
// pixel the icon's mask leaves opaque must be its two values, wherever the
// icon is. Every place it is counts.
//
// KEEP, when given and not empty, gets the screen as a PNG at 640x400 (each
// line twice), as it shows. Prints LABEL: PASS or LABEL: FAIL; 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"
#include "toolPng.h"
#define TOOL_NAME "iigsIcon"
#define FAIL_EXIT 1
#define WIDTH_640 640u
#define HEIGHT 200u
#define ROW_BYTES 160u
#define SCB_AT 0x7D00u
#define PALETTES_AT 0x7E00u
#define SHR_BYTES 0x8000u
#define SCB_640 0x80u
#define SCB_PALETTE_MASK 0x0Fu
#define PALETTE_BYTES 32u
#define PALETTE_ENTRIES 16u
#define ENTRY_BYTES 2u
#define BYTE_BITS 8u
#define PIXELS_A_BYTE_640 4u
// A 320-mode pixel is two screen pixels at 640 a line.
#define PIXEL_320_WIDTH 2u
#define NIBBLE_BITS 4u
#define NIBBLE_MASK 0xFu
#define KEEP_SCALE_Y 2u
#define NO_VALUE (-1)
// A 640-mode screen pixel is 2 bits; so is each half of an icon pixel.
#define HALF_BITS 2u
#define HALF_MASK 3u
#define HALVES 2u
// The icon file: its header (4 bytes, the file ID, 4 bytes, a 16-byte
// name), then the record's length word, 64-byte pathname, 16-byte name,
// file type, and aux type before its big icon: iconType, iconSize, height,
// width, the image (4 bits a pixel, the even pixel in the high nibble),
// and the mask, of the same shape.
#define FILE_HEADER_BYTES 26u
#define RECORD_HEAD_BYTES 86u
#define ICON_AT (FILE_HEADER_BYTES + RECORD_HEAD_BYTES)
#define ICON_SIZE_AT 2u
#define ICON_HEIGHT_AT 4u
#define ICON_WIDTH_AT 6u
#define ICON_HEADER_BYTES 8u
#define ICON_PIXELS_A_BYTE 2u
// One opaque half of an icon pixel: where it is (screen pixels) and its
// 2-bit value.
typedef struct HalfT {
uint32_t x;
uint32_t y;
int8_t value;
} HalfT;
static void keepScreen(const char *path, const uint8_t *rgb);
static void readScreen(const uint8_t *shr, uint8_t *rgb, int8_t *values);
// The palette quarter of each 2-bit pixel of a 640-mode byte, high bits
// first.
static const uint32_t kQuarterBase[PIXELS_A_BYTE_640] = { 8u, 12u, 0u, 4u };
static const char *const kUsage[] = {
"usage: iigsIcon SHR.bin ICON LABEL [KEEP.png]"
};
// The screen as it shows, each line twice, as a PNG.
static void keepScreen(const char *path, const uint8_t *rgb) {
ToolImageT keep;
uint32_t x;
uint32_t y;
uint8_t *out;
if (!toolImageCreate(&keep, WIDTH_640, HEIGHT * KEEP_SCALE_Y, false)) {
toolFail("out of memory");
}
for (y = 0; y < HEIGHT * KEEP_SCALE_Y; y++) {
for (x = 0; x < WIDTH_640; x++) {
out = keep.pixels + ((size_t)y * WIDTH_640 + x) * GATE_RGBA_BYTES;
memcpy(out, rgb + ((size_t)(y / KEEP_SCALE_Y) * WIDTH_640 + x) * GATE_RGB_BYTES, GATE_RGB_BYTES);
out[GATE_ALPHA] = UINT8_MAX;
}
}
if (!toolPngSave(path, &keep)) {
toolFail("cannot write %s", path);
}
toolImageFree(&keep);
}
// The screen at 640 pixels a line, as RGB and as each 640-mode pixel's
// 2-bit value (NO_VALUE on a 320-mode line).
static void readScreen(const uint8_t *shr, uint8_t *rgb, int8_t *values) {
uint8_t palette[PALETTE_ENTRIES][GATE_RGB_BYTES];
uint32_t scb;
uint32_t x;
uint32_t y;
uint32_t i;
uint32_t p;
uint32_t value;
uint8_t byte;
size_t at;
for (y = 0; y < HEIGHT; y++) {
scb = shr[SCB_AT + y];
for (i = 0; i < PALETTE_ENTRIES; i++) {
gateRgb12(gateLe16(shr + PALETTES_AT + (scb & SCB_PALETTE_MASK) * PALETTE_BYTES + i * ENTRY_BYTES), palette[i]);
}
for (x = 0; x < ROW_BYTES; x++) {
byte = shr[y * ROW_BYTES + x];
for (p = 0; p < PIXELS_A_BYTE_640; p++) {
at = (size_t)y * WIDTH_640 + x * PIXELS_A_BYTE_640 + p;
if (scb & SCB_640) {
value = (byte >> (BYTE_BITS - HALF_BITS - HALF_BITS * p)) & HALF_MASK;
values[at] = (int8_t)value;
memcpy(rgb + at * GATE_RGB_BYTES, palette[kQuarterBase[p] + value], GATE_RGB_BYTES);
} else {
value = (byte >> ((p / PIXEL_320_WIDTH == 0u) ? NIBBLE_BITS : 0u)) & NIBBLE_MASK;
values[at] = NO_VALUE;
memcpy(rgb + at * GATE_RGB_BYTES, palette[value], GATE_RGB_BYTES);
}
}
}
}
}
int main(int argc, char **argv) {
uint8_t *shr;
uint8_t *icon;
uint8_t *rgb;
int8_t *values;
HalfT *halves;
const uint8_t *image;
const char *label;
size_t shrLen;
size_t iconLen;
uint32_t size;
uint32_t w;
uint32_t h;
uint32_t rowBytes;
uint32_t halfCount;
uint32_t found;
uint32_t firstX;
uint32_t firstY;
uint32_t x;
uint32_t y;
uint32_t i;
uint32_t shift;
uint32_t nibble;
uint32_t half;
bool match;
toolFileName(TOOL_NAME);
if (argc != 4 && argc != 5) {
return gateUsage(kUsage, GATE_COUNT_OF(kUsage));
}
label = argv[3];
if (!toolReadFile(argv[1], &shr, &shrLen) || shrLen < SHR_BYTES) {
toolFail("cannot read %u bytes of %s", (unsigned)SHR_BYTES, argv[1]);
}
rgb = (uint8_t *)malloc((size_t)WIDTH_640 * HEIGHT * GATE_RGB_BYTES);
values = (int8_t *)malloc((size_t)WIDTH_640 * HEIGHT);
if (rgb == NULL || values == NULL) {
toolFail("out of memory");
}
readScreen(shr, rgb, values);
if (argc == 5 && argv[4][0] != '\0') {
keepScreen(argv[4], rgb);
}
// The big icon of the file's one record: its 4-bit pixels and mask,
// the even pixel in the high nibble.
if (!toolReadFile(argv[2], &icon, &iconLen) || iconLen < ICON_AT + ICON_HEADER_BYTES) {
toolFail("cannot read the icon record of %s", argv[2]);
}
size = gateLe16(icon + ICON_AT + ICON_SIZE_AT);
h = gateLe16(icon + ICON_AT + ICON_HEIGHT_AT);
w = gateLe16(icon + ICON_AT + ICON_WIDTH_AT);
image = icon + ICON_AT + ICON_HEADER_BYTES;
rowBytes = (w + ICON_PIXELS_A_BYTE - 1u) / ICON_PIXELS_A_BYTE;
if ((size_t)size + (size_t)rowBytes * h > iconLen - ICON_AT - ICON_HEADER_BYTES) {
toolFail("%s's icon is truncated", argv[2]);
}
halves = (HalfT *)malloc(((size_t)w * h + 1u) * HALVES * sizeof(HalfT));
halfCount = 0u;
if (halves == NULL) {
toolFail("out of memory");
}
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
shift = (x % ICON_PIXELS_A_BYTE == 0u) ? NIBBLE_BITS : 0u;
if (((image[size + y * rowBytes + x / ICON_PIXELS_A_BYTE] >> shift) & NIBBLE_MASK) == 0u) {
continue;
}
// In 640 mode an icon pixel is two screen pixels: its high
// half, then its low half.
nibble = (image[y * rowBytes + x / ICON_PIXELS_A_BYTE] >> shift) & NIBBLE_MASK;
for (half = 0; half < HALVES; half++) {
halves[halfCount].x = x * HALVES + half;
halves[halfCount].y = y;
halves[halfCount].value = (int8_t)((half == 0u) ? nibble >> HALF_BITS : nibble & HALF_MASK);
halfCount++;
}
}
}
if (halfCount == 0u) {
toolFail("%s's icon has no opaque pixel", argv[2]);
}
found = 0u;
firstX = 0u;
firstY = 0u;
for (y = 0; y + h <= HEIGHT; y++) {
for (x = 0; x + w * HALVES <= WIDTH_640; x++) {
match = true;
for (i = 0; i < halfCount && match; i++) {
match = values[(size_t)(y + halves[i].y) * WIDTH_640 + x + halves[i].x] == halves[i].value;
}
if (match) {
firstX = (found == 0u) ? x : firstX;
firstY = (found == 0u) ? y : firstY;
found++;
}
}
}
if (found == 0u) {
printf("%s: FAIL - no program's icon is in the JOEYLIB window (%u opaque pixels looked for)\n", label, (unsigned)(halfCount / HALVES));
return FAIL_EXIT;
}
printf("%s: PASS (the Finder shows %u programs by the baked %ux%u color icon, the first at %u,%u: all %u opaque pixels, each its two screen pixels' values)\n", label, (unsigned)found, (unsigned)w, (unsigned)h, (unsigned)firstX, (unsigned)firstY, (unsigned)(halfCount / HALVES));
free(halves);
free(icon);
free(values);
free(rgb);
free(shr);
return 0;
}