/*
* yapf_cli.c — the `yapf` command-line tool.
*
* Copyright 2026 Nexoniarz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* -------------------------------------------------------------------------
*
* yapf [options] convert; formats chosen by extension
* yapf info print header information
* yapf bench time decoding
* yapf thumbnail [size] small preview (default 256)
*
* Reads PNG, JPEG, BMP, TGA, GIF (first frame), PSD (composite), PNM, HDR
* (tone-clamped) and YAPF. Writes YAPF, PNG, BMP, TGA and JPEG.
*
* Options for writing YAPF:
* --mips also store a full mip chain (2×2 box filter)
* --linear mark pixels as linear instead of sRGB
* --premult mark alpha as premultiplied
* Options for reading YAPF:
* --mip N export mip level N instead of the base image
*
* Build: cc -O2 -std=c99 -Ithird_party tools/yapf_cli.c yapf.c -o yapf -lm -pthread
*/
#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)
# define _POSIX_C_SOURCE 200809L
#endif
#include "../yapf.h"
/* Third-party, public domain; keep their warnings out of our build. */
#if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
# pragma GCC diagnostic ignored "-Wsign-compare"
#endif
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "../third_party/stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "../third_party/stb_image_write.h"
#if defined(__GNUC__) || defined(__clang__)
# pragma GCC diagnostic pop
#endif
#include
#include
#include
#include
#include
static int ends_with(const char *s, const char *ext) {
size_t n = strlen(s), m = strlen(ext);
if (n < m) return 0;
for (size_t i = 0; i < m; i++)
if (tolower((unsigned char)s[n - m + i]) != ext[i]) return 0;
return 1;
}
static int is_yapf(const char *path) { return ends_with(path, ".yapf"); }
static double now_s(void) {
#if defined(_WIN32)
return (double)clock() / CLOCKS_PER_SEC;
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec + (double)t.tv_nsec * 1e-9;
#endif
}
static uint8_t gpu_hint(int ch, int srgb) {
switch (ch) {
case 1: return YAPF_GPU_R8;
case 2: return YAPF_GPU_RG8;
case 3: return srgb ? YAPF_GPU_SRGB8 : YAPF_GPU_RGB8;
default: return srgb ? YAPF_GPU_SRGB8_A8 : YAPF_GPU_RGBA8;
}
}
/* Full mip chain with a 2×2 box filter (edges clamp for odd sizes). */
static int build_mips(yapf_image_t *img) {
int levels = 1;
while (levels < (int)YAPF_MAX_MIPS &&
((img->width >> levels) > 0 || (img->height >> levels) > 0))
levels++;
img->mips = (uint8_t **)calloc((size_t)levels, sizeof(uint8_t *));
if (!img->mips) return -1;
img->mips[0] = img->pixels;
img->mip_levels = (uint8_t)levels;
int ch = img->channels;
for (int m = 1; m < levels; m++) {
uint32_t pw = img->width >> (m - 1), ph = img->height >> (m - 1);
uint32_t w = img->width >> m, h = img->height >> m;
if (pw < 1) pw = 1;
if (ph < 1) ph = 1;
if (w < 1) w = 1;
if (h < 1) h = 1;
uint8_t *src = img->mips[m - 1];
uint8_t *dst = (uint8_t *)malloc((size_t)w * h * ch);
if (!dst) return -1;
for (uint32_t y = 0; y < h; y++)
for (uint32_t x = 0; x < w; x++)
for (int c = 0; c < ch; c++) {
uint32_t x0 = 2 * x < pw ? 2 * x : pw - 1, x1 = 2 * x + 1 < pw ? 2 * x + 1 : pw - 1;
uint32_t y0 = 2 * y < ph ? 2 * y : ph - 1, y1 = 2 * y + 1 < ph ? 2 * y + 1 : ph - 1;
unsigned s = src[((size_t)y0 * pw + x0) * ch + c] + src[((size_t)y0 * pw + x1) * ch + c]
+ src[((size_t)y1 * pw + x0) * ch + c] + src[((size_t)y1 * pw + x1) * ch + c];
dst[((size_t)y * w + x) * ch + c] = (uint8_t)((s + 2) / 4);
}
img->mips[m] = dst;
}
return 0;
}
static int write_image(const char *path, const uint8_t *px, int w, int h, int ch) {
if (ends_with(path, ".png")) return stbi_write_png(path, w, h, ch, px, w * ch);
if (ends_with(path, ".bmp")) return stbi_write_bmp(path, w, h, ch, px);
if (ends_with(path, ".tga")) return stbi_write_tga(path, w, h, ch, px);
if (ends_with(path, ".jpg") || ends_with(path, ".jpeg"))
return stbi_write_jpg(path, w, h, ch, px, 95);
fprintf(stderr, "yapf: unsupported output format: %s (use .yapf .png .bmp .tga .jpg)\n", path);
return 0;
}
static const char *gpu_name(int g) {
static const char *n[] = { "RGBA8", "RGB8", "RG8", "R8", "SRGB8_A8", "SRGB8" };
return g >= 0 && g < 6 ? n[g] : "unknown";
}
static int cmd_info(const char *path) {
FILE *f = fopen(path, "rb");
uint8_t hdr[20];
if (!f || fread(hdr, 1, 20, f) != 20 || memcmp(hdr, "YAPF", 4) != 0) {
fprintf(stderr, "yapf: %s is not a YAPF file\n", path);
if (f) fclose(f);
return 1;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fclose(f);
unsigned w = hdr[12] | hdr[13] << 8 | (unsigned)hdr[14] << 16 | (unsigned)hdr[15] << 24;
unsigned h = hdr[16] | hdr[17] << 8 | (unsigned)hdr[18] << 16 | (unsigned)hdr[19] << 24;
printf("%s\n", path);
printf(" format version : %u\n", hdr[4]);
printf(" size : %u x %u, %u channel(s)\n", w, h, hdr[5]);
printf(" mip levels : %u\n", hdr[7]);
printf(" gpu format : %s\n", gpu_name(hdr[6]));
printf(" flags : %s%s\n", hdr[8] & YAPF_FLAG_SRGB ? "sRGB " : "linear ",
hdr[8] & YAPF_FLAG_PREMULT_ALPHA ? "premultiplied-alpha" : "");
printf(" file size : %ld bytes (%.1f%% of raw)\n", size,
100.0 * (double)size / ((double)w * h * (hdr[5] ? hdr[5] : 1)));
yapf_image_t *img = yapf_load(path);
printf(" decodes : %s\n", img ? "ok" : "FAILED (corrupt or unsupported)");
yapf_free(img);
return img ? 0 : 1;
}
static int cmd_bench(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) { fprintf(stderr, "yapf: cannot open %s\n", path); return 1; }
fseek(f, 0, SEEK_END);
long n = ftell(f);
rewind(f);
uint8_t *buf = (uint8_t *)malloc((size_t)n);
if (!buf || fread(buf, 1, (size_t)n, f) != (size_t)n) { fclose(f); free(buf); return 1; }
fclose(f);
yapf_image_t *img = yapf_load_memory(buf, (size_t)n);
if (!img) { fprintf(stderr, "yapf: %s does not decode\n", path); free(buf); return 1; }
double raw = (double)img->width * img->height * img->channels;
yapf_free(img);
for (int mt = 0; mt < 2; mt++) {
/* Best of 15 rounds; each round repeats enough decodes to last
* ~20 ms, so short images are measured warm and not timer-bound. */
double one = now_s();
yapf_free(mt ? yapf_load_memory_mt(buf, (size_t)n, 0) : yapf_load_memory(buf, (size_t)n));
one = now_s() - one;
int reps = (int)(0.02 / (one > 1e-6 ? one : 1e-6)) + 1;
double best = 1e9;
for (int r = 0; r < 15; r++) {
double t = now_s();
for (int k = 0; k < reps; k++)
yapf_free(mt ? yapf_load_memory_mt(buf, (size_t)n, 0)
: yapf_load_memory(buf, (size_t)n));
t = (now_s() - t) / reps;
if (t < best) best = t;
}
printf(" %-12s %8.3f ms %6.2f GB/s\n", mt ? "all cores" : "1 thread", best * 1e3, raw / best / 1e9);
}
free(buf);
return 0;
}
/* Preview no larger than size×size: start from the smallest mip that is
* still big enough, then average boxes of source pixels. */
static int cmd_thumbnail(const char *in, const char *out, int size) {
yapf_image_t *img = yapf_load_mt(in, 0);
if (!img) { fprintf(stderr, "yapf: cannot read %s\n", in); return 1; }
if (size < 1) size = 256;
int m = 0;
while (m + 1 < img->mip_levels &&
(int)(img->width >> (m + 1)) >= size && (int)(img->height >> (m + 1)) >= size)
m++;
uint32_t sw = img->width >> m ? img->width >> m : 1, sh = img->height >> m ? img->height >> m : 1;
const uint8_t *src = img->mips[m];
double k = (double)size / (double)(sw > sh ? sw : sh);
if (k > 1.0) k = 1.0;
uint32_t dw = (uint32_t)(sw * k + 0.5), dh = (uint32_t)(sh * k + 0.5);
if (dw < 1) dw = 1;
if (dh < 1) dh = 1;
int ch = img->channels;
uint8_t *dst = (uint8_t *)malloc((size_t)dw * dh * ch);
if (!dst) { yapf_free(img); return 1; }
for (uint32_t y = 0; y < dh; y++) {
uint32_t y0 = (uint32_t)((uint64_t)y * sh / dh), y1 = (uint32_t)((uint64_t)(y + 1) * sh / dh);
if (y1 <= y0) y1 = y0 + 1;
for (uint32_t x = 0; x < dw; x++) {
uint32_t x0 = (uint32_t)((uint64_t)x * sw / dw), x1 = (uint32_t)((uint64_t)(x + 1) * sw / dw);
if (x1 <= x0) x1 = x0 + 1;
for (int c = 0; c < ch; c++) {
uint64_t sum = 0;
for (uint32_t yy = y0; yy < y1; yy++)
for (uint32_t xx = x0; xx < x1; xx++)
sum += src[((size_t)yy * sw + xx) * ch + c];
uint64_t n = (uint64_t)(y1 - y0) * (x1 - x0);
dst[((size_t)y * dw + x) * ch + c] = (uint8_t)((sum + n / 2) / n);
}
}
}
int ok = stbi_write_png(out, (int)dw, (int)dh, ch, dst, (int)dw * ch);
free(dst);
yapf_free(img);
if (!ok) fprintf(stderr, "yapf: cannot write %s\n", out);
return ok ? 0 : 1;
}
static void usage(void) {
fprintf(stderr,
"usage: yapf [options] convert (formats by extension)\n"
" yapf info show header information\n"
" yapf bench time decoding\n"
" yapf thumbnail [size] preview, default 256 px\n"
"\n"
"reads: .yapf .png .jpg .bmp .tga .gif .psd .pnm .hdr\n"
"writes: .yapf .png .jpg .bmp .tga\n"
"\n"
"options when writing .yapf:\n"
" --mips store a full mip chain\n"
" --linear pixels are linear, not sRGB\n"
" --premult alpha is premultiplied\n"
"options when reading .yapf:\n"
" --mip N export mip level N\n");
}
int main(int argc, char **argv) {
if (argc == 3 && strcmp(argv[1], "info") == 0) return cmd_info(argv[2]);
if (argc == 3 && strcmp(argv[1], "bench") == 0) return cmd_bench(argv[2]);
if ((argc == 4 || argc == 5) && strcmp(argv[1], "thumbnail") == 0)
return cmd_thumbnail(argv[2], argv[3], argc == 5 ? atoi(argv[4]) : 256);
if (argc < 3) { usage(); return 2; }
const char *in = argv[1], *out = argv[2];
int mips = 0, srgb = 1, premult = 0, mip_out = 0;
for (int i = 3; i < argc; i++) {
if (strcmp(argv[i], "--mips") == 0) mips = 1;
else if (strcmp(argv[i], "--linear") == 0) srgb = 0;
else if (strcmp(argv[i], "--premult") == 0) premult = 1;
else if (strcmp(argv[i], "--mip") == 0 && i + 1 < argc) mip_out = atoi(argv[++i]);
else { usage(); return 2; }
}
/* Read */
yapf_image_t src;
yapf_image_t *loaded = NULL;
uint8_t *stb_px = NULL;
memset(&src, 0, sizeof(src));
if (is_yapf(in)) {
loaded = yapf_load_mt(in, 0);
if (!loaded) { fprintf(stderr, "yapf: cannot read %s (missing, corrupt or not YAPF v1)\n", in); return 1; }
src = *loaded;
} else {
int w, h, ch;
if (stbi_is_16_bit(in))
fprintf(stderr, "yapf: note: %s has 16-bit channels; YAPF stores 8 bits per channel\n", in);
stb_px = stbi_load(in, &w, &h, &ch, 0);
if (!stb_px) { fprintf(stderr, "yapf: cannot read %s: %s\n", in, stbi_failure_reason()); return 1; }
src.width = (uint32_t)w; src.height = (uint32_t)h; src.channels = (uint8_t)ch;
src.pixels = stb_px; src.mip_levels = 1;
src.flags = srgb ? YAPF_FLAG_SRGB : 0;
src.gpu_format = gpu_hint(ch, srgb);
}
/* Write */
int rc = 0;
if (is_yapf(out)) {
yapf_image_t dst = src;
if (!loaded) {
if (premult) dst.flags |= YAPF_FLAG_PREMULT_ALPHA;
}
dst.mips = NULL;
dst.mip_levels = 1;
if (mips && build_mips(&dst) != 0) { fprintf(stderr, "yapf: out of memory\n"); rc = 1; }
if (!rc && yapf_save(out, &dst) != YAPF_OK) { fprintf(stderr, "yapf: cannot write %s\n", out); rc = 1; }
if (dst.mips) {
for (int m = 1; m < dst.mip_levels; m++) free(dst.mips[m]);
free(dst.mips);
}
} else {
const uint8_t *px = src.pixels;
uint32_t w = src.width, h = src.height;
if (mip_out > 0) {
if (!loaded || mip_out >= src.mip_levels) {
fprintf(stderr, "yapf: %s has no mip level %d\n", in, mip_out);
rc = 1;
} else {
px = src.mips[mip_out];
w = src.width >> mip_out ? src.width >> mip_out : 1;
h = src.height >> mip_out ? src.height >> mip_out : 1;
}
}
if (!rc && !write_image(out, px, (int)w, (int)h, src.channels)) {
fprintf(stderr, "yapf: cannot write %s\n", out);
rc = 1;
}
}
yapf_free(loaded);
stbi_image_free(stb_px);
return rc;
}