// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // iconBake.c - a program's icon, from one picture, for each system that // shows programs by an icon. // // The picture is a PNG, best square, with transparency; it is fitted into // each icon's size keeping its shape, and centered. A picture of 64 pixels or // less is taken for pixel art and enlarged by whole pixels; anything else is // resampled smoothly (Lanczos, three lobes, on color weighted by its // opacity, in fixed point as Pillow does it). Where an icon has few colors // it takes the nearest (the 8-bit Windows icon dithers to its own 256, cut // from the picture's by median cut), and a pixel at least half transparent // is transparent. The formats each system takes are told at their bake // functions; usage() lists the command lines. #include #include #include #include #include "bePalette.h" #include "toolBuf.h" #include "toolFile.h" #include "toolPng.h" #define TOOL_NAME "iconBake" #define USAGE_EXIT 2 #define RGB_BYTES 3u #define RGBA_BYTES 4u #define ALPHA 3u #define SAMPLE_MAX 255u #define PALETTE_SIZE 256u #define BYTE_BITS 8u #define NIBBLE_BITS 4u // A picture this small or smaller is pixel art: enlarged by whole pixels. #define PIXEL_ART_MAX 64u #define ALPHA_OPAQUE_MIN 128u // The biggest icon side asked for on the command line (a sanity bound). #define ICON_SIDE_MAX 4096u // Resampling: Lanczos with three lobes, its taps in fixed point with 22 // fraction bits (32 less a sample's 8 and 2 of headroom), as Pillow's // resize does it. #define LANCZOS_LOBES 3.0 #define PI 3.14159265358979323846 #define TAP_BITS 22 #define TAP_ONE (1 << TAP_BITS) #define TAP_HALF (1 << (TAP_BITS - 1)) // Compositing over a background, as Pillow's alpha_composite: 7 fraction // bits, a division by 255 done as two shifts. #define BLEND_BITS 7u #define BLEND_HALF 0x80u #define DIV255_SHIFT 8u // The nearest color is looked up by the RGB cell a color falls in, 6 bits // a channel, measured from the cell's low corner (as Pillow's palette cache // does it, so the same colors are chosen). #define CELL_SHIFT 2u #define CELL_SIDE 64u #define CELL_LOW_MASK 0xFCu #define CELL_COUNT (CELL_SIDE * CELL_SIDE * CELL_SIDE) #define CELL_EMPTY (-1) // Floyd-Steinberg error diffusion: 7/16 right, 3/16 below left, 5/16 below, // 1/16 below right. #define FS_DIVISOR 16 // Median cut: the 8-bit Windows icon's 256 colors are black (behind the // AND mask) and up to 255 of the picture's own. #define MEDIAN_COLORS 255u #define RGB_RED_SHIFT 16u #define RGB_GREEN_SHIFT 8u #define KEY_SHIFT 24u // Windows: the sizes Win32 icons come in, and the 16 colors of a Windows // 3.x icon (the VGA's, in Windows' order). #define ICO_SIZE_COUNT 3u #define ICO16_SIZE 32u #define ICO16_COLORS 16u #define ICO_TYPE 1u #define DIB_HEADER_BYTES 40u #define ICONDIR_BYTES 6u #define ICONDIRENTRY_BYTES 16u #define ICO_TRUE_COLOR_BITS 32u #define ICO_256_BITS 8u #define ICO_16_BITS 4u #define DIB_ROW_ALIGN 4u #define ENTRY_SIZE_WRAP 256u // DVX: its icons' size and the gray they sit on (its own apps'); the // 24-bit BMP DVX reads, at 96 dots an inch. #define DVX_SIZE 32u #define BMP_FILE_HEADER_BYTES 14u #define BMP_BITS 24u #define BMP_PIXELS_PER_METER 3780u // The Amiga: a Workbench tool icon (struct DiskObject, workbench.h) of // AMIGA_W x AMIGA_H Workbench pixels, which are twice as tall as wide, in // the four colors Workbench 2.0 on has (gray, black, white, blue). #define AMIGA_W 48u #define AMIGA_H 24u #define AMIGA_DEPTH 2u #define AMIGA_COLORS 4u #define AMIGA_WORD_PIXELS 16u #define AMIGA_WORD_LEFT_BIT 0x8000u #define WB_DISKMAGIC 0xE310u #define WB_DISKVERSION 1u #define WB_DISKREVISION 1u #define WBTOOL 3u #define NO_ICON_POSITION 0x80000000u #define GFLG_GADGIMAGE 0x0004u #define GACT_RELVERIFY_IMMEDIATE 0x0003u #define GTYP_BOOLGADGET 0x0001u // Any non-zero pointer: on disk it says the structure follows. #define PRESENT 1u // BeOS and Haiku: 32x32 and 16x16 icons in the system palette (bePalette.h; // index 255 is transparent, B_TRANSPARENT_MAGIC_CMAP8). #define BE_LARGE 32u #define BE_SMALL 16u #define BE_TRANSPARENT 255u // Haiku's vector icon (HVIF, src/libs/icon/flat_icon in Haiku): the 16x16 // and 32x32 icons' pixels as outlines on its 64-unit grid, a shape a color, // hinted so every edge falls on a pixel. The 16x16 shapes show below 32 // pixels and the 32x32 ones from there up (scale 0.5 is 32 pixels; a level // of detail byte is 63.75 to 1), so each size draws exactly its own pixels. // A section holds at most 255 of anything; an icon needing more gets no // vector icon. #define HVIF_MAGIC "ncif" #define HVIF_MAGIC_BYTES 4u #define HVIF_GRID 64u #define HVIF_STYLE_SOLID_COLOR 1u #define HVIF_PATH_CLOSED 0x02u #define HVIF_PATH_NO_CURVES 0x08u #define HVIF_SHAPE_PATH_SOURCE 10u #define HVIF_SHAPE_HINTING 0x04u #define HVIF_SHAPE_LOD_SCALE 0x08u #define HVIF_LOD_SMALL_MIN 0u #define HVIF_LOD_SMALL_MAX 31u #define HVIF_LOD_LARGE_MIN 31u #define HVIF_LOD_LARGE_MAX 255u #define HVIF_COORD_BIAS 32u #define HVIF_MAX_ITEMS 255u #define HVIF_OPAQUE 255u #define HVIF_EDGES_PER_POINT 2u #define HVIF_SIDES_PER_PIXEL 4u // macOS: an app's icon file (.icns) is "icns" and the file's length, then // an element for each size: its type, its length (these 8 bytes included) // and the picture as a PNG. The @2x types are for Retina screens, twice the // pixels of the size they stand for. Sizes past the first at least as big // as the picture add nothing macOS's own enlarging would not, so are left // out, unless it is pixel art, which fit() enlarges sharply. #define ICNS_MAGIC "icns" #define ICNS_TYPE_BYTES 4u #define ICNS_HEADER_BYTES 8u #define ICNS_ELEMENT_COUNT 9u #define ICNS_LARGEST 512u // The Apple IIgs Finder: an icon file (file type $CA; Apple's File Type // Note $CA, "Finder Icons File") in a volume's Icons folder, holding one // icon data record that gives a GS/OS application (file type $B3) of the // program's name its icons, normal and small. Each is a QuickDraw II icon // (Toolbox Reference volume 2, QuickDraw II Auxiliary): iconType, iconSize // (the image's bytes), iconHeight, iconWidth, the image at 4 bits a pixel // (rows of whole bytes, the even pixel in the high nibble), then a mask of // the same shape, non-zero where the icon is opaque. They are color icons // (iconType bit 15), whose colors the Finder leaves alone. The Finder runs // in 640 mode, where QuickDraw draws each 4-bit icon pixel as two screen // pixels, its high two bits then its low two, and in the Finder's palette // the first of the pair is black, red, green, or white and the second // black, blue, yellow, or white (kIigsFirst, kIigsSecond: measured in MAME, // an icon at an even screen x), so an icon pixel is one of 16 colors a // pair makes, seen as their mean. An icon pixel is some 1.2 times as tall // as wide; the big icon is the size of the Finder's own (its folder is 24 // pixels wide). #define IIGS_BIG_W 24u #define IIGS_BIG_H 20u #define IIGS_SMALL_W 12u #define IIGS_SMALL_H 10u #define IIGS_PIXEL_ASPECT 1.2 #define IIGS_VALUES 16u #define IIGS_HALVES 4u #define IIGS_HALF_BITS 2u #define IIGS_HALF_MASK 3u #define IIGS_BLACK 0x0u #define IIGS_WHITE 0xFu #define IIGS_OPAQUE 0xFu #define IIGS_ICON_TYPE_COLOR 0x8000u #define IIGS_ICON_FILE_ID 0x0001u #define IIGS_APPLICATION_TYPE 0x00B3u #define IIGS_FILE_NAME_BYTES 16u #define IIGS_BOSS_BYTES 64u #define IIGS_NAME_BYTES 16u #define IIGS_LENGTH_WORD_BYTES 2u #define ASCII_MAX 0x7Fu // A pixel with less color than this (CIELAB chroma) may be shown in a // gray; a more colorful one keeps a color, or black or white: brown fur // nearest a gray by lightness reads better in the nearest color. #define IIGS_GRAY_CHROMA 12.0 // CIE94 (graphic arts): how much a difference in chroma and in hue counts, // against the color's own chroma. #define CIE94_CHROMA 0.045 #define CIE94_HUE 0.015 // sRGB's transfer curve, and CIELAB's white point (D65) and curve. #define SRGB_LINEAR_MAX 0.04045 #define SRGB_LINEAR_SLOPE 12.92 #define SRGB_OFFSET 0.055 #define SRGB_GAMMA 2.4 #define LAB_EPSILON 0.008856 #define LAB_KAPPA 7.787 #define LAB_L_SCALE 116.0 #define LAB_L_OFFSET 16.0 #define LAB_A_SCALE 500.0 #define LAB_B_SCALE 200.0 // The SDL3 backend's window icon as C: this many bytes a line. #define C_BYTES_PER_LINE 16u #define COUNT_OF(a) (sizeof(a) / sizeof((a)[0])) // The taps one resampling pass uses: for each output sample its first input // sample, how many it takes, and their weights (size a sample). typedef struct TapsT { uint32_t size; uint32_t *first; uint32_t *count; int32_t *weights; } TapsT; // A palette of 256 colors and, by RGB cell, the nearest entry found yet. typedef struct PaletteT { uint8_t rgb[PALETTE_SIZE][RGB_BYTES]; int16_t *cache; } PaletteT; // One of a picture's colors (0xRRGGBB), how many pixels have it, and what // a median cut box sorts it by. typedef struct ShadeT { uint32_t key; uint32_t rgb; uint32_t pixels; } ShadeT; // A median cut box: a run of shades, their pixels, and its widest channel. typedef struct BoxT { uint32_t first; uint32_t count; uint32_t pixels; uint32_t channel; uint32_t range; } BoxT; // A color in CIELAB. typedef struct LabT { double l; double a; double b; } LabT; // One palette picture for the vector icon: its side, its pixels (palette // indices, BE_TRANSPARENT clear), and the scales it shows at. typedef struct HvifSizeT { uint32_t size; const uint8_t *pixels; uint32_t lodMin; uint32_t lodMax; } HvifSizeT; typedef void (*BakeFnT)(const ToolImageT *picture, char **args); typedef struct BakeT { const char *name; BakeFnT bake; uint32_t args; } BakeT; typedef struct IcnsElementT { const char *type; uint32_t size; } IcnsElementT; static void alphaComposite(ToolImageT *dst, const ToolImageT *src); static void andMask(const ToolImageT *image, ToolBufT *out); static void bakeBe(const ToolImageT *picture, char **args); static void bakeBmp(const ToolImageT *picture, char **args); static void bakeC(const ToolImageT *picture, char **args); static void bakeIcns(const ToolImageT *picture, char **args); static void bakeIco(const ToolImageT *picture, char **args); static void bakeIco16(const ToolImageT *picture, char **args); static void bakeIigs(const ToolImageT *picture, char **args); static void bakeInfo(const ToolImageT *picture, char **args); static void bakePng(const ToolImageT *picture, char **args); static void boxMeasure(BoxT *box, const ShadeT *shades); static double chroma(LabT color); static double cie94(LabT reference, LabT sample); static uint8_t clampSample(int32_t value); static void ditherFloydSteinberg(const ToolImageT *flat, PaletteT *palette, uint8_t *indices); static void enlargeWhole(const ToolImageT *src, uint32_t scale, ToolImageT *dst); static void fit(const ToolImageT *picture, uint32_t w, uint32_t h, ToolImageT *out); static void flatten(const ToolImageT *image, const uint8_t background[RGB_BYTES], ToolImageT *flat); static bool hvif(const HvifSizeT *sizes, uint32_t sizeCount, ToolBufT *out); static bool hvifContours(const uint8_t *pixels, uint32_t size, uint8_t color, uint32_t units, ToolBufT *paths, uint32_t *pathCount); static void icoFile(const uint32_t *sides, const uint32_t *colorCounts, const uint32_t *bitCounts, const ToolBufT *images, uint32_t count, const char *path); static void icoImage(const ToolImageT *image, uint32_t bitCount, const uint8_t (*colors)[RGB_BYTES], uint32_t colorCount, ToolBufT *out); static void iigsColors(LabT colors[IIGS_VALUES], uint32_t keeps[IIGS_VALUES], uint32_t *keepCount); static void iigsIcon(const ToolImageT *picture, uint32_t w, uint32_t h, ToolBufT *out); static void imageCopy(const ToolImageT *src, ToolImageT *dst); static void imageNew(ToolImageT *img, uint32_t w, uint32_t h); static LabT lab(const double rgb[RGB_BYTES]); static double lanczos(double x); static double linear(double value); static void medianCut(const ToolImageT *image, uint8_t palette[PALETTE_SIZE][RGB_BYTES]); static uint8_t nearestColor(PaletteT *palette, uint32_t r, uint32_t g, uint32_t b); static bool opaque(const ToolImageT *image, size_t pixel); static int packedCompare(const void *a, const void *b); static void paletteFree(PaletteT *palette); static void paletteInit(PaletteT *palette, const uint8_t (*colors)[RGB_BYTES], uint32_t count); static uint32_t parseSide(const char *text, size_t len, const char *what); static void pascal(ToolBufT *out, const char *text, uint32_t size); static void premultiply(const ToolImageT *src, ToolImageT *dst); static void resamplePass(const uint8_t *in, uint8_t *out, const TapsT *taps, uint32_t outCount, uint32_t lines, size_t inStep, size_t inLineStep, size_t outStep, size_t outLineStep); static void resizeLanczos(const ToolImageT *src, uint32_t w, uint32_t h, ToolImageT *dst); static int shadeCompare(const void *a, const void *b); static double sinc(double x); static void tapsBuild(TapsT *taps, uint32_t inSize, uint32_t outSize); static void tapsFree(TapsT *taps); static void toColors(const ToolImageT *image, const uint8_t (*colors)[RGB_BYTES], uint32_t count, bool dither, const uint8_t background[RGB_BYTES], uint8_t *indices); static void unpremultiply(const ToolImageT *src, ToolImageT *dst); static void usage(void); static void writeOut(const char *path, const uint8_t *data, size_t len); static const BakeT kBakes[] = { { "ico", bakeIco, 0u }, { "ico16", bakeIco16, 0u }, { "bmp", bakeBmp, 0u }, { "png", bakePng, 1u }, { "info", bakeInfo, 1u }, { "be", bakeBe, 2u }, { "c", bakeC, 1u }, { "icns", bakeIcns, 0u }, { "iigs", bakeIigs, 1u } }; static const uint32_t kIcoSizes[ICO_SIZE_COUNT] = { 16u, 32u, 48u }; static const uint8_t kVga16[ICO16_COLORS][RGB_BYTES] = { { 0, 0, 0 }, { 128, 0, 0 }, { 0, 128, 0 }, { 128, 128, 0 }, { 0, 0, 128 }, { 128, 0, 128 }, { 0, 128, 128 }, { 192, 192, 192 }, { 128, 128, 128 }, { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 0 }, { 0, 0, 255 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 } }; static const uint8_t kBlack[RGB_BYTES] = { 0, 0, 0 }; static const uint8_t kWhite[RGB_BYTES] = { 255, 255, 255 }; static const uint8_t kDvxGray[RGB_BYTES] = { 192, 192, 192 }; static const uint8_t kAmigaColors[AMIGA_COLORS][RGB_BYTES] = { { 170, 170, 170 }, { 0, 0, 0 }, { 255, 255, 255 }, { 102, 136, 187 } }; static const IcnsElementT kIcnsElements[ICNS_ELEMENT_COUNT] = { { "icp4", 16u }, { "icp5", 32u }, { "ic11", 32u }, { "ic12", 64u }, { "ic07", 128u }, { "ic13", 256u }, { "ic08", 256u }, { "ic14", 512u }, { "ic09", 512u } }; static const uint8_t kIigsFirst[IIGS_HALVES][RGB_BYTES] = { { 0, 0, 0 }, { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 255 } }; static const uint8_t kIigsSecond[IIGS_HALVES][RGB_BYTES] = { { 0, 0, 0 }, { 0, 0, 255 }, { 255, 255, 0 }, { 255, 255, 255 } }; // sRGB to CIE XYZ (D65), and CIELAB's white point. static const double kXyzFromRgb[RGB_BYTES][RGB_BYTES] = { { 0.4124, 0.3576, 0.1805 }, { 0.2126, 0.7152, 0.0722 }, { 0.0193, 0.1192, 0.9505 } }; static const double kLabWhite[RGB_BYTES] = { 0.95047, 1.0, 1.08883 }; static const char *const kUsage[] = { "usage:", " iconBake ico PICTURE OUT.ico Windows 95 on (Win32): 16, 32 and", " 48 pixels, each in 256 colors and", " in 32-bit", " iconBake ico16 PICTURE OUT.ico Windows 3.x (Win16): 32x32 in the 16", " VGA colors", " iconBake bmp PICTURE OUT.bmp DVX: 32x32, 24-bit, on DVX's gray", " iconBake png PICTURE OUT.png WxH a picture WxH (the PSP's 144x80", " ICON0, a web page's icon)", " iconBake info PICTURE OUT.info STACK the Amiga's Workbench: a tool's icon", " in its four colors, STACK bytes of", " stack when started from it", " iconBake be PICTURE OUT32 OUT16 OUT.hvif", " BeOS and Haiku: 32x32 and 16x16 in", " the BeOS palette, raw, for xres", " (ICON, MICN), and the 32x32 one as", " Haiku's vector icon (VICN)", " iconBake c PICTURE OUT.c SIZE the SDL3 backend's window icon: SIZE", " x SIZE RGBA as C", " iconBake icns PICTURE OUT.icns macOS: an app's icon, 16 pixels up", " to 512 (or the picture's size), and", " twice that for Retina screens", " iconBake iigs PICTURE OUT NAME the Apple IIgs Finder: an icon file", " ($CA) giving the program NAME its", " icons, in the Finder's 16 colors", "", "PICTURE is a PNG, best square, with transparency. One of 64 pixels or less", "is pixel art, enlarged by whole pixels; anything else is resampled smoothly." }; // src composited over dst (Porter-Duff over), in place, as Pillow's // alpha_composite rounds it. static void alphaComposite(ToolImageT *dst, const ToolImageT *src) { size_t i; size_t pixels; uint8_t *d; const uint8_t *s; uint32_t blend; uint32_t outA255; uint32_t coef1; uint32_t coef2; uint32_t c; uint32_t mix; pixels = (size_t)dst->width * dst->height; for (i = 0; i < pixels; i++) { d = dst->pixels + i * RGBA_BYTES; s = src->pixels + i * RGBA_BYTES; if (s[ALPHA] == 0u) { continue; } blend = (uint32_t)d[ALPHA] * (SAMPLE_MAX - s[ALPHA]); outA255 = (uint32_t)s[ALPHA] * SAMPLE_MAX + blend; coef1 = (uint32_t)s[ALPHA] * SAMPLE_MAX * SAMPLE_MAX * (1u << BLEND_BITS) / outA255; coef2 = SAMPLE_MAX * (1u << BLEND_BITS) - coef1; for (c = 0; c < RGB_BYTES; c++) { mix = s[c] * coef1 + d[c] * coef2 + (BLEND_HALF << BLEND_BITS); d[c] = (uint8_t)((((mix >> DIV255_SHIFT) + mix) >> DIV255_SHIFT) >> BLEND_BITS); } mix = outA255 + BLEND_HALF; d[ALPHA] = (uint8_t)(((mix >> DIV255_SHIFT) + mix) >> DIV255_SHIFT); } } // A Windows icon's AND mask: 1 where transparent, bottom-up rows of 1 bit a // pixel padded to DWORDs. static void andMask(const ToolImageT *image, ToolBufT *out) { uint32_t x; uint32_t y; uint32_t bits; size_t rowStart; for (y = image->height; y-- > 0u;) { rowStart = out->len; bits = 0u; for (x = 0; x < image->width; x++) { bits = (bits << 1) | (opaque(image, (size_t)y * image->width + x) ? 0u : 1u); if (x % BYTE_BITS == BYTE_BITS - 1u) { toolBufByte(out, bits); bits = 0u; } } if (image->width % BYTE_BITS != 0u) { toolBufByte(out, bits << (BYTE_BITS - image->width % BYTE_BITS)); } toolBufZeros(out, (DIB_ROW_ALIGN - (out->len - rowStart) % DIB_ROW_ALIGN) % DIB_ROW_ALIGN); } } // BeOS and Haiku: the 32x32 and 16x16 icons in the BeOS palette, raw, and // both as Haiku's vector icon (empty when HVIF cannot hold it). static void bakeBe(const ToolImageT *picture, char **args) { static const uint32_t sides[] = { BE_LARGE, BE_SMALL }; static const uint32_t lodMin[] = { HVIF_LOD_LARGE_MIN, HVIF_LOD_SMALL_MIN }; static const uint32_t lodMax[] = { HVIF_LOD_LARGE_MAX, HVIF_LOD_SMALL_MAX }; ToolImageT image; uint8_t *pixels[COUNT_OF(sides)]; HvifSizeT sizes[COUNT_OF(sides)]; ToolBufT vector; size_t count; size_t i; uint32_t s; for (s = 0; s < COUNT_OF(sides); s++) { fit(picture, sides[s], sides[s], &image); count = (size_t)sides[s] * sides[s]; pixels[s] = (uint8_t *)malloc(count); if (pixels[s] == NULL) { toolFail("out of memory"); } toColors(&image, kBePalette, BE_TRANSPARENT, false, kBlack, pixels[s]); for (i = 0; i < count; i++) { if (!opaque(&image, i)) { pixels[s][i] = BE_TRANSPARENT; } } writeOut(args[s], pixels[s], count); toolImageFree(&image); sizes[s].size = sides[s]; sizes[s].pixels = pixels[s]; sizes[s].lodMin = lodMin[s]; sizes[s].lodMax = lodMax[s]; } memset(&vector, 0, sizeof(vector)); if (!hvif(sizes, COUNT_OF(sides), &vector)) { fprintf(stderr, "%s: the icon needs more than %u paths or shapes; no vector icon\n", TOOL_NAME, HVIF_MAX_ITEMS); } writeOut(args[COUNT_OF(sides)], vector.data, vector.len); toolBufFree(&vector); for (s = 0; s < COUNT_OF(sides); s++) { free(pixels[s]); } } // DVX: 32x32, 24-bit, on DVX's gray, as a BMP. static void bakeBmp(const ToolImageT *picture, char **args) { ToolImageT image; ToolImageT flat; ToolBufT out; uint32_t stride; uint32_t x; uint32_t y; const uint8_t *p; fit(picture, DVX_SIZE, DVX_SIZE, &image); flatten(&image, kDvxGray, &flat); stride = (DVX_SIZE * RGB_BYTES + DIB_ROW_ALIGN - 1u) / DIB_ROW_ALIGN * DIB_ROW_ALIGN; memset(&out, 0, sizeof(out)); toolBufAdd(&out, "BM", 2u); toolBufLe32(&out, BMP_FILE_HEADER_BYTES + DIB_HEADER_BYTES + stride * DVX_SIZE); toolBufLe32(&out, 0u); toolBufLe32(&out, BMP_FILE_HEADER_BYTES + DIB_HEADER_BYTES); toolBufLe32(&out, DIB_HEADER_BYTES); toolBufLe32(&out, DVX_SIZE); toolBufLe32(&out, DVX_SIZE); toolBufLe16(&out, 1u); toolBufLe16(&out, BMP_BITS); toolBufLe32(&out, 0u); toolBufLe32(&out, stride * DVX_SIZE); toolBufLe32(&out, BMP_PIXELS_PER_METER); toolBufLe32(&out, BMP_PIXELS_PER_METER); toolBufLe32(&out, 0u); toolBufLe32(&out, 0u); for (y = DVX_SIZE; y-- > 0u;) { for (x = 0; x < DVX_SIZE; x++) { p = flat.pixels + ((size_t)y * DVX_SIZE + x) * RGBA_BYTES; toolBufByte(&out, p[2]); toolBufByte(&out, p[1]); toolBufByte(&out, p[0]); } toolBufZeros(&out, stride - DVX_SIZE * RGB_BYTES); } writeOut(args[0], out.data, out.len); toolBufFree(&out); toolImageFree(&flat); toolImageFree(&image); } // The SDL3 backend's window icon: SIZE x SIZE RGBA pixels as C. static void bakeC(const ToolImageT *picture, char **args) { ToolImageT image; uint32_t size; size_t bytes; size_t i; FILE *f; bool ok; size = parseSide(args[1], strlen(args[1]), "SIZE"); fit(picture, size, size, &image); bytes = (size_t)size * size * RGBA_BYTES; f = fopen(args[0], "w"); if (f == NULL) { toolFail("cannot write %s", args[0]); } fprintf(f, "// Generated by tools/iconBake: the program's window icon, %u x %u\n", size, size); fprintf(f, "// RGBA pixels, row after row (src/hosted/sdl3/sdl3Backend.c).\n\n"); fprintf(f, "#include \n\n"); fprintf(f, "const uint16_t jlgHostedIconSize = %uu;\n", size); fprintf(f, "const uint8_t jlgHostedIconRgba[] = {\n"); for (i = 0; i < bytes; i++) { fprintf(f, "%s0x%02X%s", i % C_BYTES_PER_LINE == 0u ? " " : " ", image.pixels[i], (i % C_BYTES_PER_LINE == C_BYTES_PER_LINE - 1u || i == bytes - 1u) ? ",\n" : ","); } fprintf(f, "};\n"); ok = !ferror(f); ok = fclose(f) == 0 && ok; if (!ok) { toolFail("cannot write %s", args[0]); } toolImageFree(&image); } // macOS: each element's size as a PNG, up to the first size at least as big // as the picture (all of them for pixel art). The elements go up in size, // so an element the size of the one before shares its PNG. static void bakeIcns(const ToolImageT *picture, char **args) { uint8_t *png; size_t pngLen; uint32_t pngSize; uint32_t largest; uint32_t longest; uint32_t e; ToolImageT image; ToolBufT elements; ToolBufT out; longest = picture->width > picture->height ? picture->width : picture->height; largest = ICNS_LARGEST; if (longest > PIXEL_ART_MAX) { for (e = 0; e < ICNS_ELEMENT_COUNT; e++) { if (kIcnsElements[e].size >= longest) { largest = kIcnsElements[e].size; break; } } } memset(&elements, 0, sizeof(elements)); png = NULL; pngLen = 0u; pngSize = 0u; for (e = 0; e < ICNS_ELEMENT_COUNT && kIcnsElements[e].size <= largest; e++) { if (kIcnsElements[e].size != pngSize) { free(png); fit(picture, kIcnsElements[e].size, kIcnsElements[e].size, &image); if (!toolPngEncode(&image, &png, &pngLen)) { toolFail("out of memory"); } toolImageFree(&image); pngSize = kIcnsElements[e].size; } toolBufAdd(&elements, kIcnsElements[e].type, ICNS_TYPE_BYTES); toolBufBe32(&elements, (uint32_t)(ICNS_HEADER_BYTES + pngLen)); toolBufAdd(&elements, png, pngLen); } free(png); memset(&out, 0, sizeof(out)); toolBufAdd(&out, ICNS_MAGIC, ICNS_TYPE_BYTES); toolBufBe32(&out, (uint32_t)(ICNS_HEADER_BYTES + elements.len)); toolBufAdd(&out, elements.data, elements.len); writeOut(args[0], out.data, out.len); toolBufFree(&out); toolBufFree(&elements); } // Windows 95 on: 16, 32 and 48 pixels, each in 256 colors (its own, by // median cut) and in 32-bit. static void bakeIco(const ToolImageT *picture, char **args) { ToolImageT image; ToolBufT images[ICO_SIZE_COUNT * 2u]; uint32_t sides[ICO_SIZE_COUNT * 2u]; uint32_t colorCounts[ICO_SIZE_COUNT * 2u]; uint32_t bitCounts[ICO_SIZE_COUNT * 2u]; uint8_t palette[PALETTE_SIZE][RGB_BYTES]; uint32_t s; uint32_t i; memset(images, 0, sizeof(images)); for (s = 0; s < ICO_SIZE_COUNT; s++) { fit(picture, kIcoSizes[s], kIcoSizes[s], &image); medianCut(&image, palette); for (i = s * 2u; i < s * 2u + 2u; i++) { sides[i] = kIcoSizes[s]; colorCounts[i] = 0u; } bitCounts[s * 2u] = ICO_256_BITS; bitCounts[s * 2u + 1u] = ICO_TRUE_COLOR_BITS; icoImage(&image, ICO_256_BITS, (const uint8_t (*)[RGB_BYTES])palette, PALETTE_SIZE, &images[s * 2u]); icoImage(&image, ICO_TRUE_COLOR_BITS, NULL, 0u, &images[s * 2u + 1u]); toolImageFree(&image); } icoFile(sides, colorCounts, bitCounts, images, ICO_SIZE_COUNT * 2u, args[0]); for (i = 0; i < ICO_SIZE_COUNT * 2u; i++) { toolBufFree(&images[i]); } } // Windows 3.x: 32x32 in the 16 VGA colors. static void bakeIco16(const ToolImageT *picture, char **args) { ToolImageT image; ToolBufT data; const uint32_t side = ICO16_SIZE; const uint32_t colorCount = ICO16_COLORS; const uint32_t bitCount = ICO_16_BITS; fit(picture, ICO16_SIZE, ICO16_SIZE, &image); memset(&data, 0, sizeof(data)); icoImage(&image, ICO_16_BITS, kVga16, ICO16_COLORS, &data); icoFile(&side, &colorCount, &bitCount, &data, 1u, args[0]); toolBufFree(&data); toolImageFree(&image); } // The Apple IIgs Finder: an icon file whose one record gives the // application NAME its normal and small icons. The record's length counts // its own length word; a zero length ends the list. static void bakeIigs(const ToolImageT *picture, char **args) { const char *name; ToolBufT record; ToolBufT out; size_t i; name = args[1]; for (i = 0; name[i] != '\0'; i++) { if ((unsigned char)name[i] > ASCII_MAX) { toolFail("NAME '%s' is not ASCII", name); } } memset(&record, 0, sizeof(record)); pascal(&record, "", IIGS_BOSS_BYTES); pascal(&record, name, IIGS_NAME_BYTES); toolBufLe16(&record, IIGS_APPLICATION_TYPE); toolBufLe16(&record, 0u); iigsIcon(picture, IIGS_BIG_W, IIGS_BIG_H, &record); iigsIcon(picture, IIGS_SMALL_W, IIGS_SMALL_H, &record); memset(&out, 0, sizeof(out)); toolBufLe32(&out, 0u); toolBufLe16(&out, IIGS_ICON_FILE_ID); toolBufLe32(&out, 0u); pascal(&out, name, IIGS_FILE_NAME_BYTES); toolBufLe16(&out, (uint32_t)(IIGS_LENGTH_WORD_BYTES + record.len)); toolBufAdd(&out, record.data, record.len); toolBufLe16(&out, 0u); writeOut(args[0], out.data, out.len); toolBufFree(&out); toolBufFree(&record); } // The Amiga: a WBTOOL DiskObject: the header, its gadget, the image (struct // Image and its planes, color 0 where transparent) and nothing more. // Workbench's gray is the background alone: the picture is drawn in the // other three. static void bakeInfo(const ToolImageT *picture, char **args) { ToolImageT tall; ToolImageT image; ToolBufT out; uint8_t indices[AMIGA_W * AMIGA_H]; uint32_t rowWords; uint32_t plane; uint32_t x; uint32_t y; uint32_t word; uint32_t bits; long stack; size_t i; if (!toolParseInt(args[1], strlen(args[1]), &stack) || stack < 0 || (unsigned long)stack > UINT32_MAX) { toolFail("bad STACK '%s'", args[1]); } fit(picture, AMIGA_W, AMIGA_H * 2u, &tall); resizeLanczos(&tall, AMIGA_W, AMIGA_H, &image); toColors(&image, kAmigaColors + 1, AMIGA_COLORS - 1u, false, kWhite, indices); for (i = 0; i < (size_t)AMIGA_W * AMIGA_H; i++) { indices[i] = opaque(&image, i) ? (uint8_t)(indices[i] + 1u) : 0u; } memset(&out, 0, sizeof(out)); toolBufBe16(&out, WB_DISKMAGIC); toolBufBe16(&out, WB_DISKVERSION); // struct Gadget do_Gadget: NextGadget, LeftEdge, TopEdge, Width, // Height, Flags, Activation, GadgetType, GadgetRender, SelectRender, // GadgetText, MutualExclude, SpecialInfo, GadgetID, UserData (the // revision). toolBufBe32(&out, 0u); toolBufBe16(&out, 0u); toolBufBe16(&out, 0u); toolBufBe16(&out, AMIGA_W); toolBufBe16(&out, AMIGA_H + 1u); toolBufBe16(&out, GFLG_GADGIMAGE); toolBufBe16(&out, GACT_RELVERIFY_IMMEDIATE); toolBufBe16(&out, GTYP_BOOLGADGET); toolBufBe32(&out, PRESENT); toolBufBe32(&out, 0u); toolBufBe32(&out, 0u); toolBufBe32(&out, 0u); toolBufBe32(&out, 0u); toolBufBe16(&out, 0u); toolBufBe32(&out, WB_DISKREVISION); // do_Type, a pad byte, do_DefaultTool, do_ToolTypes, do_CurrentX, // do_CurrentY, do_DrawerData, do_ToolWindow, do_StackSize. toolBufByte(&out, WBTOOL); toolBufByte(&out, 0u); toolBufBe32(&out, 0u); toolBufBe32(&out, 0u); toolBufBe32(&out, NO_ICON_POSITION); toolBufBe32(&out, NO_ICON_POSITION); toolBufBe32(&out, 0u); toolBufBe32(&out, 0u); toolBufBe32(&out, (uint32_t)stack); // struct Image: LeftEdge, TopEdge, Width, Height, Depth, ImageData, // PlanePick, PlaneOnOff, NextImage. toolBufBe16(&out, 0u); toolBufBe16(&out, 0u); toolBufBe16(&out, AMIGA_W); toolBufBe16(&out, AMIGA_H); toolBufBe16(&out, AMIGA_DEPTH); toolBufBe32(&out, PRESENT); toolBufByte(&out, (1u << AMIGA_DEPTH) - 1u); toolBufByte(&out, 0u); toolBufBe32(&out, 0u); rowWords = (AMIGA_W + AMIGA_WORD_PIXELS - 1u) / AMIGA_WORD_PIXELS; for (plane = 0; plane < AMIGA_DEPTH; plane++) { for (y = 0; y < AMIGA_H; y++) { for (word = 0; word < rowWords; word++) { bits = 0u; for (x = word * AMIGA_WORD_PIXELS; x < (word + 1u) * AMIGA_WORD_PIXELS && x < AMIGA_W; x++) { if ((indices[y * AMIGA_W + x] & (1u << plane)) != 0u) { bits |= AMIGA_WORD_LEFT_BIT >> (x % AMIGA_WORD_PIXELS); } } toolBufBe16(&out, bits); } } } writeOut(args[0], out.data, out.len); toolBufFree(&out); toolImageFree(&image); toolImageFree(&tall); } // A picture WxH (the PSP's 144x80 ICON0, a web page's icon). static void bakePng(const ToolImageT *picture, char **args) { const char *spec; const char *x; uint32_t w; uint32_t h; ToolImageT image; spec = args[1]; x = strpbrk(spec, "xX"); if (x == NULL || strpbrk(x + 1, "xX") != NULL) { toolFail("bad size '%s', need 'WxH'", spec); } w = parseSide(spec, (size_t)(x - spec), "width"); h = parseSide(x + 1, strlen(x + 1), "height"); fit(picture, w, h, &image); if (!toolPngSave(args[0], &image)) { toolFail("cannot write %s", args[0]); } toolImageFree(&image); } // A median cut box's pixels and widest channel (the first of equals). static void boxMeasure(BoxT *box, const ShadeT *shades) { uint32_t lo[RGB_BYTES]; uint32_t hi[RGB_BYTES]; uint32_t i; uint32_t c; uint32_t v; for (c = 0; c < RGB_BYTES; c++) { lo[c] = SAMPLE_MAX; hi[c] = 0u; } box->pixels = 0u; for (i = box->first; i < box->first + box->count; i++) { box->pixels += shades[i].pixels; for (c = 0; c < RGB_BYTES; c++) { v = (shades[i].rgb >> (RGB_RED_SHIFT - c * BYTE_BITS)) & SAMPLE_MAX; if (v < lo[c]) { lo[c] = v; } if (v > hi[c]) { hi[c] = v; } } } box->channel = 0u; box->range = hi[0] - lo[0]; for (c = 1; c < RGB_BYTES; c++) { if (hi[c] - lo[c] > box->range) { box->channel = c; box->range = hi[c] - lo[c]; } } } static double chroma(LabT color) { return hypot(color.a, color.b); } // CIE94's color difference of sample from reference. static double cie94(LabT reference, LabT sample) { double c1; double dc; double dh2; double dl; double da; double db; double sc; double sh; c1 = chroma(reference); dc = c1 - chroma(sample); dl = reference.l - sample.l; da = reference.a - sample.a; db = reference.b - sample.b; dh2 = da * da + db * db - dc * dc; if (dh2 < 0.0) { dh2 = 0.0; } sc = dc / (1.0 + CIE94_CHROMA * c1); sh = 1.0 + CIE94_HUE * c1; return sqrt(dl * dl + sc * sc + dh2 / (sh * sh)); } // A fixed-point resampling sum (TAP_BITS of fraction) as a sample. static uint8_t clampSample(int32_t value) { if (value < 0) { return 0u; } value >>= TAP_BITS; return value > (int32_t)SAMPLE_MAX ? (uint8_t)SAMPLE_MAX : (uint8_t)value; } // flat's pixels as palette indices, dithered Floyd-Steinberg in integers: // each pixel takes the error carried to it (sixteenths, truncated), // clipped, and passes on its own. (Pillow's carries blue's error below the // last pixel of a row in all three channels; this carries each its own.) static void ditherFloydSteinberg(const ToolImageT *flat, PaletteT *palette, uint8_t *indices) { int32_t *errors; int32_t *e; int32_t err[RGB_BYTES]; int32_t below[RGB_BYTES]; int32_t belowRight[RGB_BYTES]; int32_t value[RGB_BYTES]; int32_t d2; int32_t self; uint32_t x; uint32_t y; uint32_t c; uint8_t index; const uint8_t *in; errors = (int32_t *)calloc(((size_t)flat->width + 1u) * RGB_BYTES, sizeof(int32_t)); if (errors == NULL) { toolFail("out of memory"); } for (y = 0; y < flat->height; y++) { e = errors; for (c = 0; c < RGB_BYTES; c++) { err[c] = 0; below[c] = 0; belowRight[c] = 0; } for (x = 0; x < flat->width; x++) { in = flat->pixels + ((size_t)y * flat->width + x) * RGBA_BYTES; for (c = 0; c < RGB_BYTES; c++) { value[c] = (int32_t)in[c] + (err[c] + e[RGB_BYTES + c]) / FS_DIVISOR; if (value[c] < 0) { value[c] = 0; } else if (value[c] > (int32_t)SAMPLE_MAX) { value[c] = (int32_t)SAMPLE_MAX; } } index = nearestColor(palette, (uint32_t)value[0], (uint32_t)value[1], (uint32_t)value[2]); indices[(size_t)y * flat->width + x] = index; for (c = 0; c < RGB_BYTES; c++) { self = value[c] - (int32_t)palette->rgb[index][c]; d2 = self + self; e[c] = self + d2 + below[c]; below[c] = self + d2 + d2 + belowRight[c]; belowRight[c] = self; err[c] = self + d2 + d2 + d2; } e += RGB_BYTES; } for (c = 0; c < RGB_BYTES; c++) { e[c] = below[c]; } } free(errors); } // src enlarged scale whole pixels a pixel. static void enlargeWhole(const ToolImageT *src, uint32_t scale, ToolImageT *dst) { uint32_t x; uint32_t y; imageNew(dst, src->width * scale, src->height * scale); for (y = 0; y < dst->height; y++) { for (x = 0; x < dst->width; x++) { memcpy(dst->pixels + ((size_t)y * dst->width + x) * RGBA_BYTES, src->pixels + ((size_t)(y / scale) * src->width + x / scale) * RGBA_BYTES, RGBA_BYTES); } } } // picture fitted into w x h, its shape kept, centered on transparency. Its // sized copy never outgrows w x h (the scale is the smaller ratio, rounded // to the nearest pixel), so the centering offsets are not negative. static void fit(const ToolImageT *picture, uint32_t w, uint32_t h, ToolImageT *out) { double scale; double sw; double sh; uint32_t longest; uint32_t y; uint32_t ox; uint32_t oy; ToolImageT sized; scale = fmin((double)w / (double)picture->width, (double)h / (double)picture->height); longest = picture->width > picture->height ? picture->width : picture->height; if (longest <= PIXEL_ART_MAX && scale >= 2.0) { enlargeWhole(picture, (uint32_t)scale, &sized); } else { sw = nearbyint((double)picture->width * scale); sh = nearbyint((double)picture->height * scale); resizeLanczos(picture, sw < 1.0 ? 1u : (uint32_t)sw, sh < 1.0 ? 1u : (uint32_t)sh, &sized); } imageNew(out, w, h); ox = (w - sized.width) / 2u; oy = (h - sized.height) / 2u; for (y = 0; y < sized.height; y++) { memcpy(out->pixels + ((size_t)(oy + y) * w + ox) * RGBA_BYTES, sized.pixels + (size_t)y * sized.width * RGBA_BYTES, (size_t)sized.width * RGBA_BYTES); } toolImageFree(&sized); } // image over an opaque background color. static void flatten(const ToolImageT *image, const uint8_t background[RGB_BYTES], ToolImageT *flat) { size_t i; size_t pixels; imageNew(flat, image->width, image->height); pixels = (size_t)image->width * image->height; for (i = 0; i < pixels; i++) { memcpy(flat->pixels + i * RGBA_BYTES, background, RGB_BYTES); flat->pixels[i * RGBA_BYTES + ALPHA] = (uint8_t)SAMPLE_MAX; } alphaComposite(flat, image); } // An HVIF of the palette pictures in sizes: each color's outlines a closed // path each, a shape a color and size, hinted, and shown only at the // scales [lodMin, lodMax]. False, out untouched, when that is more of // anything than HVIF holds. static bool hvif(const HvifSizeT *sizes, uint32_t sizeCount, ToolBufT *out) { uint32_t styles[HVIF_MAX_ITEMS]; uint32_t styleCount; uint32_t pathCount; uint32_t shapeCount; uint32_t style; uint32_t first; uint32_t rgb; uint32_t s; uint32_t color; uint32_t i; bool present[PALETTE_SIZE]; bool ok; ToolBufT paths; ToolBufT shapes; memset(&paths, 0, sizeof(paths)); memset(&shapes, 0, sizeof(shapes)); styleCount = 0u; pathCount = 0u; shapeCount = 0u; ok = true; for (s = 0; s < sizeCount && ok; s++) { memset(present, 0, sizeof(present)); for (i = 0; i < sizes[s].size * sizes[s].size; i++) { present[sizes[s].pixels[i]] = true; } for (color = 0; color < BE_TRANSPARENT && ok; color++) { if (!present[color]) { continue; } rgb = ((uint32_t)kBePalette[color][0] << RGB_RED_SHIFT) | ((uint32_t)kBePalette[color][1] << RGB_GREEN_SHIFT) | kBePalette[color][2]; for (style = 0; style < styleCount && styles[style] != rgb; style++) { } if (style == styleCount) { if (styleCount == HVIF_MAX_ITEMS) { ok = false; break; } styles[styleCount++] = rgb; } first = pathCount; ok = hvifContours(sizes[s].pixels, sizes[s].size, (uint8_t)color, HVIF_GRID / sizes[s].size, &paths, &pathCount) && pathCount <= HVIF_MAX_ITEMS && shapeCount < HVIF_MAX_ITEMS; if (!ok) { break; } toolBufByte(&shapes, HVIF_SHAPE_PATH_SOURCE); toolBufByte(&shapes, style); toolBufByte(&shapes, pathCount - first); for (i = first; i < pathCount; i++) { toolBufByte(&shapes, i); } toolBufByte(&shapes, HVIF_SHAPE_HINTING | HVIF_SHAPE_LOD_SCALE); toolBufByte(&shapes, sizes[s].lodMin); toolBufByte(&shapes, sizes[s].lodMax); shapeCount++; } } if (ok) { toolBufAdd(out, HVIF_MAGIC, HVIF_MAGIC_BYTES); toolBufByte(out, styleCount); for (style = 0; style < styleCount; style++) { toolBufByte(out, HVIF_STYLE_SOLID_COLOR); toolBufByte(out, styles[style] >> RGB_RED_SHIFT); toolBufByte(out, (styles[style] >> RGB_GREEN_SHIFT) & SAMPLE_MAX); toolBufByte(out, styles[style] & SAMPLE_MAX); toolBufByte(out, HVIF_OPAQUE); } toolBufByte(out, pathCount); toolBufAdd(out, paths.data, paths.len); toolBufByte(out, shapeCount); toolBufAdd(out, shapes.data, shapes.len); } toolBufFree(&paths); toolBufFree(&shapes); return ok; } // The outlines of color's pixels in the size x size palette pixels, each // appended to paths as a closed HVIF path of its corner points: every pixel // side with another color (or the edge) beyond it, directed so the color // is on its right, chained head to tail. Outer edges come out clockwise and // holes the other way, so a fill under either rule leaves the holes empty. // Loops start at the first corner an edge was found from, and where two // edges leave a corner the later found is taken first. False when a loop // has more corners than a path holds. static bool hvifContours(const uint8_t *pixels, uint32_t size, uint8_t color, uint32_t units, ToolBufT *paths, uint32_t *pathCount) { static const int32_t dx[HVIF_SIDES_PER_PIXEL][2] = { { 0, 1 }, { 1, 1 }, { 1, 0 }, { 0, 0 } }; static const int32_t dy[HVIF_SIDES_PER_PIXEL][2] = { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 0 } }; static const int32_t nx[HVIF_SIDES_PER_PIXEL] = { 0, 1, 0, -1 }; static const int32_t ny[HVIF_SIDES_PER_PIXEL] = { -1, 0, 1, 0 }; uint32_t side; uint32_t points; uint8_t *outCount; uint32_t *outTo; uint32_t *order; uint32_t *loop; uint32_t orderCount; uint32_t cursor; uint32_t n; uint32_t i; uint32_t corners; uint32_t from; uint32_t to; uint32_t start; uint32_t point; uint32_t prev; uint32_t next; int32_t x; int32_t y; int32_t ax; int32_t ay; uint32_t k; bool ok; side = size + 1u; points = side * side; outCount = (uint8_t *)calloc(points, sizeof(uint8_t)); outTo = (uint32_t *)malloc((size_t)points * HVIF_EDGES_PER_POINT * sizeof(uint32_t)); order = (uint32_t *)malloc((size_t)points * sizeof(uint32_t)); loop = (uint32_t *)malloc(((size_t)size * size * HVIF_SIDES_PER_PIXEL + 1u) * sizeof(uint32_t)); if (outCount == NULL || outTo == NULL || order == NULL || loop == NULL) { toolFail("out of memory"); } orderCount = 0u; for (y = 0; y < (int32_t)size; y++) { for (x = 0; x < (int32_t)size; x++) { if (pixels[(uint32_t)y * size + (uint32_t)x] != color) { continue; } for (k = 0; k < HVIF_SIDES_PER_PIXEL; k++) { ax = x + nx[k]; ay = y + ny[k]; if (ax >= 0 && ay >= 0 && ax < (int32_t)size && ay < (int32_t)size && pixels[(uint32_t)ay * size + (uint32_t)ax] == color) { continue; } from = (uint32_t)(y + dy[k][0]) * side + (uint32_t)(x + dx[k][0]); to = (uint32_t)(y + dy[k][1]) * side + (uint32_t)(x + dx[k][1]); if (outCount[from] == 0u) { order[orderCount++] = from; } outTo[from * HVIF_EDGES_PER_POINT + outCount[from]] = to; outCount[from]++; } } } ok = true; cursor = 0u; for (;;) { while (cursor < orderCount && outCount[order[cursor]] == 0u) { cursor++; } if (cursor == orderCount) { break; } start = order[cursor]; n = 0u; loop[n++] = start; point = start; for (;;) { outCount[point]--; next = outTo[point * HVIF_EDGES_PER_POINT + outCount[point]]; if (next == start) { break; } loop[n++] = next; point = next; } // The corners alone: drop each point on a straight run (kept in // place, never ahead of the point read). corners = 0u; prev = loop[n - 1u]; for (i = 0; i < n; i++) { point = loop[i]; next = (i + 1u < n) ? loop[i + 1u] : start; if ((prev % side == point % side) != (point % side == next % side)) { loop[corners++] = point; } prev = point; } if (corners > HVIF_MAX_ITEMS) { ok = false; break; } toolBufByte(paths, HVIF_PATH_CLOSED | HVIF_PATH_NO_CURVES); toolBufByte(paths, corners); for (i = 0; i < corners; i++) { toolBufByte(paths, HVIF_COORD_BIAS + loop[i] % side * units); toolBufByte(paths, HVIF_COORD_BIAS + loop[i] / side * units); } (*pathCount)++; } free(loop); free(order); free(outTo); free(outCount); return ok; } // images (side, color count, bit count, data) into a .ico. static void icoFile(const uint32_t *sides, const uint32_t *colorCounts, const uint32_t *bitCounts, const ToolBufT *images, uint32_t count, const char *path) { ToolBufT out; uint32_t offset; uint32_t i; memset(&out, 0, sizeof(out)); toolBufLe16(&out, 0u); toolBufLe16(&out, ICO_TYPE); toolBufLe16(&out, count); offset = ICONDIR_BYTES + ICONDIRENTRY_BYTES * count; for (i = 0; i < count; i++) { toolBufByte(&out, sides[i] % ENTRY_SIZE_WRAP); toolBufByte(&out, sides[i] % ENTRY_SIZE_WRAP); toolBufByte(&out, colorCounts[i] % ENTRY_SIZE_WRAP); toolBufByte(&out, 0u); toolBufLe16(&out, 1u); toolBufLe16(&out, bitCounts[i]); toolBufLe32(&out, (uint32_t)images[i].len); toolBufLe32(&out, offset); offset += (uint32_t)images[i].len; } for (i = 0; i < count; i++) { toolBufAdd(&out, images[i].data, images[i].len); } writeOut(path, out.data, out.len); toolBufFree(&out); } // One icon image: its BITMAPINFOHEADER, color table, XOR bits and AND // mask. bitCount 32: BGRA, black where wholly transparent (a system that // draws through the AND mask XORs this in); 4 or 8: indices into colors, // index 0 (black) where transparent. static void icoImage(const ToolImageT *image, uint32_t bitCount, const uint8_t (*colors)[RGB_BYTES], uint32_t colorCount, ToolBufT *out) { ToolBufT xorBits; ToolBufT andBits; uint8_t *indices; uint32_t x; uint32_t y; uint32_t c; size_t at; size_t rowStart; const uint8_t *p; memset(&xorBits, 0, sizeof(xorBits)); memset(&andBits, 0, sizeof(andBits)); if (bitCount == ICO_TRUE_COLOR_BITS) { for (y = image->height; y-- > 0u;) { for (x = 0; x < image->width; x++) { p = image->pixels + ((size_t)y * image->width + x) * RGBA_BYTES; if (p[ALPHA] == 0u) { toolBufZeros(&xorBits, RGBA_BYTES); } else { toolBufByte(&xorBits, p[2]); toolBufByte(&xorBits, p[1]); toolBufByte(&xorBits, p[0]); toolBufByte(&xorBits, p[ALPHA]); } } } } else { indices = (uint8_t *)malloc((size_t)image->width * image->height); if (indices == NULL) { toolFail("out of memory"); } toColors(image, colors, colorCount, bitCount == ICO_256_BITS, kBlack, indices); for (y = image->height; y-- > 0u;) { rowStart = xorBits.len; for (x = 0; x < image->width; x += (bitCount == ICO_16_BITS) ? 2u : 1u) { at = (size_t)y * image->width + x; c = opaque(image, at) ? indices[at] : 0u; if (bitCount == ICO_16_BITS) { c <<= NIBBLE_BITS; if (x + 1u < image->width && opaque(image, at + 1u)) { c |= indices[at + 1u]; } } toolBufByte(&xorBits, c); } toolBufZeros(&xorBits, (DIB_ROW_ALIGN - (xorBits.len - rowStart) % DIB_ROW_ALIGN) % DIB_ROW_ALIGN); } free(indices); } andMask(image, &andBits); toolBufLe32(out, DIB_HEADER_BYTES); toolBufLe32(out, image->width); toolBufLe32(out, image->height * 2u); toolBufLe16(out, 1u); toolBufLe16(out, bitCount); toolBufLe32(out, 0u); toolBufLe32(out, (uint32_t)(xorBits.len + andBits.len)); toolBufLe32(out, 0u); toolBufLe32(out, 0u); toolBufLe32(out, bitCount <= ICO_256_BITS ? colorCount : 0u); toolBufLe32(out, 0u); for (c = 0; c < colorCount && bitCount <= ICO_256_BITS; c++) { toolBufByte(out, colors[c][2]); toolBufByte(out, colors[c][1]); toolBufByte(out, colors[c][0]); toolBufByte(out, 0u); } toolBufAdd(out, xorBits.data, xorBits.len); toolBufAdd(out, andBits.data, andBits.len); toolBufFree(&xorBits); toolBufFree(&andBits); } // The 16 colors of a Finder icon pixel, each the mean of its pair, in // CIELAB; and which of them a colorful pixel may take. static void iigsColors(LabT colors[IIGS_VALUES], uint32_t keeps[IIGS_VALUES], uint32_t *keepCount) { double mean[RGB_BYTES]; uint32_t value; uint32_t c; *keepCount = 0u; for (value = 0; value < IIGS_VALUES; value++) { for (c = 0; c < RGB_BYTES; c++) { mean[c] = ((double)kIigsFirst[value >> IIGS_HALF_BITS][c] + (double)kIigsSecond[value & IIGS_HALF_MASK][c]) / 2.0; } colors[value] = lab(mean); if (value == IIGS_BLACK || value == IIGS_WHITE || chroma(colors[value]) >= IIGS_GRAY_CHROMA) { keeps[(*keepCount)++] = value; } } } // A QuickDraw II color icon of the picture at w x h icon pixels: black on // its edge, within it the nearest of the Finder's 16 colors. static void iigsIcon(const ToolImageT *picture, uint32_t w, uint32_t h, ToolBufT *out) { static const int32_t nx[] = { 1, -1, 0, 0 }; static const int32_t ny[] = { 0, 0, 1, -1 }; LabT colors[IIGS_VALUES]; LabT color; uint32_t keeps[IIGS_VALUES]; uint32_t all[IIGS_VALUES]; uint32_t keepCount; uint32_t poolCount; const uint32_t *pool; uint32_t rowBytes; uint32_t value; uint32_t shift; uint32_t v; size_t at; int32_t x; int32_t y; int32_t k; int32_t ax; int32_t ay; double rgb[RGB_BYTES]; double best; double d; bool edge; uint8_t *pixels; uint8_t *shape; const uint8_t *p; ToolImageT tall; ToolImageT image; fit(picture, w, (uint32_t)nearbyint((double)h * IIGS_PIXEL_ASPECT), &tall); resizeLanczos(&tall, w, h, &image); iigsColors(colors, keeps, &keepCount); for (v = 0; v < IIGS_VALUES; v++) { all[v] = v; } rowBytes = (w + 1u) / 2u; pixels = (uint8_t *)calloc((size_t)rowBytes * h, 1u); shape = (uint8_t *)calloc((size_t)rowBytes * h, 1u); if (pixels == NULL || shape == NULL) { toolFail("out of memory"); } for (y = 0; y < (int32_t)h; y++) { for (x = 0; x < (int32_t)w; x++) { if (!opaque(&image, (size_t)y * w + (size_t)x)) { continue; } edge = false; for (k = 0; k < (int32_t)COUNT_OF(nx); k++) { ax = x + nx[k]; ay = y + ny[k]; if (ax < 0 || ay < 0 || ax >= (int32_t)w || ay >= (int32_t)h || !opaque(&image, (size_t)ay * w + (size_t)ax)) { edge = true; } } if (edge) { value = IIGS_BLACK; } else { p = image.pixels + ((size_t)y * w + (size_t)x) * RGBA_BYTES; for (v = 0; v < RGB_BYTES; v++) { rgb[v] = p[v]; } color = lab(rgb); pool = chroma(color) < IIGS_GRAY_CHROMA ? all : keeps; poolCount = chroma(color) < IIGS_GRAY_CHROMA ? IIGS_VALUES : keepCount; value = pool[0]; best = cie94(color, colors[pool[0]]); for (v = 1; v < poolCount; v++) { d = cie94(color, colors[pool[v]]); if (d < best) { best = d; value = pool[v]; } } } shift = (x % 2 == 0) ? NIBBLE_BITS : 0u; at = (size_t)y * rowBytes + (size_t)x / 2u; pixels[at] |= (uint8_t)(value << shift); shape[at] |= (uint8_t)(IIGS_OPAQUE << shift); } } toolBufLe16(out, IIGS_ICON_TYPE_COLOR); toolBufLe16(out, rowBytes * h); toolBufLe16(out, h); toolBufLe16(out, w); toolBufAdd(out, pixels, (size_t)rowBytes * h); toolBufAdd(out, shape, (size_t)rowBytes * h); free(shape); free(pixels); toolImageFree(&image); toolImageFree(&tall); } static void imageCopy(const ToolImageT *src, ToolImageT *dst) { imageNew(dst, src->width, src->height); memcpy(dst->pixels, src->pixels, (size_t)src->width * src->height * RGBA_BYTES); } // A transparent RGBA image, or a fatal error. static void imageNew(ToolImageT *img, uint32_t w, uint32_t h) { if (!toolImageCreate(img, w, h, false)) { toolFail("out of memory"); } } // An sRGB color (channels 0-255) in CIELAB. static LabT lab(const double rgb[RGB_BYTES]) { double channels[RGB_BYTES]; double f[RGB_BYTES]; double t; uint32_t row; uint32_t c; LabT out; for (c = 0; c < RGB_BYTES; c++) { channels[c] = linear(rgb[c]); } for (row = 0; row < RGB_BYTES; row++) { t = 0.0; for (c = 0; c < RGB_BYTES; c++) { t += kXyzFromRgb[row][c] * channels[c]; } t /= kLabWhite[row]; f[row] = t > LAB_EPSILON ? pow(t, 1.0 / 3.0) : LAB_KAPPA * t + LAB_L_OFFSET / LAB_L_SCALE; } out.l = LAB_L_SCALE * f[1] - LAB_L_OFFSET; out.a = LAB_A_SCALE * (f[0] - f[1]); out.b = LAB_B_SCALE * (f[1] - f[2]); return out; } // Lanczos' window: a sinc truncated to LANCZOS_LOBES lobes, weighted by a // sinc that many times wider. static double lanczos(double x) { if (-LANCZOS_LOBES <= x && x < LANCZOS_LOBES) { return sinc(x) * sinc(x / LANCZOS_LOBES); } return 0.0; } // An sRGB channel (0-255) as linear light, 0-1. static double linear(double value) { value /= (double)SAMPLE_MAX; return value <= SRGB_LINEAR_MAX ? value / SRGB_LINEAR_SLOPE : pow((value + SRGB_OFFSET) / (1.0 + SRGB_OFFSET), SRGB_GAMMA); } // 256 colors for an 8-bit icon of image: black (where transparent: the // AND mask's partner), then up to 255 of its own (flattened on black), cut // down by median cut: the box with the widest channel (the most pixels of // equals) is split at its pixels' median along it, until there are enough // boxes or each holds one color; a box's color is its pixels' mean. static void medianCut(const ToolImageT *image, uint8_t palette[PALETTE_SIZE][RGB_BYTES]) { ToolImageT flat; uint32_t *packed; ShadeT *shades; BoxT boxes[MEDIAN_COLORS]; uint32_t boxCount; uint32_t shadeCount; uint32_t pixels; uint32_t best; uint32_t b; uint32_t i; uint32_t c; uint32_t half; uint32_t seen; uint32_t cut; uint64_t sum[RGB_BYTES]; const uint8_t *p; flatten(image, kBlack, &flat); pixels = flat.width * flat.height; packed = (uint32_t *)malloc((size_t)pixels * sizeof(uint32_t)); shades = (ShadeT *)malloc((size_t)pixels * sizeof(ShadeT)); if (packed == NULL || shades == NULL) { toolFail("out of memory"); } for (i = 0; i < pixels; i++) { p = flat.pixels + (size_t)i * RGBA_BYTES; packed[i] = ((uint32_t)p[0] << RGB_RED_SHIFT) | ((uint32_t)p[1] << RGB_GREEN_SHIFT) | p[2]; } qsort(packed, pixels, sizeof(uint32_t), packedCompare); shadeCount = 0u; for (i = 0; i < pixels; i++) { if (shadeCount > 0u && shades[shadeCount - 1u].rgb == packed[i]) { shades[shadeCount - 1u].pixels++; } else { shades[shadeCount].rgb = packed[i]; shades[shadeCount].pixels = 1u; shadeCount++; } } boxes[0].first = 0u; boxes[0].count = shadeCount; boxMeasure(&boxes[0], shades); boxCount = 1u; while (boxCount < MEDIAN_COLORS) { best = boxCount; for (b = 0; b < boxCount; b++) { if (boxes[b].range > 0u && (best == boxCount || boxes[b].range > boxes[best].range || (boxes[b].range == boxes[best].range && boxes[b].pixels > boxes[best].pixels))) { best = b; } } if (best == boxCount) { break; } for (i = boxes[best].first; i < boxes[best].first + boxes[best].count; i++) { shades[i].key = (((shades[i].rgb >> (RGB_RED_SHIFT - boxes[best].channel * BYTE_BITS)) & SAMPLE_MAX) << KEY_SHIFT) | shades[i].rgb; } qsort(shades + boxes[best].first, boxes[best].count, sizeof(ShadeT), shadeCompare); half = (boxes[best].pixels + 1u) / 2u; seen = 0u; for (cut = 1u; cut < boxes[best].count - 1u; cut++) { seen += shades[boxes[best].first + cut - 1u].pixels; if (seen >= half) { break; } } boxes[boxCount].first = boxes[best].first + cut; boxes[boxCount].count = boxes[best].count - cut; boxes[best].count = cut; boxMeasure(&boxes[best], shades); boxMeasure(&boxes[boxCount], shades); boxCount++; } memset(palette, 0, PALETTE_SIZE * RGB_BYTES); for (b = 0; b < boxCount; b++) { for (c = 0; c < RGB_BYTES; c++) { sum[c] = 0u; } for (i = boxes[b].first; i < boxes[b].first + boxes[b].count; i++) { for (c = 0; c < RGB_BYTES; c++) { sum[c] += (uint64_t)((shades[i].rgb >> (RGB_RED_SHIFT - c * BYTE_BITS)) & SAMPLE_MAX) * shades[i].pixels; } } for (c = 0; c < RGB_BYTES; c++) { palette[b + 1u][c] = (uint8_t)((sum[c] + boxes[b].pixels / 2u) / boxes[b].pixels); } } free(shades); free(packed); toolImageFree(&flat); } // The palette entry nearest (r, g, b) by squared distance, the first of // equals, measured from the low corner of the color's cell, as Pillow // measures it. static uint8_t nearestColor(PaletteT *palette, uint32_t r, uint32_t g, uint32_t b) { uint32_t cell; uint32_t i; int32_t dr; int32_t dg; int32_t db; uint32_t d; uint32_t best; cell = (r >> CELL_SHIFT) + (g >> CELL_SHIFT) * CELL_SIDE + (b >> CELL_SHIFT) * CELL_SIDE * CELL_SIDE; if (palette->cache[cell] == CELL_EMPTY) { best = UINT32_MAX; for (i = 0; i < PALETTE_SIZE; i++) { dr = (int32_t)(r & CELL_LOW_MASK) - palette->rgb[i][0]; dg = (int32_t)(g & CELL_LOW_MASK) - palette->rgb[i][1]; db = (int32_t)(b & CELL_LOW_MASK) - palette->rgb[i][2]; d = (uint32_t)(dr * dr + dg * dg + db * db); if (d < best) { best = d; palette->cache[cell] = (int16_t)i; } } } return (uint8_t)palette->cache[cell]; } // A pixel at least half opaque. static bool opaque(const ToolImageT *image, size_t pixel) { return image->pixels[pixel * RGBA_BYTES + ALPHA] >= ALPHA_OPAQUE_MIN; } 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); } static void paletteFree(PaletteT *palette) { free(palette->cache); palette->cache = NULL; } // count colors, then the first again to 256, and an empty cache. static void paletteInit(PaletteT *palette, const uint8_t (*colors)[RGB_BYTES], uint32_t count) { uint32_t i; for (i = 0; i < PALETTE_SIZE; i++) { memcpy(palette->rgb[i], colors[i < count ? i : 0u], RGB_BYTES); } palette->cache = (int16_t *)malloc(CELL_COUNT * sizeof(int16_t)); if (palette->cache == NULL) { toolFail("out of memory"); } for (i = 0; i < CELL_COUNT; i++) { palette->cache[i] = CELL_EMPTY; } } // An icon side from the command line: 1 to ICON_SIDE_MAX. static uint32_t parseSide(const char *text, size_t len, const char *what) { long value; if (!toolParseInt(text, len, &value) || value < 1 || value > (long)ICON_SIDE_MAX) { toolFail("bad %s '%.*s', need 1 to %u", what, (int)len, text, ICON_SIDE_MAX); } return (uint32_t)value; } // text as a Pascal string (a length byte first) in size bytes. static void pascal(ToolBufT *out, const char *text, uint32_t size) { size_t len; len = strlen(text); if (len > size - 1u) { len = size - 1u; } toolBufByte(out, (uint32_t)len); toolBufAdd(out, text, len); toolBufZeros(out, size - 1u - len); } // src with its color weighted by its opacity (Pillow's RGBa). static void premultiply(const ToolImageT *src, ToolImageT *dst) { size_t i; size_t pixels; uint32_t c; uint32_t mix; const uint8_t *s; uint8_t *d; imageNew(dst, src->width, src->height); pixels = (size_t)src->width * src->height; for (i = 0; i < pixels; i++) { s = src->pixels + i * RGBA_BYTES; d = dst->pixels + i * RGBA_BYTES; for (c = 0; c < RGB_BYTES; c++) { mix = (uint32_t)s[c] * s[ALPHA] + BLEND_HALF; d[c] = (uint8_t)(((mix >> DIV255_SHIFT) + mix) >> DIV255_SHIFT); } d[ALPHA] = s[ALPHA]; } } // One resampling pass over lines lines of RGBA samples: each line's // outCount outputs from its inputs, inStep bytes apart (outStep in the // output), lines inLineStep (outLineStep) apart. The horizontal pass steps // a pixel along a row, the vertical a row down a column. static void resamplePass(const uint8_t *in, uint8_t *out, const TapsT *taps, uint32_t outCount, uint32_t lines, size_t inStep, size_t inLineStep, size_t outStep, size_t outLineStep) { const uint8_t *src; const int32_t *w; uint8_t *dst; int32_t sum[RGBA_BYTES]; uint32_t line; uint32_t o; uint32_t t; uint32_t c; for (line = 0; line < lines; line++) { for (o = 0; o < outCount; o++) { src = in + line * inLineStep + taps->first[o] * inStep; w = taps->weights + (size_t)o * taps->size; for (c = 0; c < RGBA_BYTES; c++) { sum[c] = TAP_HALF; } for (t = 0; t < taps->count[o]; t++) { for (c = 0; c < RGBA_BYTES; c++) { sum[c] += (int32_t)src[t * inStep + c] * w[t]; } } dst = out + line * outLineStep + o * outStep; for (c = 0; c < RGBA_BYTES; c++) { dst[c] = clampSample(sum[c]); } } } } // src resized to w x h by Lanczos, as Pillow's resize does it: unchanged at // its own size; otherwise premultiplied, a horizontal pass if the width // changes then a vertical one if the height does, and divided back. static void resizeLanczos(const ToolImageT *src, uint32_t w, uint32_t h, ToolImageT *dst) { ToolImageT work; ToolImageT next; TapsT taps; if (w == src->width && h == src->height) { imageCopy(src, dst); return; } premultiply(src, &work); if (w != src->width) { tapsBuild(&taps, src->width, w); imageNew(&next, w, src->height); resamplePass(work.pixels, next.pixels, &taps, w, src->height, RGBA_BYTES, (size_t)src->width * RGBA_BYTES, RGBA_BYTES, (size_t)w * RGBA_BYTES); tapsFree(&taps); toolImageFree(&work); work = next; } if (h != src->height) { tapsBuild(&taps, src->height, h); imageNew(&next, w, h); resamplePass(work.pixels, next.pixels, &taps, h, w, (size_t)w * RGBA_BYTES, RGBA_BYTES, (size_t)w * RGBA_BYTES, RGBA_BYTES); tapsFree(&taps); toolImageFree(&work); work = next; } unpremultiply(&work, dst); toolImageFree(&work); } static int shadeCompare(const void *a, const void *b) { uint32_t x; uint32_t y; x = ((const ShadeT *)a)->key; y = ((const ShadeT *)b)->key; return (x > y) - (x < y); } static double sinc(double x) { if (x == 0.0) { return 1.0; } x = x * PI; return sin(x) / x; } // The taps resampling inSize samples to outSize takes, as Pillow computes // them: the window widened by the scale when shrinking, each output's // weights normalized to sum to one, then rounded to fixed point. static void tapsBuild(TapsT *taps, uint32_t inSize, uint32_t outSize) { double scale; double filterScale; double reciprocal; double support; double center; double total; double *weights; double v; int32_t lo; int32_t hi; uint32_t o; uint32_t t; scale = (double)inSize / (double)outSize; filterScale = scale < 1.0 ? 1.0 : scale; support = LANCZOS_LOBES * filterScale; reciprocal = 1.0 / filterScale; taps->size = (uint32_t)ceil(support) * 2u + 1u; taps->first = (uint32_t *)malloc((size_t)outSize * sizeof(uint32_t)); taps->count = (uint32_t *)malloc((size_t)outSize * sizeof(uint32_t)); taps->weights = (int32_t *)malloc((size_t)outSize * taps->size * sizeof(int32_t)); weights = (double *)malloc((size_t)taps->size * sizeof(double)); if (taps->first == NULL || taps->count == NULL || taps->weights == NULL || weights == NULL) { toolFail("out of memory"); } for (o = 0; o < outSize; o++) { center = ((double)o + 0.5) * scale; lo = (int32_t)(center - support + 0.5); if (lo < 0) { lo = 0; } hi = (int32_t)(center + support + 0.5); if (hi > (int32_t)inSize) { hi = (int32_t)inSize; } taps->first[o] = (uint32_t)lo; taps->count[o] = (uint32_t)(hi - lo); total = 0.0; for (t = 0; t < taps->count[o]; t++) { weights[t] = lanczos(((double)((int32_t)t + lo) - center + 0.5) * reciprocal); total += weights[t]; } for (t = 0; t < taps->count[o]; t++) { v = total != 0.0 ? weights[t] / total : weights[t]; taps->weights[(size_t)o * taps->size + t] = (int32_t)(v < 0.0 ? -0.5 + v * TAP_ONE : 0.5 + v * TAP_ONE); } } free(weights); } static void tapsFree(TapsT *taps) { free(taps->first); free(taps->count); free(taps->weights); } // The image's pixels as indices into colors (count of them, padded to 256 // with the first): over background where transparent, dithered // (Floyd-Steinberg) or the nearest. static void toColors(const ToolImageT *image, const uint8_t (*colors)[RGB_BYTES], uint32_t count, bool dither, const uint8_t background[RGB_BYTES], uint8_t *indices) { ToolImageT flat; PaletteT palette; size_t i; size_t pixels; const uint8_t *p; flatten(image, background, &flat); paletteInit(&palette, colors, count); if (dither) { ditherFloydSteinberg(&flat, &palette, indices); } else { pixels = (size_t)flat.width * flat.height; for (i = 0; i < pixels; i++) { p = flat.pixels + i * RGBA_BYTES; indices[i] = nearestColor(&palette, p[0], p[1], p[2]); } } paletteFree(&palette); toolImageFree(&flat); } // Premultiplied color divided back by its opacity (Pillow's RGBa to RGBA); // clear and opaque pixels are left as they are. static void unpremultiply(const ToolImageT *src, ToolImageT *dst) { size_t i; size_t pixels; uint32_t alpha; uint32_t c; uint32_t v; imageNew(dst, src->width, src->height); pixels = (size_t)src->width * src->height; for (i = 0; i < pixels; i++) { alpha = src->pixels[i * RGBA_BYTES + ALPHA]; for (c = 0; c < RGB_BYTES; c++) { v = src->pixels[i * RGBA_BYTES + c]; if (alpha != 0u && alpha != SAMPLE_MAX) { v = SAMPLE_MAX * v / alpha; if (v > SAMPLE_MAX) { v = SAMPLE_MAX; } } dst->pixels[i * RGBA_BYTES + c] = (uint8_t)v; } dst->pixels[i * RGBA_BYTES + ALPHA] = (uint8_t)alpha; } } static void usage(void) { size_t i; for (i = 0; i < COUNT_OF(kUsage); i++) { fprintf(stderr, "%s\n", kUsage[i]); } exit(USAGE_EXIT); } static void writeOut(const char *path, const uint8_t *data, size_t len) { if (!toolWriteFile(path, data, len)) { toolFail("cannot write %s", path); } } int main(int argc, char **argv) { const BakeT *bake; ToolImageT loaded; ToolImageT picture; char err[TOOL_PNG_ERROR_BYTES]; size_t b; toolFileName(TOOL_NAME); bake = NULL; for (b = 0; argc >= 2 && b < COUNT_OF(kBakes); b++) { if (strcmp(argv[1], kBakes[b].name) == 0) { bake = &kBakes[b]; } } if (bake == NULL || argc != (int)(4u + bake->args)) { usage(); } if (!toolPngLoad(argv[2], &loaded, err)) { toolFail("%s: %s", argv[2], err); } if (loaded.indexed) { if (!toolImageToRgba(&loaded, &picture)) { toolFail("out of memory"); } toolImageFree(&loaded); } else { picture = loaded; } bake->bake(&picture, argv + 3); toolImageFree(&picture); return 0; }