682 lines
23 KiB
C
682 lines
23 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// gateImage.c - screenshots and icons for the gate tools (gateImage.h).
|
|
//
|
|
// The rules the pictures are read by (checked once against the Python
|
|
// gates these tools replaced):
|
|
// - A PNG goes through toolPng to RGBA: gray spread to RGB, 16-bit
|
|
// samples cut to their high byte, palette entries looked up.
|
|
// - A BMP is an uncompressed DIB: 1, 4, or 8 bits through its color
|
|
// table (an index past the table is black), or 24 or 32 bits of BGR; it
|
|
// is opaque.
|
|
// - An ICO's directory is sorted largest picture first and, among equals,
|
|
// fewest colors first (a stable sort); its default picture is the first.
|
|
// A picture that is a PNG is decoded as one. A DIB picture is its XOR
|
|
// bitmap (half the height its header gives), its alpha the 32-bit
|
|
// picture's fourth bytes or else its AND mask (a set bit transparent),
|
|
// which is the last bytes of the picture as the directory sizes it.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gateArg.h"
|
|
#include "gateImage.h"
|
|
#include "toolFile.h"
|
|
#include "toolPng.h"
|
|
|
|
#define BYTE_BITS 8u
|
|
#define LE16_BYTES 2u
|
|
#define SAMPLE_MAX 255u
|
|
#define RED_SHIFT 16u
|
|
#define GREEN_SHIFT 8u
|
|
#define NIBBLE_MASK 0xFu
|
|
#define NIBBLE_BITS 4u
|
|
// A 4-bit gun widened to 8 bits: 0xF x 17 is 0xFF.
|
|
#define NIBBLE_WIDEN 17u
|
|
#define PIXELS_INITIAL 256u
|
|
|
|
// KEYS's palette 0: a gray ramp (n in each gun), then black, dark gray,
|
|
// green, and white at 0 to 3 and red at 15 (examples/keys buildPalette).
|
|
#define KEYS_BLACK 0x000u
|
|
#define KEYS_UNLIT 0x333u
|
|
#define KEYS_LIT 0x0F0u
|
|
#define KEYS_CURSOR 0xFFFu
|
|
#define KEYS_RED 0xF00u
|
|
#define KEYS_RED_INDEX 15u
|
|
#define KEYS_SENTINEL 0xA5u
|
|
#define KEYS_COUNT_MAX 255u
|
|
#define ASCII_MAX 0x7Fu
|
|
|
|
// File magic.
|
|
#define PNG_MAGIC "\x89PNG\r\n\x1a\n"
|
|
#define PNG_MAGIC_BYTES 8u
|
|
#define BMP_MAGIC "BM"
|
|
#define BMP_MAGIC_BYTES 2u
|
|
#define ICO_MAGIC "\0\0\1\0"
|
|
#define ICO_MAGIC_BYTES 4u
|
|
|
|
// BMP: the file header, and where its pixels start.
|
|
#define BMP_FILE_HEADER 14u
|
|
#define BMP_PIXELS_AT 10u
|
|
|
|
// A DIB's BITMAPINFOHEADER and color table.
|
|
#define DIB_HEADER_MIN 40u
|
|
#define DIB_WIDTH_AT 4u
|
|
#define DIB_HEIGHT_AT 8u
|
|
#define DIB_BITS_AT 14u
|
|
#define DIB_COMPRESSION_AT 16u
|
|
#define DIB_COLORS_AT 32u
|
|
#define DIB_RGB 0u
|
|
#define DIB_ENTRY_BYTES 4u
|
|
#define DIB_ROW_BITS 32u
|
|
#define DIB_BLUE 0u
|
|
#define DIB_GREEN 1u
|
|
#define DIB_RED 2u
|
|
#define DIB_PALETTE_MAX 256u
|
|
#define BITS_1 1u
|
|
#define BITS_4 4u
|
|
#define BITS_8 8u
|
|
#define BITS_24 24u
|
|
#define BITS_32 32u
|
|
#define TOP_BIT 0x80u
|
|
|
|
// ICO: the directory and its entries (a 0 side means 256).
|
|
#define ICO_COUNT_AT 4u
|
|
#define ICO_DIR_BYTES 6u
|
|
#define ICO_ENTRY_BYTES 16u
|
|
#define ICO_ENTRY_W 0u
|
|
#define ICO_ENTRY_H 1u
|
|
#define ICO_ENTRY_COLORS 2u
|
|
#define ICO_ENTRY_BPP 6u
|
|
#define ICO_ENTRY_SIZE 8u
|
|
#define ICO_ENTRY_OFFSET 12u
|
|
#define ICO_SIDE_ZERO 256u
|
|
#define ICO_DEPTH_NONE 256u
|
|
// gateImageLoad's ICO picture: the default, the sorted directory's first;
|
|
// and a picture of any depth.
|
|
#define ICO_DEFAULT_SIDE 0u
|
|
#define ICO_ANY_BPP 0u
|
|
|
|
// A DIB's layout: the picture's size (the XOR half of an icon's), its bits
|
|
// a pixel, row order, and where its rows and its color table are.
|
|
typedef struct DibT {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t bits;
|
|
bool bottomUp;
|
|
size_t paletteAt;
|
|
uint32_t paletteCount;
|
|
size_t pixelsAt;
|
|
size_t stride;
|
|
} DibT;
|
|
|
|
// An ICO directory entry: its picture's size and depth, and where it is.
|
|
typedef struct IcoEntryT {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t colorDepth;
|
|
uint32_t bpp;
|
|
uint32_t size;
|
|
uint32_t offset;
|
|
} IcoEntryT;
|
|
|
|
static void bmpDecode(const uint8_t *data, size_t len, const char *path, GateImageT *img);
|
|
static void dibDecode(const uint8_t *data, size_t len, const DibT *dib, const char *path, GateImageT *img);
|
|
static void dibRead(const uint8_t *data, size_t len, size_t headerAt, size_t pixelsAt, bool icon, const char *path, DibT *dib);
|
|
static void icoAndMask(const uint8_t *data, size_t len, const IcoEntryT *entry, const char *path, GateImageT *img);
|
|
static void icoDecode(const uint8_t *data, size_t len, uint32_t side, uint32_t bpp, const char *path, GateImageT *img);
|
|
static uint32_t icoDepth(uint32_t bpp, uint32_t colors);
|
|
static bool icoEntryBefore(const IcoEntryT *a, const IcoEntryT *b);
|
|
static uint32_t iconAnchor(const GateImageT *screen, const GateIconT *icon, uint32_t tolerance);
|
|
static bool iconAt(const GateImageT *screen, const GateIconT *icon, uint32_t tolerance, uint32_t left, uint32_t top);
|
|
static void imageAlloc(GateImageT *img, uint32_t width, uint32_t height);
|
|
static bool magic(const uint8_t *data, size_t len, const char *bytes, size_t count);
|
|
static int packedCompare(const void *a, const void *b);
|
|
static void pngDecode(const uint8_t *data, size_t len, const char *path, GateImageT *img);
|
|
static bool within(const uint8_t *a, const uint8_t *b, uint32_t tolerance);
|
|
|
|
|
|
// A BMP file: its DIB, whose rows start where the file header says.
|
|
static void bmpDecode(const uint8_t *data, size_t len, const char *path, GateImageT *img) {
|
|
DibT dib;
|
|
|
|
if (len < BMP_FILE_HEADER) {
|
|
toolFail("%s: truncated BMP", path);
|
|
}
|
|
dibRead(data, len, BMP_FILE_HEADER, gateLe32(data + BMP_PIXELS_AT), false, path, &dib);
|
|
dibDecode(data, len, &dib, path, img);
|
|
}
|
|
|
|
|
|
// A DIB's rows as RGBA, opaque.
|
|
static void dibDecode(const uint8_t *data, size_t len, const DibT *dib, const char *path, GateImageT *img) {
|
|
const uint8_t *row;
|
|
const uint8_t *entry;
|
|
uint8_t *out;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
uint32_t index;
|
|
uint32_t shift;
|
|
|
|
if (dib->pixelsAt > len || dib->stride * dib->height > len - dib->pixelsAt) {
|
|
toolFail("%s: truncated picture", path);
|
|
}
|
|
imageAlloc(img, dib->width, dib->height);
|
|
for (y = 0; y < dib->height; y++) {
|
|
row = data + dib->pixelsAt + dib->stride * (dib->bottomUp ? dib->height - 1u - y : y);
|
|
for (x = 0; x < dib->width; x++) {
|
|
out = img->rgba + ((size_t)y * dib->width + x) * GATE_RGBA_BYTES;
|
|
if (dib->bits >= BITS_24) {
|
|
entry = row + (size_t)x * (dib->bits / BYTE_BITS);
|
|
} else {
|
|
shift = BYTE_BITS - dib->bits - (x * dib->bits) % BYTE_BITS;
|
|
index = (row[(x * dib->bits) / BYTE_BITS] >> shift) & ((1u << dib->bits) - 1u);
|
|
entry = (index < dib->paletteCount) ? data + dib->paletteAt + (size_t)index * DIB_ENTRY_BYTES : NULL;
|
|
}
|
|
if (entry != NULL) {
|
|
out[0] = entry[DIB_RED];
|
|
out[1] = entry[DIB_GREEN];
|
|
out[2] = entry[DIB_BLUE];
|
|
}
|
|
out[GATE_ALPHA] = SAMPLE_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// A DIB's BITMAPINFOHEADER at headerAt: an icon's picture is the upper
|
|
// half of the height it gives, and its rows follow its color table
|
|
// (pixelsAt 0); a BMP file says where its rows are.
|
|
static void dibRead(const uint8_t *data, size_t len, size_t headerAt, size_t pixelsAt, bool icon, const char *path, DibT *dib) {
|
|
const uint8_t *h;
|
|
uint32_t headerBytes;
|
|
int32_t height;
|
|
uint32_t colors;
|
|
|
|
if (headerAt > len || len - headerAt < DIB_HEADER_MIN) {
|
|
toolFail("%s: truncated bitmap header", path);
|
|
}
|
|
h = data + headerAt;
|
|
headerBytes = gateLe32(h);
|
|
height = (int32_t)gateLe32(h + DIB_HEIGHT_AT);
|
|
dib->width = gateLe32(h + DIB_WIDTH_AT);
|
|
dib->bits = gateLe16(h + DIB_BITS_AT);
|
|
colors = gateLe32(h + DIB_COLORS_AT);
|
|
if (headerBytes < DIB_HEADER_MIN || gateLe32(h + DIB_COMPRESSION_AT) != DIB_RGB || (int32_t)dib->width <= 0 || height == 0) {
|
|
toolFail("%s: not an uncompressed bitmap", path);
|
|
}
|
|
if (dib->bits != BITS_1 && dib->bits != BITS_4 && dib->bits != BITS_8 && dib->bits != BITS_24 && dib->bits != BITS_32) {
|
|
toolFail("%s: %u bits a pixel is not read", path, (unsigned)dib->bits);
|
|
}
|
|
dib->bottomUp = height > 0;
|
|
dib->height = (uint32_t)(height > 0 ? height : -height);
|
|
dib->paletteAt = headerAt + headerBytes;
|
|
dib->paletteCount = 0u;
|
|
if (dib->bits <= BITS_8) {
|
|
dib->paletteCount = (colors != 0u) ? colors : (1u << dib->bits);
|
|
if (dib->paletteCount > DIB_PALETTE_MAX || dib->paletteAt > len || (size_t)dib->paletteCount * DIB_ENTRY_BYTES > len - dib->paletteAt) {
|
|
toolFail("%s: bad color table", path);
|
|
}
|
|
}
|
|
if (icon) {
|
|
dib->height /= 2u;
|
|
}
|
|
dib->pixelsAt = (pixelsAt != 0u) ? pixelsAt : dib->paletteAt + (size_t)dib->paletteCount * DIB_ENTRY_BYTES;
|
|
dib->stride = (((size_t)dib->width * dib->bits + DIB_ROW_BITS - 1u) / DIB_ROW_BITS) * (DIB_ROW_BITS / BYTE_BITS);
|
|
}
|
|
|
|
|
|
uint32_t gateDistance(const uint8_t *a, const uint8_t *b) {
|
|
uint32_t sum;
|
|
int32_t d;
|
|
uint32_t i;
|
|
|
|
sum = 0u;
|
|
for (i = 0; i < GATE_RGB_BYTES; i++) {
|
|
d = (int32_t)a[i] - (int32_t)b[i];
|
|
sum += (uint32_t)(d * d);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
|
|
void gateHexPrint(const uint8_t *data, size_t len) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
printf("%02X", (unsigned)data[i]);
|
|
}
|
|
}
|
|
|
|
|
|
void gateIconAdd(GateIconT *icon, uint32_t x, uint32_t y, const uint8_t *rgb) {
|
|
GateIconPixelT *grown;
|
|
|
|
if (icon->count == icon->cap) {
|
|
icon->cap = (icon->cap == 0u) ? PIXELS_INITIAL : icon->cap * 2u;
|
|
grown = (GateIconPixelT *)realloc(icon->pixels, (size_t)icon->cap * sizeof(GateIconPixelT));
|
|
if (grown == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
icon->pixels = grown;
|
|
}
|
|
icon->pixels[icon->count].x = x;
|
|
icon->pixels[icon->count].y = y;
|
|
memcpy(icon->pixels[icon->count].rgb, rgb, GATE_RGB_BYTES);
|
|
icon->count++;
|
|
}
|
|
|
|
|
|
bool gateIconFind(const GateImageT *screen, const GateIconT *icon, uint32_t tolerance, uint32_t *left, uint32_t *top) {
|
|
const GateIconPixelT *a;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
|
|
if (icon->count == 0u) {
|
|
toolFail("the icon has no opaque pixel");
|
|
}
|
|
a = &icon->pixels[iconAnchor(screen, icon, tolerance)];
|
|
for (y = a->y; y < screen->height; y++) {
|
|
for (x = a->x; x < screen->width; x++) {
|
|
if (within(gateImagePixel(screen, x, y), a->rgb, tolerance) && iconAt(screen, icon, tolerance, x - a->x, y - a->y)) {
|
|
*left = x - a->x;
|
|
*top = y - a->y;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void gateIconFree(GateIconT *icon) {
|
|
free(icon->pixels);
|
|
icon->pixels = NULL;
|
|
icon->count = 0u;
|
|
icon->cap = 0u;
|
|
}
|
|
|
|
|
|
void gateImageFree(GateImageT *img) {
|
|
free(img->rgba);
|
|
img->rgba = NULL;
|
|
}
|
|
|
|
|
|
bool gateImageHas(const GateImageT *img, long x, long y) {
|
|
return x >= 0 && y >= 0 && x < (long)img->width && y < (long)img->height;
|
|
}
|
|
|
|
|
|
void gateImageLoad(const char *path, GateImageT *img) {
|
|
gateImageLoadIcon(path, ICO_DEFAULT_SIDE, ICO_ANY_BPP, img);
|
|
}
|
|
|
|
|
|
// A picture file by its magic: PNG, BMP, or ICO (side and bpp choose an
|
|
// ICO's picture).
|
|
void gateImageLoadIcon(const char *path, uint32_t side, uint32_t bpp, GateImageT *img) {
|
|
uint8_t *data;
|
|
size_t len;
|
|
|
|
if (!toolReadFile(path, &data, &len)) {
|
|
toolFail("cannot read %s", path);
|
|
}
|
|
if (magic(data, len, PNG_MAGIC, PNG_MAGIC_BYTES)) {
|
|
pngDecode(data, len, path, img);
|
|
} else if (magic(data, len, BMP_MAGIC, BMP_MAGIC_BYTES)) {
|
|
bmpDecode(data, len, path, img);
|
|
} else if (magic(data, len, ICO_MAGIC, ICO_MAGIC_BYTES)) {
|
|
icoDecode(data, len, side, bpp, path, img);
|
|
} else {
|
|
toolFail("cannot identify image file %s", path);
|
|
}
|
|
free(data);
|
|
}
|
|
|
|
|
|
const uint8_t *gateImagePixel(const GateImageT *img, uint32_t x, uint32_t y) {
|
|
return img->rgba + ((size_t)y * img->width + x) * GATE_RGBA_BYTES;
|
|
}
|
|
|
|
|
|
void gateKeysExpect(const char *target, ToolBufT *expect) {
|
|
size_t len;
|
|
size_t i;
|
|
bool ascii;
|
|
|
|
len = strlen(target);
|
|
ascii = true;
|
|
for (i = 0; i < len; i++) {
|
|
ascii = ascii && (uint8_t)target[i] <= ASCII_MAX;
|
|
}
|
|
if (!ascii || len > KEYS_COUNT_MAX) {
|
|
gateArgBad("target", target);
|
|
}
|
|
toolBufByte(expect, KEYS_SENTINEL);
|
|
toolBufByte(expect, KEYS_SENTINEL);
|
|
toolBufByte(expect, (uint32_t)len);
|
|
toolBufByte(expect, 0u);
|
|
toolBufAdd(expect, target, len);
|
|
}
|
|
|
|
|
|
void gateKeysPalette(uint16_t words[GATE_KEYS_COLORS]) {
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < GATE_KEYS_COLORS; i++) {
|
|
words[i] = (uint16_t)((i << (2u * NIBBLE_BITS)) | (i << NIBBLE_BITS) | i);
|
|
}
|
|
words[0] = KEYS_BLACK;
|
|
words[1] = KEYS_UNLIT;
|
|
words[2] = KEYS_LIT;
|
|
words[3] = KEYS_CURSOR;
|
|
words[KEYS_RED_INDEX] = KEYS_RED;
|
|
}
|
|
|
|
|
|
uint32_t gateLe16(const uint8_t *p) {
|
|
return (uint32_t)p[0] | ((uint32_t)p[1] << BYTE_BITS);
|
|
}
|
|
|
|
|
|
uint32_t gateLe32(const uint8_t *p) {
|
|
return gateLe16(p) | (gateLe16(p + LE16_BYTES) << (LE16_BYTES * BYTE_BITS));
|
|
}
|
|
|
|
|
|
uint32_t gateNearest(const uint8_t *rgb, const uint8_t (*palette)[GATE_RGB_BYTES], uint32_t count, uint32_t *distance) {
|
|
uint32_t best;
|
|
uint32_t bestDistance;
|
|
uint32_t d;
|
|
uint32_t i;
|
|
|
|
best = 0u;
|
|
bestDistance = 0u;
|
|
for (i = 0; i < count; i++) {
|
|
d = gateDistance(rgb, palette[i]);
|
|
if (i == 0u || d < bestDistance) {
|
|
best = i;
|
|
bestDistance = d;
|
|
}
|
|
}
|
|
*distance = bestDistance;
|
|
return best;
|
|
}
|
|
|
|
|
|
void gateRgb12(uint32_t word, uint8_t *rgb) {
|
|
rgb[0] = (uint8_t)(((word >> (2u * NIBBLE_BITS)) & NIBBLE_MASK) * NIBBLE_WIDEN);
|
|
rgb[1] = (uint8_t)(((word >> NIBBLE_BITS) & NIBBLE_MASK) * NIBBLE_WIDEN);
|
|
rgb[2] = (uint8_t)((word & NIBBLE_MASK) * NIBBLE_WIDEN);
|
|
}
|
|
|
|
|
|
// The alpha of an icon's picture of fewer than 32 bits, from its AND mask:
|
|
// the last bytes of the picture's, rows padded to 32 bits, bottom row
|
|
// first; a set bit is transparent.
|
|
static void icoAndMask(const uint8_t *data, size_t len, const IcoEntryT *entry, const char *path, GateImageT *img) {
|
|
uint8_t *out;
|
|
size_t rowBytes;
|
|
size_t total;
|
|
size_t maskAt;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
uint8_t bits;
|
|
|
|
rowBytes = (((size_t)img->width + DIB_ROW_BITS - 1u) / DIB_ROW_BITS) * (DIB_ROW_BITS / BYTE_BITS);
|
|
total = rowBytes * img->height;
|
|
if ((size_t)entry->offset + entry->size > len || total > (size_t)entry->offset + entry->size) {
|
|
toolFail("%s: truncated icon mask", path);
|
|
}
|
|
maskAt = (size_t)entry->offset + entry->size - total;
|
|
for (y = 0; y < img->height; y++) {
|
|
for (x = 0; x < img->width; x++) {
|
|
bits = data[maskAt + rowBytes * (img->height - 1u - y) + x / BYTE_BITS];
|
|
out = img->rgba + ((size_t)y * img->width + x) * GATE_RGBA_BYTES;
|
|
out[GATE_ALPHA] = (bits & (TOP_BIT >> (x % BYTE_BITS))) ? 0u : SAMPLE_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// An ICO's picture: the first in the sorted directory side x side at bpp
|
|
// bits (any bits for ICO_ANY_BPP), else (or for side ICO_DEFAULT_SIDE) the
|
|
// first of all.
|
|
static void icoDecode(const uint8_t *data, size_t len, uint32_t side, uint32_t bpp, const char *path, GateImageT *img) {
|
|
IcoEntryT *entries;
|
|
IcoEntryT held;
|
|
const uint8_t *e;
|
|
uint32_t count;
|
|
uint32_t i;
|
|
uint32_t j;
|
|
uint32_t chosen;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
DibT dib;
|
|
|
|
count = gateLe16(data + ICO_COUNT_AT);
|
|
if (count == 0u || len < ICO_DIR_BYTES + (size_t)count * ICO_ENTRY_BYTES) {
|
|
toolFail("%s: truncated icon directory", path);
|
|
}
|
|
entries = (IcoEntryT *)malloc((size_t)count * sizeof(IcoEntryT));
|
|
if (entries == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
e = data + ICO_DIR_BYTES + (size_t)i * ICO_ENTRY_BYTES;
|
|
entries[i].width = (e[ICO_ENTRY_W] != 0u) ? e[ICO_ENTRY_W] : ICO_SIDE_ZERO;
|
|
entries[i].height = (e[ICO_ENTRY_H] != 0u) ? e[ICO_ENTRY_H] : ICO_SIDE_ZERO;
|
|
entries[i].bpp = gateLe16(e + ICO_ENTRY_BPP);
|
|
entries[i].colorDepth = icoDepth(entries[i].bpp, e[ICO_ENTRY_COLORS]);
|
|
entries[i].size = gateLe32(e + ICO_ENTRY_SIZE);
|
|
entries[i].offset = gateLe32(e + ICO_ENTRY_OFFSET);
|
|
// An insertion sort, which keeps equals in the directory's order.
|
|
held = entries[i];
|
|
for (j = i; j > 0u && icoEntryBefore(&held, &entries[j - 1u]); j--) {
|
|
entries[j] = entries[j - 1u];
|
|
}
|
|
entries[j] = held;
|
|
}
|
|
chosen = 0u;
|
|
for (i = 0; i < count && side != ICO_DEFAULT_SIDE; i++) {
|
|
if (entries[i].width == side && entries[i].height == side && (bpp == ICO_ANY_BPP || bpp == entries[i].colorDepth)) {
|
|
chosen = i;
|
|
break;
|
|
}
|
|
}
|
|
held = entries[chosen];
|
|
free(entries);
|
|
if (held.offset >= len) {
|
|
toolFail("%s: truncated icon", path);
|
|
}
|
|
if (magic(data + held.offset, len - held.offset, PNG_MAGIC, PNG_MAGIC_BYTES)) {
|
|
pngDecode(data + held.offset, len - held.offset, path, img);
|
|
return;
|
|
}
|
|
dibRead(data, len, held.offset, 0u, true, path, &dib);
|
|
dibDecode(data, len, &dib, path, img);
|
|
if (held.bpp != BITS_32) {
|
|
icoAndMask(data, len, &held, path, img);
|
|
return;
|
|
}
|
|
for (y = 0; y < img->height; y++) {
|
|
for (x = 0; x < img->width; x++) {
|
|
img->rgba[((size_t)y * img->width + x) * GATE_RGBA_BYTES + GATE_ALPHA] = data[dib.pixelsAt + ((size_t)(img->height - 1u - y) * img->width + x) * GATE_RGBA_BYTES + GATE_ALPHA];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// An ICO entry's color depth: its bits a pixel, or else the bits its
|
|
// color count needs, or else ICO_DEPTH_NONE.
|
|
static uint32_t icoDepth(uint32_t bpp, uint32_t colors) {
|
|
uint32_t depth;
|
|
|
|
if (bpp != 0u) {
|
|
return bpp;
|
|
}
|
|
depth = 0u;
|
|
while (depth < BITS_32 && (1u << depth) < colors) {
|
|
depth++;
|
|
}
|
|
return (depth != 0u) ? depth : ICO_DEPTH_NONE;
|
|
}
|
|
|
|
|
|
// Whether entry a goes before b: bigger first, then fewer colors.
|
|
static bool icoEntryBefore(const IcoEntryT *a, const IcoEntryT *b) {
|
|
uint32_t squareA;
|
|
uint32_t squareB;
|
|
|
|
squareA = a->width * a->height;
|
|
squareB = b->width * b->height;
|
|
if (squareA != squareB) {
|
|
return squareA > squareB;
|
|
}
|
|
return a->colorDepth < b->colorDepth;
|
|
}
|
|
|
|
|
|
// The icon's opaque pixel whose color (within tolerance) the screen holds
|
|
// least, the first of equals: the screen's colors are counted once, sorted.
|
|
static uint32_t iconAnchor(const GateImageT *screen, const GateIconT *icon, uint32_t tolerance) {
|
|
uint32_t *packed;
|
|
uint32_t *counts;
|
|
const uint8_t *pixel;
|
|
uint8_t rgb[GATE_RGB_BYTES];
|
|
size_t pixelCount;
|
|
size_t i;
|
|
uint32_t unique;
|
|
uint32_t u;
|
|
uint32_t p;
|
|
uint32_t count;
|
|
uint32_t bestCount;
|
|
uint32_t anchor;
|
|
|
|
pixelCount = (size_t)screen->width * screen->height;
|
|
packed = (uint32_t *)malloc((pixelCount + 1u) * sizeof(uint32_t));
|
|
counts = (uint32_t *)malloc((pixelCount + 1u) * sizeof(uint32_t));
|
|
if (packed == NULL || counts == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
for (i = 0; i < pixelCount; i++) {
|
|
pixel = screen->rgba + i * GATE_RGBA_BYTES;
|
|
packed[i] = ((uint32_t)pixel[0] << RED_SHIFT) | ((uint32_t)pixel[1] << GREEN_SHIFT) | pixel[2];
|
|
}
|
|
qsort(packed, pixelCount, sizeof(uint32_t), packedCompare);
|
|
unique = 0u;
|
|
for (i = 0; i < pixelCount; i++) {
|
|
if (unique > 0u && packed[unique - 1u] == packed[i]) {
|
|
counts[unique - 1u]++;
|
|
} else {
|
|
packed[unique] = packed[i];
|
|
counts[unique] = 1u;
|
|
unique++;
|
|
}
|
|
}
|
|
anchor = 0u;
|
|
bestCount = 0u;
|
|
for (p = 0; p < icon->count; p++) {
|
|
count = 0u;
|
|
for (u = 0; u < unique; u++) {
|
|
rgb[0] = (uint8_t)(packed[u] >> RED_SHIFT);
|
|
rgb[1] = (uint8_t)(packed[u] >> GREEN_SHIFT);
|
|
rgb[2] = (uint8_t)packed[u];
|
|
if (within(rgb, icon->pixels[p].rgb, tolerance)) {
|
|
count += counts[u];
|
|
}
|
|
}
|
|
if (p == 0u || count < bestCount) {
|
|
anchor = p;
|
|
bestCount = count;
|
|
}
|
|
}
|
|
free(packed);
|
|
free(counts);
|
|
return anchor;
|
|
}
|
|
|
|
|
|
// Whether the screen shows the icon with its top left at (left, top): it
|
|
// lies wholly on the screen and every opaque pixel is within tolerance.
|
|
static bool iconAt(const GateImageT *screen, const GateIconT *icon, uint32_t tolerance, uint32_t left, uint32_t top) {
|
|
const GateIconPixelT *q;
|
|
uint32_t p;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
|
|
if ((uint64_t)top + icon->height > screen->height || (uint64_t)left + icon->width > screen->width) {
|
|
return false;
|
|
}
|
|
for (p = 0; p < icon->count; p++) {
|
|
q = &icon->pixels[p];
|
|
x = left + q->x;
|
|
y = top + q->y;
|
|
if (x >= screen->width || y >= screen->height || !within(gateImagePixel(screen, x, y), q->rgb, tolerance)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static void imageAlloc(GateImageT *img, uint32_t width, uint32_t height) {
|
|
img->width = width;
|
|
img->height = height;
|
|
img->rgba = (uint8_t *)calloc((size_t)width * height + 1u, GATE_RGBA_BYTES);
|
|
if (img->rgba == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
}
|
|
|
|
|
|
static bool magic(const uint8_t *data, size_t len, const char *bytes, size_t count) {
|
|
return len >= count && memcmp(data, bytes, count) == 0;
|
|
}
|
|
|
|
|
|
static int packedCompare(const void *a, const void *b) {
|
|
uint32_t x;
|
|
uint32_t y;
|
|
|
|
x = *(const uint32_t *)a;
|
|
y = *(const uint32_t *)b;
|
|
return (x > y) - (x < y);
|
|
}
|
|
|
|
|
|
// A PNG through toolPng, made RGBA.
|
|
static void pngDecode(const uint8_t *data, size_t len, const char *path, GateImageT *img) {
|
|
ToolImageT decoded;
|
|
ToolImageT rgba;
|
|
char err[TOOL_PNG_ERROR_BYTES];
|
|
|
|
if (!toolPngDecode(data, len, &decoded, err)) {
|
|
toolFail("%s: %s", path, err);
|
|
}
|
|
if (!toolImageToRgba(&decoded, &rgba)) {
|
|
toolFail("out of memory");
|
|
}
|
|
toolImageFree(&decoded);
|
|
img->width = rgba.width;
|
|
img->height = rgba.height;
|
|
img->rgba = rgba.pixels;
|
|
}
|
|
|
|
|
|
// Whether every gun of a is within tolerance of b's.
|
|
static bool within(const uint8_t *a, const uint8_t *b, uint32_t tolerance) {
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < GATE_RGB_BYTES; i++) {
|
|
if ((uint32_t)abs((int32_t)a[i] - (int32_t)b[i]) > tolerance) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|