mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-03 04:54:26 +00:00
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:
parent
83588d8e2a
commit
a6c032d824
13 changed files with 336 additions and 48 deletions
|
|
@ -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
39
default.nix
Normal 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";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)");
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
68
yapf.c
|
|
@ -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
10
yapf.h
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue