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
|
|
@ -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
|
||||
plugin factory and creating thumbnails (RGBA, RGB, mipmapped, wide images;
|
||||
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 <KPluginFactory>
|
||||
#include <QFile>
|
||||
#include <QImage>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
#include "yapf.h"
|
||||
}
|
||||
|
|
@ -20,52 +23,50 @@ public:
|
|||
|
||||
KIO::ThumbnailResult create(const KIO::ThumbnailRequest &request) override
|
||||
{
|
||||
const QByteArray path = request.url().toLocalFile().toLocal8Bit();
|
||||
yapf_image_t *img = yapf_load(path.constData());
|
||||
if (!img) {
|
||||
QFile file(request.url().toLocalFile());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
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();
|
||||
}
|
||||
|
||||
// Start from the smallest stored mip level that still covers the
|
||||
// requested size; Qt scales the rest.
|
||||
// The smallest stored mip that still covers the requested size;
|
||||
// Qt scales the rest.
|
||||
const QSize want = request.targetSize();
|
||||
int m = 0;
|
||||
while (m + 1 < img->mip_levels &&
|
||||
int(img->width >> (m + 1)) >= want.width() &&
|
||||
int(img->height >> (m + 1)) >= want.height()) {
|
||||
uint32_t m = 0, w = 0, h = 0;
|
||||
while (m + 1 < tex->mip_levels) {
|
||||
yapf_tex_mip_size(tex, m + 1, &w, &h, nullptr);
|
||||
if (int(w) < want.width() || int(h) < want.height()) {
|
||||
break;
|
||||
}
|
||||
++m;
|
||||
}
|
||||
const int w = qMax(1, int(img->width >> m));
|
||||
const int h = qMax(1, int(img->height >> m));
|
||||
const uchar *px = img->mips[m];
|
||||
yapf_tex_mip_size(tex, m, &w, &h, nullptr);
|
||||
const uint32_t s = yapf_tex_surface(tex, 0, 0, 0, m);
|
||||
|
||||
QImage image;
|
||||
switch (img->channels) {
|
||||
case 1:
|
||||
image = QImage(px, w, h, w, QImage::Format_Grayscale8).copy();
|
||||
break;
|
||||
case 2: { // gray + alpha → RGBA
|
||||
image = QImage(w, h, QImage::Format_RGBA8888);
|
||||
for (int y = 0; y < h; ++y) {
|
||||
const uchar *s = px + size_t(y) * w * 2;
|
||||
uchar *d = image.scanLine(y);
|
||||
for (int x = 0; x < w; ++x, s += 2, d += 4) {
|
||||
d[0] = d[1] = d[2] = s[0];
|
||||
d[3] = s[1];
|
||||
}
|
||||
QImage image(int(w), int(h), (tex->flags & YAPF_FLAG_PREMULT_ALPHA) ? QImage::Format_RGBA8888_Premultiplied
|
||||
: QImage::Format_RGBA8888);
|
||||
bool ok = !image.isNull() &&
|
||||
yapf_tex_decode_surface(tex, data.constData(), size_t(data.size()), s, 0) == YAPF_OK;
|
||||
if (ok) {
|
||||
// RGBA 8-bit rows (blocks decoded, 16-bit / float converted)
|
||||
QByteArray rgba(int(size_t(w) * h * 4), Qt::Uninitialized);
|
||||
ok = yapf_tex_to_rgba8(tex, s, 0, reinterpret_cast<uint8_t *>(rgba.data())) == YAPF_OK;
|
||||
for (int y = 0; ok && y < int(h); ++y) {
|
||||
memcpy(image.scanLine(y), rgba.constData() + size_t(y) * w * 4, size_t(w) * 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
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_tex_free(tex);
|
||||
if (!ok) {
|
||||
return KIO::ThumbnailResult::fail();
|
||||
}
|
||||
yapf_free(img);
|
||||
|
||||
if (image.width() > want.width() || image.height() > want.height()) {
|
||||
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),
|
||||
Nemo, Caja, and Thunar (with tumbler). KDE Dolphin uses its own plugin
|
||||
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];
|
||||
|
||||
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)) {
|
||||
alert("Could not open " + files[i].name + ".\n\n" + YAPF_TOOL_HELP);
|
||||
alert("Could not open " + files[i].name + ".\n\n" + yapfFailText());
|
||||
return;
|
||||
}
|
||||
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`.
|
||||
|
||||
## 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
|
||||
|
||||
The scripts are written for Photoshop CC's ExtendScript and are not yet
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@
|
|||
try {
|
||||
if (copy.mode !== DocumentMode.RGB && copy.mode !== DocumentMode.GRAYSCALE)
|
||||
copy.changeMode(ChangeMode.RGB);
|
||||
if (copy.bitsPerChannel !== BitsPerChannelType.EIGHT)
|
||||
copy.bitsPerChannel = BitsPerChannelType.EIGHT;
|
||||
// 8 and 16 bits per channel are kept (16-bit becomes a 16-bit
|
||||
// YAPF texture); 32-bit documents are saved as 16-bit.
|
||||
if (copy.bitsPerChannel === BitsPerChannelType.THIRTYTWO)
|
||||
copy.bitsPerChannel = BitsPerChannelType.SIXTEEN;
|
||||
var opts = new PNGSaveOptions();
|
||||
opts.interlaced = false;
|
||||
copy.saveAs(tmp, opts, true, Extension.LOWERCASE);
|
||||
|
|
@ -35,6 +37,6 @@
|
|||
if (mips) args.push("--mips");
|
||||
var ok = yapfRun(args, out);
|
||||
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)");
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -20,16 +20,52 @@ function yapfTool() {
|
|||
return names[0]; // rely on the PATH
|
||||
}
|
||||
|
||||
// Runs the tool; returns true when `expectOut` was produced.
|
||||
function yapfRun(args, expectOut) {
|
||||
// Runs the tool with its output (stdout) going to `stdoutFile` when given.
|
||||
// Returns the tool's error text ("" when it printed none).
|
||||
function yapfExec(args, stdoutFile) {
|
||||
var errFile = yapfTempFile(".txt");
|
||||
var cmd = yapfQuote(yapfTool());
|
||||
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 (expectOut.exists) expectOut.remove();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return new File(Folder.temp.fsName + "/yapf_" + new Date().getTime() + "_" +
|
||||
Math.floor(Math.random() * 1e6) + ext);
|
||||
|
|
|
|||
Loading…
Reference in a new issue