Files
hfut-bishe/client/core/large_image/VipsBackend.cpp
T
2026-05-14 13:30:06 +08:00

272 lines
7.0 KiB
C++

// 必须在任何 Qt 头(会定义宏 signals)之前包含 vips/glib
#ifdef LT_HAVE_VIPS
# include <vips/vips.h>
#endif
#include "large_image/VipsBackend.h"
#include <QByteArray>
#include <QFile>
#include <QFileInfo>
#include <algorithm>
#include <cmath>
#include <cstdlib>
namespace core {
#ifdef LT_HAVE_VIPS
namespace {
bool gReady = false;
QByteArray encodedPath(const QString& path) {
return QFile::encodeName(path);
}
bool saveVipsImageToPngFile(VipsImage* img, const QString& destPath) {
if (!img) {
return false;
}
const QByteArray dest = encodedPath(destPath);
if (vips_pngsave(img, dest.constData(), "compression", 3, nullptr) != 0) {
return false;
}
return true;
}
} // namespace
bool VipsBackend::init(const char* argv0) {
if (gReady) {
return true;
}
if (vips_init(argv0) != 0) {
return false;
}
gReady = true;
return true;
}
void VipsBackend::shutdown() {
if (!gReady) {
return;
}
vips_shutdown();
gReady = false;
}
bool VipsBackend::isAvailable() {
return gReady;
}
bool VipsBackend::probeSize(const QString& path, QSize* out) {
if (!gReady || !out) {
return false;
}
const QByteArray enc = encodedPath(path);
if (enc.isEmpty()) {
return false;
}
VipsImage* in = vips_image_new_from_file(enc.constData(), "access", VIPS_ACCESS_SEQUENTIAL, nullptr);
if (!in) {
return false;
}
const int w = vips_image_get_width(in);
const int h = vips_image_get_height(in);
g_object_unref(in);
if (w <= 0 || h <= 0) {
return false;
}
*out = QSize(w, h);
return true;
}
bool VipsBackend::loadThumbnailQImage(const QString& path, qint64 maxPixels, QImage* out) {
if (!gReady || !out || maxPixels < 1024) {
return false;
}
const QByteArray enc = encodedPath(path);
if (enc.isEmpty()) {
return false;
}
QSize sz;
if (!probeSize(path, &sz)) {
return false;
}
const qint64 pixels = qint64(sz.width()) * qint64(sz.height());
if (pixels <= 0) {
return false;
}
VipsImage* thumb = nullptr;
if (pixels <= maxPixels) {
thumb = vips_image_new_from_file(enc.constData(), "access", VIPS_ACCESS_SEQUENTIAL, nullptr);
if (!thumb) {
return false;
}
} else {
const int targetW =
std::max(1, int(std::floor(std::sqrt(double(maxPixels) * double(sz.width()) / double(sz.height())))));
if (vips_thumbnail(enc.constData(), &thumb, targetW, nullptr) != 0) {
return false;
}
}
void* buf = nullptr;
size_t len = 0;
if (vips_pngsave_buffer(thumb, &buf, &len, "compression", 3, nullptr) != 0) {
g_object_unref(thumb);
return false;
}
g_object_unref(thumb);
const bool ok = out->loadFromData(static_cast<const uchar*>(buf), static_cast<int>(len), "PNG");
g_free(buf);
return ok && !out->isNull();
}
bool VipsBackend::loadRegionToQImage(const QString& sourcePath, const QRect& rectInSource, int maxOutputWidth,
int maxOutputHeight, QImage* out) {
if (!gReady || !out || maxOutputWidth < 32 || maxOutputHeight < 32) {
return false;
}
out->detach();
*out = QImage();
const QByteArray src = encodedPath(sourcePath);
if (src.isEmpty()) {
return false;
}
VipsImage* in = vips_image_new_from_file(src.constData(), "access", VIPS_ACCESS_RANDOM, nullptr);
if (!in) {
return false;
}
const int iw = vips_image_get_width(in);
const int ih = vips_image_get_height(in);
const QRect bounds(0, 0, iw, ih);
QRect r = rectInSource.normalized().intersected(bounds);
if (r.width() <= 0 || r.height() <= 0) {
g_object_unref(in);
return false;
}
VipsImage* cropped = nullptr;
if (vips_crop(in, &cropped, r.left(), r.top(), r.width(), r.height(), nullptr) != 0) {
g_object_unref(in);
return false;
}
g_object_unref(in);
const int cw = r.width();
const int ch = r.height();
VipsImage* toEncode = cropped;
// 视口解码:输出尺寸不应超过 maxOutput*,否则后续绘制阶段再缩小会引入明显的线性滤波“发糊”。
// 这里始终把区域降采样到不超过 maxOutput*(保持宽高比),让 vips 在解码阶段完成高质量缩放。
double scale = 1.0;
if (cw > maxOutputWidth || ch > maxOutputHeight) {
const double sx = static_cast<double>(maxOutputWidth) / static_cast<double>(cw);
const double sy = static_cast<double>(maxOutputHeight) / static_cast<double>(ch);
scale = std::min({sx, sy, 1.0});
}
if (scale < 0.9999) {
VipsImage* resized = nullptr;
if (vips_resize(cropped, &resized, scale, "kernel", VIPS_KERNEL_LANCZOS3, nullptr) != 0) {
g_object_unref(cropped);
return false;
}
g_object_unref(cropped);
toEncode = resized;
}
void* buf = nullptr;
size_t len = 0;
if (vips_pngsave_buffer(toEncode, &buf, &len, "compression", 3, nullptr) != 0) {
g_object_unref(toEncode);
return false;
}
g_object_unref(toEncode);
const bool ok = out->loadFromData(static_cast<const uchar*>(buf), static_cast<int>(len), "PNG");
g_free(buf);
return ok && !out->isNull();
}
bool VipsBackend::saveRectToPng(const QString& sourcePath, const QRect& rectInSource, const QString& destPngPath) {
if (!gReady) {
return false;
}
const QByteArray src = encodedPath(sourcePath);
if (src.isEmpty()) {
return false;
}
VipsImage* in = vips_image_new_from_file(src.constData(), "access", VIPS_ACCESS_RANDOM, nullptr);
if (!in) {
return false;
}
const int iw = vips_image_get_width(in);
const int ih = vips_image_get_height(in);
const QRect bounds(0, 0, iw, ih);
QRect r = rectInSource.normalized().intersected(bounds);
if (r.width() <= 0 || r.height() <= 0) {
g_object_unref(in);
return false;
}
VipsImage* toSave = in;
VipsImage* cropped = nullptr;
if (r != bounds) {
if (vips_crop(in, &cropped, r.left(), r.top(), r.width(), r.height(), nullptr) != 0) {
g_object_unref(in);
return false;
}
toSave = cropped;
}
const bool ok = saveVipsImageToPngFile(toSave, destPngPath);
if (cropped) {
g_object_unref(cropped);
}
g_object_unref(in);
return ok;
}
#else // !LT_HAVE_VIPS
bool VipsBackend::init(const char*) {
return false;
}
void VipsBackend::shutdown() {}
bool VipsBackend::isAvailable() {
return false;
}
bool VipsBackend::probeSize(const QString&, QSize*) {
return false;
}
bool VipsBackend::loadThumbnailQImage(const QString&, qint64, QImage*) {
return false;
}
bool VipsBackend::saveRectToPng(const QString&, const QRect&, const QString&) {
return false;
}
bool VipsBackend::loadRegionToQImage(const QString&, const QRect&, int, int, QImage*) {
return false;
}
#endif // LT_HAVE_VIPS
} // namespace core