Extensions read YAPF 2; Nix package

- KDE thumbnailer: texture loader, decodes only the mip it needs; GPU
  blocks, 16-bit and float via the new yapf_tex_to_rgba8. Tested through
  KDE's plugin loader with KF6 from nixpkgs.
- Photoshop: shows the tool's error message; HDR textures open as 32-bit
  (.hdr), 16-bit stays 16-bit; 16-bit documents save as 16-bit.
- CLI: 16-bit PNG export, readable conversion errors.
- default.nix: yapf tool, library, header, MIME type and thumbnailer
  (absolute store path); runs the test suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVrVthWN8GsmgHGUhhsCYm
This commit is contained in:
Claude 2026-10-11 09:40:30 +00:00
commit a6c032d824
No known key found for this signature in database
13 changed files with 336 additions and 48 deletions

View file

@ -332,6 +332,7 @@ YAPF_CHANNELS_RGBA 4
| System | Command | Output | | System | Command | Output |
| :----- | :------ | :----- | | :----- | :------ | :----- |
| Linux, macOS, BSD | `make` · `make test` · `sudo make install` | `build/`: `libyapf.a`, `libyapf.so` / `.dylib`, `yapf` | | Linux, macOS, BSD | `make` · `make test` · `sudo make install` | `build/`: `libyapf.a`, `libyapf.so` / `.dylib`, `yapf` |
| NixOS / Nix | `nix-build` (or `nix-env -if .`) — see [extensions/linux](extensions/linux#nixos) | `result/`: `bin/yapf`, `lib/`, `include/`, MIME type, thumbnailer |
| Linux, macOS (LLVM) | `./build.sh` | `dist/<platform>/` incl. macOS universal binaries | | Linux, macOS (LLVM) | `./build.sh` | `dist/<platform>/` incl. macOS universal binaries |
| Windows (LLVM) | `build.bat` | `dist\windows-x64\`, `dist\windows-arm64\`: `yapf.dll`, `yapf.lib`, `libyapf.a`, `yapf.exe` | | Windows (LLVM) | `build.bat` | `dist\windows-x64\`, `dist\windows-arm64\`: `yapf.dll`, `yapf.lib`, `libyapf.a`, `yapf.exe` |
| Windows (Visual Studio) | `cl /O2 /c yapf.c` and add `yapf.obj` to your project | | | Windows (Visual Studio) | `cl /O2 /c yapf.c` and add `yapf.obj` to your project | |

39
default.nix Normal file
View file

@ -0,0 +1,39 @@
# YAPF for Nix / NixOS: the `yapf` tool, libyapf (static + shared) and
# yapf.h, plus the image/x-yapf MIME type and a freedesktop thumbnailer.
#
# nix-build build ./result
# nix-env -if . install for your user
# NixOS: environment.systemPackages = [ (pkgs.callPackage /path/to/YAPF { }) ];
{ pkgs ? import <nixpkgs> { }, lib ? pkgs.lib, stdenv ? pkgs.stdenv }:
stdenv.mkDerivation {
pname = "yapf";
version = "2.0";
src = lib.cleanSourceWith {
src = ./.;
filter = path: type:
let base = baseNameOf path; in
!(lib.elem base [ "build" "dist" "result" ".git" "node_modules" ]);
};
makeFlags = [ "PREFIX=$(out)" ];
doCheck = true;
checkTarget = "test";
postInstall = ''
install -Dm644 extensions/linux/yapf-mime.xml $out/share/mime/packages/yapf.xml
install -Dm644 extensions/linux/yapf.thumbnailer $out/share/thumbnailers/yapf.thumbnailer
substituteInPlace $out/share/thumbnailers/yapf.thumbnailer \
--replace-fail "TryExec=yapf" "TryExec=$out/bin/yapf" \
--replace-fail "Exec=yapf " "Exec=$out/bin/yapf "
'';
meta = {
description = "YAPF: fast lossless images and GPU-ready textures";
homepage = "https://github.com/Nexoniarz/YAPF";
license = lib.licenses.asl20;
platforms = lib.platforms.unix;
mainProgram = "yapf";
};
}

View file

@ -48,3 +48,9 @@ Log out and back in, then enable **YAPF Images** in Dolphin's preview settings.
Built against KF6 / Qt 6 and tested by loading the plugin through KDE's Built against KF6 / Qt 6 and tested by loading the plugin through KDE's
plugin factory and creating thumbnails (RGBA, RGB, mipmapped, wide images; plugin factory and creating thumbnails (RGBA, RGB, mipmapped, wide images;
corrupt files are rejected). Not yet tried inside a running Dolphin. corrupt files are rejected). Not yet tried inside a running Dolphin.
Reads YAPF version 1 and 2: GPU blocks (BC1–BC7, BC6H), 16-bit and float
textures, cubes (face +X), arrays, volumes and animations (the first
image). Only the mip level the thumbnail needs is decoded. Tested with
KF6 from nixpkgs 25.05 on BC7, BC5, BC6H, half float, 16-bit, cube and
animation files. ETC2 / ASTC files get no thumbnail.

View file

@ -3,8 +3,11 @@
#include <KIO/ThumbnailCreator> #include <KIO/ThumbnailCreator>
#include <KPluginFactory> #include <KPluginFactory>
#include <QFile>
#include <QImage> #include <QImage>
#include <cstring>
extern "C" { extern "C" {
#include "yapf.h" #include "yapf.h"
} }
@ -20,52 +23,50 @@ public:
KIO::ThumbnailResult create(const KIO::ThumbnailRequest &request) override KIO::ThumbnailResult create(const KIO::ThumbnailRequest &request) override
{ {
const QByteArray path = request.url().toLocalFile().toLocal8Bit(); QFile file(request.url().toLocalFile());
yapf_image_t *img = yapf_load(path.constData()); if (!file.open(QIODevice::ReadOnly)) {
if (!img) { return KIO::ThumbnailResult::fail();
}
const QByteArray data = file.readAll();
// Version 1 and 2: read the tables, then decode only the mip we
// need (frame 0, layer 0, face 0, slice 0).
int err = 0;
yapf_texture_t *tex = yapf_tex_open(data.constData(), size_t(data.size()), 0, &err);
if (!tex) {
return KIO::ThumbnailResult::fail(); return KIO::ThumbnailResult::fail();
} }
// Start from the smallest stored mip level that still covers the // The smallest stored mip that still covers the requested size;
// requested size; Qt scales the rest. // Qt scales the rest.
const QSize want = request.targetSize(); const QSize want = request.targetSize();
int m = 0; uint32_t m = 0, w = 0, h = 0;
while (m + 1 < img->mip_levels && while (m + 1 < tex->mip_levels) {
int(img->width >> (m + 1)) >= want.width() && yapf_tex_mip_size(tex, m + 1, &w, &h, nullptr);
int(img->height >> (m + 1)) >= want.height()) { if (int(w) < want.width() || int(h) < want.height()) {
break;
}
++m; ++m;
} }
const int w = qMax(1, int(img->width >> m)); yapf_tex_mip_size(tex, m, &w, &h, nullptr);
const int h = qMax(1, int(img->height >> m)); const uint32_t s = yapf_tex_surface(tex, 0, 0, 0, m);
const uchar *px = img->mips[m];
QImage image; QImage image(int(w), int(h), (tex->flags & YAPF_FLAG_PREMULT_ALPHA) ? QImage::Format_RGBA8888_Premultiplied
switch (img->channels) { : QImage::Format_RGBA8888);
case 1: bool ok = !image.isNull() &&
image = QImage(px, w, h, w, QImage::Format_Grayscale8).copy(); yapf_tex_decode_surface(tex, data.constData(), size_t(data.size()), s, 0) == YAPF_OK;
break; if (ok) {
case 2: { // gray + alpha → RGBA // RGBA 8-bit rows (blocks decoded, 16-bit / float converted)
image = QImage(w, h, QImage::Format_RGBA8888); QByteArray rgba(int(size_t(w) * h * 4), Qt::Uninitialized);
for (int y = 0; y < h; ++y) { ok = yapf_tex_to_rgba8(tex, s, 0, reinterpret_cast<uint8_t *>(rgba.data())) == YAPF_OK;
const uchar *s = px + size_t(y) * w * 2; for (int y = 0; ok && y < int(h); ++y) {
uchar *d = image.scanLine(y); memcpy(image.scanLine(y), rgba.constData() + size_t(y) * w * 4, size_t(w) * 4);
for (int x = 0; x < w; ++x, s += 2, d += 4) {
d[0] = d[1] = d[2] = s[0];
d[3] = s[1];
} }
} }
break; yapf_tex_free(tex);
if (!ok) {
return KIO::ThumbnailResult::fail();
} }
case 3:
image = QImage(px, w, h, w * 3, QImage::Format_RGB888).copy();
break;
default:
image = QImage(px, w, h, w * 4,
(img->flags & YAPF_FLAG_PREMULT_ALPHA) ? QImage::Format_RGBA8888_Premultiplied
: QImage::Format_RGBA8888).copy();
break;
}
yapf_free(img);
if (image.width() > want.width() || image.height() > want.height()) { if (image.width() > want.width() || image.height() > want.height()) {
image = image.scaled(want, Qt::KeepAspectRatio, Qt::SmoothTransformation); image = image.scaled(want, Qt::KeepAspectRatio, Qt::SmoothTransformation);

View file

@ -13,3 +13,22 @@ on your `PATH` — `sudo make install` puts it in `/usr/local/bin`). They work
in file managers that use freedesktop thumbnailers: GNOME Files (Nautilus), in file managers that use freedesktop thumbnailers: GNOME Files (Nautilus),
Nemo, Caja, and Thunar (with tumbler). KDE Dolphin uses its own plugin Nemo, Caja, and Thunar (with tumbler). KDE Dolphin uses its own plugin
system and is not covered yet. system and is not covered yet.
Thumbnails cover every YAPF file the tool reads: version 1 and 2, GPU
blocks (BC1–BC7, BC6H), 16-bit and float textures, and the first image of
cubes, arrays and animations. KDE Dolphin: see [../kde](../kde).
### NixOS
`/usr/share` is not used on NixOS. Install the package from the
repository's `default.nix` instead — it carries the MIME type and a
thumbnailer that calls `yapf` by its full store path:
```nix
# configuration.nix
environment.systemPackages = [ (pkgs.callPackage /path/to/YAPF { }) ];
environment.pathsToLink = [ "/share/thumbnailers" ]; # if your desktop does not link it already
```
For your user only: `nix-env -if /path/to/YAPF`, then
`sh extensions/linux/install.sh` (the per-user install).

View file

@ -14,9 +14,11 @@
if (!(files instanceof Array)) files = [files]; if (!(files instanceof Array)) files = [files];
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {
var tmp = yapfTempFile(".png"); // 8 / 16-bit images come through PNG (16 bits are kept); half,
// float and BC6H HDR textures through Radiance .hdr (32-bit).
var tmp = yapfTempFile(yapfIsHdr(files[i]) ? ".hdr" : ".png");
if (!yapfRun([files[i].fsName, tmp.fsName], tmp)) { if (!yapfRun([files[i].fsName, tmp.fsName], tmp)) {
alert("Could not open " + files[i].name + ".\n\n" + YAPF_TOOL_HELP); alert("Could not open " + files[i].name + ".\n\n" + yapfFailText());
return; return;
} }
var doc = app.open(tmp); var doc = app.open(tmp);

View file

@ -25,6 +25,16 @@ native plugin.
Instead of copying `yapf` next to the scripts you can put it on your `PATH`. Instead of copying `yapf` next to the scripts you can put it on your `PATH`.
## What opens how
Every YAPF file the `yapf` tool reads (version 1 and 2): 8-bit images open
as 8-bit documents, 16-bit textures as 16-bit documents, half / float and
BC6H HDR textures as 32-bit documents (through Radiance `.hdr`), other GPU
blocks (BC1–BC7) decoded to 8-bit. Cubes, arrays and animations open their
first image. When a file cannot be opened, the alert shows the tool's
reason (for example a damaged file). Saving keeps 8 or 16 bits per
channel.
## Status ## Status
The scripts are written for Photoshop CC's ExtendScript and are not yet The scripts are written for Photoshop CC's ExtendScript and are not yet

View file

@ -22,8 +22,10 @@
try { try {
if (copy.mode !== DocumentMode.RGB && copy.mode !== DocumentMode.GRAYSCALE) if (copy.mode !== DocumentMode.RGB && copy.mode !== DocumentMode.GRAYSCALE)
copy.changeMode(ChangeMode.RGB); copy.changeMode(ChangeMode.RGB);
if (copy.bitsPerChannel !== BitsPerChannelType.EIGHT) // 8 and 16 bits per channel are kept (16-bit becomes a 16-bit
copy.bitsPerChannel = BitsPerChannelType.EIGHT; // YAPF texture); 32-bit documents are saved as 16-bit.
if (copy.bitsPerChannel === BitsPerChannelType.THIRTYTWO)
copy.bitsPerChannel = BitsPerChannelType.SIXTEEN;
var opts = new PNGSaveOptions(); var opts = new PNGSaveOptions();
opts.interlaced = false; opts.interlaced = false;
copy.saveAs(tmp, opts, true, Extension.LOWERCASE); copy.saveAs(tmp, opts, true, Extension.LOWERCASE);
@ -35,6 +37,6 @@
if (mips) args.push("--mips"); if (mips) args.push("--mips");
var ok = yapfRun(args, out); var ok = yapfRun(args, out);
tmp.remove(); tmp.remove();
if (!ok) { alert("Could not write " + out.name + ".\n\n" + YAPF_TOOL_HELP); return; } if (!ok) { alert("Could not write " + out.name + ".\n\n" + yapfFailText()); return; }
alert("Saved " + out.fsName + " (" + Math.round(out.length / 1024) + " KB)"); alert("Saved " + out.fsName + " (" + Math.round(out.length / 1024) + " KB)");
})(); })();

View file

@ -20,16 +20,52 @@ function yapfTool() {
return names[0]; // rely on the PATH return names[0]; // rely on the PATH
} }
// Runs the tool; returns true when `expectOut` was produced. // Runs the tool with its output (stdout) going to `stdoutFile` when given.
function yapfRun(args, expectOut) { // Returns the tool's error text ("" when it printed none).
function yapfExec(args, stdoutFile) {
var errFile = yapfTempFile(".txt");
var cmd = yapfQuote(yapfTool()); var cmd = yapfQuote(yapfTool());
for (var i = 0; i < args.length; i++) cmd += " " + (args[i].charAt(0) === "-" ? args[i] : yapfQuote(args[i])); for (var i = 0; i < args.length; i++) cmd += " " + (args[i].charAt(0) === "-" ? args[i] : yapfQuote(args[i]));
if (stdoutFile) cmd += " > " + yapfQuote(stdoutFile.fsName);
cmd += " 2> " + yapfQuote(errFile.fsName);
if (YAPF_IS_WINDOWS) cmd = '"' + cmd + '"'; // cmd.exe strips one pair of quotes if (YAPF_IS_WINDOWS) cmd = '"' + cmd + '"'; // cmd.exe strips one pair of quotes
if (expectOut.exists) expectOut.remove();
app.system(cmd); app.system(cmd);
var text = yapfReadText(errFile);
errFile.remove();
return text;
}
function yapfReadText(f) {
if (!f.exists || !f.open("r")) return "";
var t = f.read();
f.close();
return t.replace(/\s+$/, "");
}
// Runs the tool; returns true when `expectOut` was produced. The tool's
// own message (why it failed) is left in yapfLastError.
var yapfLastError = "";
function yapfRun(args, expectOut) {
if (expectOut.exists) expectOut.remove();
yapfLastError = yapfExec(args, null);
return expectOut.exists; return expectOut.exists;
} }
// The tool's message, or the help text when the tool could not be run.
function yapfFailText() {
return /yapf: /.test(yapfLastError) ? yapfLastError : YAPF_TOOL_HELP;
}
// True when a .yapf file holds float / HDR data (half, float, BC6H), so it
// should open as a 32-bit document instead of a clamped 8-bit one.
function yapfIsHdr(file) {
var info = yapfTempFile(".txt");
yapfExec(["info", file.fsName], info);
var text = yapfReadText(info);
info.remove();
return /payload\s*:[^\n]*(half float|, float|BC6H)/.test(text);
}
function yapfTempFile(ext) { function yapfTempFile(ext) {
return new File(Folder.temp.fsName + "/yapf_" + new Date().getTime() + "_" + return new File(Folder.temp.fsName + "/yapf_" + new Date().getTime() + "_" +
Math.floor(Math.random() * 1e6) + ext); Math.floor(Math.random() * 1e6) + ext);

View file

@ -632,6 +632,35 @@ static void v2_mips(void) {
yapf_tex_free(t); yapf_tex_free(t);
} }
static void v2_preview(void) {
uint8_t out[4 * 4 * 4];
/* gray + alpha u16 */
yapf_texture_t d = desc(4, 4, 2, YAPF_SAMPLE_U16);
yapf_texture_t *t = yapf_tex_create(&d);
uint16_t *p = (uint16_t *)t->data[0];
for (int i = 0; i < 16; i++) { p[2 * i] = 65535; p[2 * i + 1] = 32896; }
int ok = yapf_tex_to_rgba8(t, 0, 0, out) == YAPF_OK &&
out[0] == 255 && out[1] == 255 && out[2] == 255 && out[3] == 128;
yapf_tex_free(t);
/* linear half 0.2140 → sRGB 128, 2.0 clamps, alpha stays linear */
d = desc(4, 4, 4, YAPF_SAMPLE_F16);
t = yapf_tex_create(&d);
p = (uint16_t *)t->data[0];
for (int i = 0; i < 16; i++) {
p[4 * i] = yapf_float_to_half(0.2158605f); p[4 * i + 1] = yapf_float_to_half(2.0f);
p[4 * i + 2] = yapf_float_to_half(-1.0f); p[4 * i + 3] = yapf_float_to_half(0.5f);
}
ok = ok && yapf_tex_to_rgba8(t, 0, 0, out) == YAPF_OK &&
out[0] == 128 && out[1] == 255 && out[2] == 0 && out[3] == 128;
/* BC7 and BC6H go through the block decoders */
yapf_texture_t *c = NULL;
ok = ok && yapf_tex_compress(t, YAPF_GPU_BC6H, &c) == YAPF_OK &&
yapf_tex_to_rgba8(c, 0, 0, out) == YAPF_OK && out[3] == 255 && out[1] == 255;
yapf_tex_free(c);
yapf_tex_free(t);
CHECK(ok, "yapf_tex_to_rgba8 converts every payload for display");
}
static void v2_misc(void) { static void v2_misc(void) {
int ok = 1; int ok = 1;
for (uint32_t h = 0; h < 0x10000 && ok; h++) { for (uint32_t h = 0; h < 0x10000 && ok; h++) {
@ -697,6 +726,7 @@ int main(int argc, char **argv) {
v2_gpu(); v2_gpu();
v2_mips(); v2_mips();
v2_misc(); v2_misc();
v2_preview();
printf("%d tests, %d failures\n", tests, fails); printf("%d tests, %d failures\n", tests, fails);
return fails != 0; return fails != 0;

View file

@ -167,6 +167,8 @@ static int parse_map(const char *s, uint8_t *map) {
return 1; return 1;
} }
static const char *err_name(int e);
/* ── Reading any input into a texture ────────────────────────────────── */ /* ── Reading any input into a texture ────────────────────────────────── */
static yapf_texture_t *new_tex(uint32_t w, uint32_t h, int ch, int sample) { static yapf_texture_t *new_tex(uint32_t w, uint32_t h, int ch, int sample) {
@ -182,7 +184,7 @@ static yapf_texture_t *read_input(const char *path, const opts_t *o, int *from_y
if (is_yapf(path)) { if (is_yapf(path)) {
int err = 0; int err = 0;
yapf_texture_t *t = yapf_tex_load(path, 0, 0, &err); yapf_texture_t *t = yapf_tex_load(path, 0, 0, &err);
if (!t) fprintf(stderr, "yapf: cannot read %s (error %d)\n", path, err); if (!t) fprintf(stderr, "yapf: cannot read %s: %s\n", path, err_name(err));
*from_yapf = 1; *from_yapf = 1;
return t; return t;
} }
@ -399,6 +401,58 @@ static uint8_t *to8(const float *f, size_t n) {
return o; return o;
} }
/* 16-bit PNG (stb_image_write only writes 8 bits): rows of big-endian
* samples, filter 0, compressed with stb's zlib. */
static uint32_t png_crc(const uint8_t *p, size_t n, uint32_t c) {
c = ~c;
for (size_t i = 0; i < n; i++) {
c ^= p[i];
for (int k = 0; k < 8; k++) c = (c >> 1) ^ (0xEDB88320u & (0u - (c & 1)));
}
return ~c;
}
static void png_chunk(FILE *f, const char *type, const uint8_t *d, uint32_t n) {
uint8_t b[4] = { (uint8_t)(n >> 24), (uint8_t)(n >> 16), (uint8_t)(n >> 8), (uint8_t)n };
fwrite(b, 1, 4, f);
fwrite(type, 1, 4, f);
if (n) fwrite(d, 1, n, f);
uint32_t c = png_crc(d, n, png_crc((const uint8_t *)type, 4, 0));
uint8_t cb[4] = { (uint8_t)(c >> 24), (uint8_t)(c >> 16), (uint8_t)(c >> 8), (uint8_t)c };
fwrite(cb, 1, 4, f);
}
static int write_png16(const char *path, const uint16_t *px, int w, int h, int ch) {
size_t row = (size_t)w * ch * 2 + 1;
uint8_t *raw = (uint8_t *)malloc(row * h);
if (!raw) return 0;
for (int y = 0; y < h; y++) {
uint8_t *r = raw + row * y;
r[0] = 0;
for (int i = 0; i < w * ch; i++) {
uint16_t v = px[(size_t)y * w * ch + i];
r[1 + 2 * i] = (uint8_t)(v >> 8);
r[2 + 2 * i] = (uint8_t)v;
}
}
int zlen = 0;
uint8_t *z = stbi_zlib_compress(raw, (int)(row * h), &zlen, 8);
free(raw);
FILE *f = z ? fopen(path, "wb") : NULL;
if (!f) { STBIW_FREE(z); return 0; }
static const uint8_t sig[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
static const uint8_t ctype[5] = { 0, 0, 4, 2, 6 };
uint8_t ihdr[13] = { (uint8_t)(w >> 24), (uint8_t)(w >> 16), (uint8_t)(w >> 8), (uint8_t)w,
(uint8_t)(h >> 24), (uint8_t)(h >> 16), (uint8_t)(h >> 8), (uint8_t)h,
16, ctype[ch], 0, 0, 0 };
fwrite(sig, 1, 8, f);
png_chunk(f, "IHDR", ihdr, 13);
png_chunk(f, "IDAT", z, (uint32_t)zlen);
png_chunk(f, "IEND", NULL, 0);
STBIW_FREE(z);
return fclose(f) == 0;
}
static int write_image(const char *path, const void *px, int w, int h, int ch, int hdr) { static int write_image(const char *path, const void *px, int w, int h, int ch, int hdr) {
if (ends_with(path, ".hdr")) { if (ends_with(path, ".hdr")) {
if (!hdr) { if (!hdr) {
@ -445,9 +499,19 @@ static int export_image(const yapf_texture_t *t, const char *in, const char *out
: "no such slice, or out of memory"); : "no such slice, or out of memory");
return 1; return 1;
} }
int ok;
if (t->payload == YAPF_PAYLOAD_PIXELS && t->sample == YAPF_SAMPLE_U16 && ends_with(out, ".png")) {
/* keep all 16 bits */
uint32_t m = s % t->mip_levels, d;
yapf_tex_mip_size(t, m, NULL, NULL, &d);
const uint16_t *src = (const uint16_t *)((const uint8_t *)t->data[s] +
yapf_tex_surface_bytes(t, m) / d * (uint32_t)o->slice);
ok = write_png16(out, src, (int)w, (int)h, t->channels);
} else {
if (t->sample == YAPF_SAMPLE_U16 && !ends_with(out, ".hdr")) if (t->sample == YAPF_SAMPLE_U16 && !ends_with(out, ".hdr"))
fprintf(stderr, "yapf: note: 16-bit samples written as 8-bit\n"); fprintf(stderr, "yapf: note: 16-bit samples written as 8-bit (use .png to keep them)\n");
int ok = write_image(out, px, (int)w, (int)h, ch, hdr); ok = write_image(out, px, (int)w, (int)h, ch, hdr);
}
free(px); free(px);
if (!ok) { fprintf(stderr, "yapf: cannot write %s\n", out); return 1; } if (!ok) { fprintf(stderr, "yapf: cannot write %s\n", out); return 1; }
return 0; return 0;

68
yapf.c
View file

@ -4677,3 +4677,71 @@ int yapf_tex_build_mips(yapf_texture_t *tex, uint32_t levels, uint8_t alpha_ref)
else tex->flags &= (uint8_t)~YAPF_FLAG_COVERAGE; else tex->flags &= (uint8_t)~YAPF_FLAG_COVERAGE;
return YAPF_OK; return YAPF_OK;
} }
/* ========================================================================
* Previews
* ======================================================================== */
/* Linear 0..1 → sRGB code, exact to the 8-bit rounding (thresholds). */
static uint8_t yapf__lin_to_code(const yapf__srgb_tab *t, float v) {
if (!(v > 0.0f)) return 0; /* also NaN */
if (v >= 1.0f) return 255;
return (uint8_t)(yapf__lin_to_srgb(t, v) * 255.0f + 0.5f);
}
int yapf_tex_to_rgba8(const yapf_texture_t *tex, uint32_t surface, uint32_t slice, uint8_t *out) {
uint32_t S;
if (!out || yapf__tex_check(tex, &S) != 0 || surface >= S || !tex->data || !tex->data[surface])
return YAPF_ERR_INVALID;
uint32_t m = surface % tex->mip_levels, w, h, d;
yapf_tex_mip_size(tex, m, &w, &h, &d);
if (slice >= d) return YAPF_ERR_INVALID;
size_t n = (size_t)w * h;
const uint8_t *src = (const uint8_t *)tex->data[surface] + yapf_tex_surface_bytes(tex, m) / d * slice;
int ch = tex->channels, sample = tex->sample;
uint16_t *half = NULL;
if (tex->payload == YAPF_PAYLOAD_BLOCKS) {
if (tex->format != YAPF_GPU_BC6H && tex->format != YAPF_GPU_BC6H_SF) {
int rc = yapf_blocks_decode(tex->format, src, w, h, out);
if (rc == YAPF_OK && tex->channels == 3) /* BC1 / BC7 without alpha */
for (size_t i = 0; i < n; i++) out[i * 4 + 3] = 255;
return rc;
}
half = (uint16_t *)malloc(n * 3 * sizeof(uint16_t));
if (!half) return YAPF_ERR_OOM;
yapf_blocks_decode(tex->format, src, w, h, half);
src = (const uint8_t *)half;
ch = 3;
sample = YAPF_SAMPLE_F16;
}
yapf__srgb_tab *st = NULL;
if (sample == YAPF_SAMPLE_F16 || sample == YAPF_SAMPLE_F32) {
st = (yapf__srgb_tab *)malloc(sizeof(yapf__srgb_tab));
if (!st) { free(half); return YAPF_ERR_OOM; }
yapf__srgb_init(st);
}
size_t sb = yapf__sbytes(sample);
for (size_t i = 0; i < n; i++) {
uint8_t v[4] = { 0, 0, 0, 255 };
for (int c = 0; c < ch; c++) {
uint32_t x = yapf__get_sample(src + (i * ch + c) * sb, sample);
int alpha = (ch == 2 && c == 1) || (ch == 4 && c == 3);
uint8_t b;
if (sample == YAPF_SAMPLE_U8) b = (uint8_t)x;
else if (sample == YAPF_SAMPLE_U16) b = (uint8_t)((x + 128) / 257);
else {
float f;
if (sample == YAPF_SAMPLE_F16) f = yapf_half_to_float((uint16_t)x);
else memcpy(&f, &x, 4);
b = alpha ? (uint8_t)yapf__clampi(f * 255.0f, 0, 255) : yapf__lin_to_code(st, f);
}
v[ch <= 2 ? (c == 0 ? 0 : 3) : c] = b;
}
if (ch <= 2) v[1] = v[2] = v[0];
memcpy(out + i * 4, v, 4);
}
free(st);
free(half);
return YAPF_OK;
}

10
yapf.h
View file

@ -601,6 +601,16 @@ int yapf_tex_compress(const yapf_texture_t *src, uint8_t format,
int yapf_blocks_decode(uint8_t format, const void *blocks, uint32_t w, uint32_t h, int yapf_blocks_decode(uint8_t format, const void *blocks, uint32_t w, uint32_t h,
void *out); void *out);
/*
* yapf_tex_to_rgba8 — one slice of a decoded surface as 8-bit RGBA for
* display (thumbnails, previews): gray is expanded, 16-bit is rounded to
* 8 bits, float and half are clamped to 0..1 and sRGB encoded (they are
* linear), GPU blocks are decoded. out holds w × h × 4 bytes of that
* mip. Returns YAPF_ERR_UNSUPPORTED for ETC2 / EAC / ASTC blocks.
*/
int yapf_tex_to_rgba8(const yapf_texture_t *tex, uint32_t surface, uint32_t slice,
uint8_t *out);
/* yapf-sum, the checksum used by version 2 files. */ /* yapf-sum, the checksum used by version 2 files. */
uint32_t yapf_checksum(const void *data, size_t size); uint32_t yapf_checksum(const void *data, size_t size);