update
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
// 必须在任何 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
|
||||
Reference in New Issue
Block a user