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
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <QImage>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
namespace core {
|
||||
|
||||
/// 基于 libvips 的大图 I/O:随机访问裁剪、流式写 PNG、缩略预览,避免整图进内存。
|
||||
/// 未编译 libvips 时 API 返回 false,由调用方回退到 Qt。
|
||||
class VipsBackend {
|
||||
public:
|
||||
/// 须在 QApplication 创建后调用一次,argv0 建议为 argv[0]。
|
||||
static bool init(const char* argv0);
|
||||
static void shutdown();
|
||||
|
||||
static bool isAvailable();
|
||||
|
||||
/// 打开文件读取宽高(轻量,不解码全图)
|
||||
static bool probeSize(const QString& path, QSize* out);
|
||||
|
||||
/// 生成预览图(总像素约不超过 maxPixels),用于裁剪对话框 / 画布
|
||||
static bool loadThumbnailQImage(const QString& path, qint64 maxPixels, QImage* out);
|
||||
|
||||
/// 将原图中矩形区域(像素坐标,含边界)无损写入 PNG;区域为整图时直接流式转存。
|
||||
/// 适合 GB 级 PNG,不构建整幅 QImage。
|
||||
static bool saveRectToPng(const QString& sourcePath, const QRect& rectInSource, const QString& destPngPath);
|
||||
|
||||
/// 裁剪原图矩形区域;若超过 maxOutput* 则按比例缩小(不降采样整图,只处理该区域)。
|
||||
static bool loadRegionToQImage(const QString& sourcePath, const QRect& rectInSource, int maxOutputWidth,
|
||||
int maxOutputHeight, QImage* out);
|
||||
|
||||
private:
|
||||
VipsBackend() = default;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
Reference in New Issue
Block a user