2004 lines
73 KiB
C
2004 lines
73 KiB
C
// 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 <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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 <stdint.h>\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;
|
|
}
|