// 必须在任何 Qt 头(会定义宏 signals)之前包含 vips/glib #ifdef LT_HAVE_VIPS # include #endif #include "large_image/VipsBackend.h" #include #include #include #include #include #include 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(buf), static_cast(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(maxOutputWidth) / static_cast(cw); const double sy = static_cast(maxOutputHeight) / static_cast(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(buf), static_cast(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