YAPF v1: fast byte-aligned format, SIMD, tools and plugins

Format (version 1, replaces the earlier experimental versions):
- per-tile YCoCg-R or subtract-green colour transform, 8-bit modular
- 12 row filters per 8-row band (Left, Gradient, Up-right, Up-average,
  Up 1-8 rows), none with more than a one-add serial dependency
- residuals in groups of 8 at the group's width (w bits x 8 = w bytes),
  4-bit control codes with repeat-previous-width runs chosen by DP
- constant and stored tiles, independent 64x64 tiles, mips, GPU hints
- all sizes validated before decoding; corrupt input is rejected

Library:
- SIMD on by default: SSSE3 (runtime CPUID check; GCC, Clang, MSVC,
  MinGW) and NEON on ARM64; -DYAPF_NO_SIMD for plain C
- yapf_load_memory, yapf_load_mt, yapf_load_memory_mt
- 6-10x faster decoding than PNG (stb_image) on one core; -O2 and -O3
  builds perform the same
- fixes for macOS (sysconf) and the Windows import library exports

Tools and integrations:
- `yapf` CLI: convert PNG/JPG/TGA/BMP/PSD/GIF <-> YAPF, info, bench,
  thumbnail, mips
- JavaScript and Python bindings (byte-identical to the C encoder)
- GIMP 3 plugin: exports the flattened image, optional mipmaps
- Blender extension: import (also as plane), export, drag and drop
- VS Code preview, Figma plugin, Photoshop scripts, Linux MIME type and
  thumbnailer
- Makefile, updated build.sh / build.bat, tools/build_release.sh
- tests/test_yapf.c (784 checks) and bindings/js/test.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-03 14:17:59 +02:00
commit 2ca726e3af
48 changed files with 14905 additions and 710 deletions

58
tools/build_release.sh Executable file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Builds every release download into dist/release/ — for maintainers.
#
# tools/build_release.sh
#
# Needs: zig (cross-compiles all six platforms from one machine), python3,
# and node + npx for the VS Code package. Outputs:
# yapf-<platform>.zip command-line tool + shared/static library + header
# yapf_image-blender.zip Blender extension with libraries for all platforms
# yapf-preview.vsix VS Code extension
# yapf-figma.zip Figma plugin (import via manifest.json)
# yapf-photoshop.zip Photoshop scripts
# yapf-gimp-source.zip GIMP plugin source (build with gimptool)
set -euo pipefail
cd "$(dirname "$0")/.."
OUT=dist/release
LIBS=dist/libs
rm -rf "$OUT" "$LIBS"
mkdir -p "$OUT" "$LIBS"
command -v zig >/dev/null || { echo "zig not found: https://ziglang.org/download/" >&2; exit 1; }
export ZIG_GLOBAL_CACHE_DIR=${ZIG_GLOBAL_CACHE_DIR:-$PWD/dist/.zig-cache}
# zig target folder library tool
targets="x86_64-linux-gnu.2.17 linux-x64 libyapf.so yapf
aarch64-linux-gnu.2.17 linux-arm64 libyapf.so yapf
x86_64-windows-gnu windows-x64 yapf.dll yapf.exe
aarch64-windows-gnu windows-arm64 yapf.dll yapf.exe
x86_64-macos macos-x64 libyapf.dylib yapf
aarch64-macos macos-arm64 libyapf.dylib yapf"
echo "$targets" | while read -r tgt dir lib tool; do
pkg=dist/pkg/yapf-$dir
rm -rf "$pkg" && mkdir -p "$pkg" "$LIBS/$dir"
zig cc -target "$tgt" -O2 -std=c99 -shared -fPIC -s yapf.c -o "$LIBS/$dir/$lib"
zig cc -target "$tgt" -O2 -std=c99 -s tools/yapf_cli.c yapf.c -o "$pkg/$tool" -lm
zig cc -target "$tgt" -O2 -std=c99 -c yapf.c -o "$pkg/yapf.o"
zig ar rcs "$pkg/libyapf.a" "$pkg/yapf.o" && rm "$pkg/yapf.o"
cp "$LIBS/$dir/$lib" yapf.h LICENSE "$pkg/"
rm -f "$pkg"/*.lib "$pkg"/*.pdb "$LIBS/$dir"/*.lib "$LIBS/$dir"/*.pdb
(cd dist/pkg && python3 -m zipfile -c "../../$OUT/yapf-$dir.zip" "yapf-$dir")
echo "built yapf-$dir.zip"
done
python3 extensions/blender/build.py "$LIBS"
mv dist/yapf_image-blender.zip "$OUT/"
if command -v npx >/dev/null; then
(cd extensions/vscode && npm run --silent package && mv yapf-preview-*.vsix "../../$OUT/yapf-preview.vsix")
else
echo "node/npx not found: skipped the VS Code package" >&2
fi
(cd extensions/figma && node build.js 2>/dev/null || true)
python3 -m zipfile -c "$OUT/yapf-figma.zip" extensions/figma/manifest.json extensions/figma/code.js extensions/figma/ui.html
python3 -m zipfile -c "$OUT/yapf-photoshop.zip" extensions/photoshop
python3 -m zipfile -c "$OUT/yapf-gimp-source.zip" extensions/gimp yapf.c yapf.h
ls -la "$OUT"

345
tools/yapf_cli.c Normal file
View file

@ -0,0 +1,345 @@
/*
* 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 <input> <output> [options] convert; formats chosen by extension
* yapf info <file.yapf> print header information
* yapf bench <file.yapf> time decoding
* yapf thumbnail <in.yapf> <out.png> [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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
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 <input> <output> [options] convert (formats by extension)\n"
" yapf info <file.yapf> show header information\n"
" yapf bench <file.yapf> time decoding\n"
" yapf thumbnail <in.yapf> <out.png> [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;
}