From 57d39fabbfbd2ddc3397d7bf2254378d8c6856a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sat, 31 Aug 2024 12:41:44 +0200 Subject: [PATCH] Add image processing using opencv --- .gitignore | 1 + YACReader/YACReader.pro | 67 +++++++++++++++++------- YACReader/viewer.cpp | 16 +++--- image_processing/image_processing.pri | 33 ++++++++++++ image_processing/resize_image.cpp | 75 +++++++++++++++++++++++++++ image_processing/resize_image.h | 20 +++++++ 6 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 image_processing/image_processing.pri create mode 100644 image_processing/resize_image.cpp create mode 100644 image_processing/resize_image.h diff --git a/.gitignore b/.gitignore index cfeaafc0..853ff472 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dependencies/create-dmg signapps.sh lib7zip libp7zip +image_processing/opencv # C++ objects and libs *.slo diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro index 08502677..08ebf150 100644 --- a/YACReader/YACReader.pro +++ b/YACReader/YACReader.pro @@ -10,7 +10,23 @@ DEFINES += YACREADER #load default build flags include (../config.pri) + +CONFIG(7zip) { +include(../compressed_archive/wrapper.pri) +} else:CONFIG(unarr) { +include(../compressed_archive/unarr/unarr-wrapper.pri) +} else:CONFIG(libarchive) { +include(../compressed_archive/libarchive/libarchive-wrapper.pri) +} else { + error(No compression backend specified. Did you mess with the build system?) +} + +include(../custom_widgets/custom_widgets_yacreader.pri) +include(../image_processing/image_processing.pri) include (../dependencies/pdf_backend.pri) +include(../shortcuts_management/shortcuts_management.pri) + +include(../third_party/QsLog/QsLog.pri) CONFIG(force_angle) { contains(QMAKE_TARGET.arch, x86_64) { @@ -30,7 +46,7 @@ CONFIG(force_angle) { } } -SOURCES += main.cpp +SOURCES += main.cpp \ INCLUDEPATH += ../common \ ../custom_widgets @@ -55,6 +71,14 @@ win32 { msvc { QMAKE_CXXFLAGS_RELEASE += /MP /Ob2 /Oi /Ot /GT /GL QMAKE_LFLAGS_RELEASE += /LTCG + + # Enable AVX and AVX2 support + QMAKE_CXXFLAGS += /arch:AVX + DEFINES += __AVX__ + + # Enable AVX2 if supported + win32:QMAKE_CXXFLAGS += /arch:AVX2 + DEFINES += __AVX2__ } CONFIG -= embed_manifest_exe } @@ -67,7 +91,27 @@ macx { lessThan(QT_MAJOR_VERSION, 6): QT += macextras } -QT += network widgets core multimedia svg +unix|mingw { + # Enable general SIMD optimizations + QMAKE_CXXFLAGS += -msse2 # Baseline for x86 + + # Architecture-specific optimizations (adjust as needed) + contains(QMAKE_TARGET.arch, x86_64) { + QMAKE_CXXFLAGS += -mavx2 -mfma + DEFINES += __AVX__ __AVX2__ + } else { # Assuming x86 (32-bit) + QMAKE_CXXFLAGS += -msse4.2 + DEFINES += __SSE4_2__ + } + + # ARM + contains(QMAKE_HOST.arch, arm) { + QMAKE_CXXFLAGS += -mfpu=neon -mfloat-abi=hard + DEFINES += __ARM_NEON__ + } +} + +QT += network widgets core multimedia svg concurrent greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat @@ -106,7 +150,7 @@ HEADERS += ../common/comic.h \ ../common/exit_check.h \ ../common/scroll_management.h \ ../common/opengl_checker.h \ - ../common/pdf_comic.h + ../common/pdf_comic.h \ !CONFIG(no_opengl) { HEADERS += ../common/gl/yacreader_flow_gl.h \ @@ -143,31 +187,16 @@ SOURCES += ../common/comic.cpp \ ../common/yacreader_global_gui.cpp \ ../common/exit_check.cpp \ ../common/scroll_management.cpp \ - ../common/opengl_checker.cpp + ../common/opengl_checker.cpp \ !CONFIG(no_opengl) { SOURCES += ../common/gl/yacreader_flow_gl.cpp \ goto_flow_gl.cpp } -include(../custom_widgets/custom_widgets_yacreader.pri) - -CONFIG(7zip) { -include(../compressed_archive/wrapper.pri) -} else:CONFIG(unarr) { -include(../compressed_archive/unarr/unarr-wrapper.pri) -} else:CONFIG(libarchive) { -include(../compressed_archive/libarchive/libarchive-wrapper.pri) -} else { - error(No compression backend specified. Did you mess with the build system?) -} -include(../shortcuts_management/shortcuts_management.pri) - RESOURCES += yacreader_images.qrc \ yacreader_files.qrc -include(../third_party/QsLog/QsLog.pri) - RC_FILE = icon.rc macx { diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index 7d6fe242..82f857a3 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -16,6 +16,7 @@ #include "notifications_label_widget.h" #include "comic_db.h" #include "shortcuts_manager.h" +#include "resize_image.h" #include "opengl_checker.h" @@ -387,16 +388,15 @@ void Viewer::updateContentSize() if (zoom != 100) { pagefit.scale(floor(pagefit.width() * zoom / 100.0f), 0, Qt::KeepAspectRatioByExpanding); } - // apply scaling + // apply size to the container content->resize(pagefit); - // TODO: updtateContentSize should only scale the pixmap once - if (devicePixelRatioF() > 1) // only in HDPI displays - { - QPixmap page = currentPage->scaled(content->width() * devicePixelRatioF(), content->height() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - page.setDevicePixelRatio(devicePixelRatioF()); - content->setPixmap(page); - } + // scale the image to fit the container + auto devicePixelRatioF = content->devicePixelRatioF(); + QLOG_ERROR() << "src size: " << currentPage->size() << " content size: " << content->size() << " target size " << QSize(content->width() * devicePixelRatioF, content->height() * devicePixelRatioF); + QPixmap page = smartScalePixmap(*currentPage, content->width() * devicePixelRatioF, content->height() * devicePixelRatioF); // currentPage->scaled(content->width() * devicePixelRatioF(), content->height() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + page.setDevicePixelRatio(devicePixelRatioF); + content->setPixmap(page); emit backgroundChanges(); } diff --git a/image_processing/image_processing.pri b/image_processing/image_processing.pri new file mode 100644 index 00000000..ebd64d87 --- /dev/null +++ b/image_processing/image_processing.pri @@ -0,0 +1,33 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += \ + $$PWD/resize_image.cpp + +HEADERS += \ + $$PWD/resize_image.h + +# include opencv, and link against it (core and imgproc) +# windows +win32 { + INCLUDEPATH += $$PWD/opencv/build/include + DEPENDPATH += $$PWD/opencv/build/include +} +# release +win32:CONFIG(release, debug|release) { + + LIBS += -L$$PWD/opencv/build/x64/vc16/lib \ + -lopencv_world490 +} +# debug +win32:CONFIG(debug, debug|release) { + LIBS += -L$$PWD/opencv/build/x64/vc16/lib \ + -lopencv_world490d +} + + +#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../../../opencv/build/x64/vc15/lib/ -lopencv_world341 +#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../../../opencv/build/x64/vc15/lib/ -lopencv_world341d + +#INCLUDEPATH += $$PWD/../../../../opencv/build/include +#DEPENDPATH += $$PWD/../../../../opencv/build/include diff --git a/image_processing/resize_image.cpp b/image_processing/resize_image.cpp new file mode 100644 index 00000000..bb352f71 --- /dev/null +++ b/image_processing/resize_image.cpp @@ -0,0 +1,75 @@ +#include "resize_image.h" + +// include opencv +#include + +QPixmap scalePixmapBicubic(const QPixmap &pixmap, int width, int height); +QPixmap scalePixmapLanczos(const QPixmap &pixmap, int width, int height); +QPixmap scalePixmapArea(const QPixmap &pixmap, int width, int height); +QPixmap scalePixmapOpenCV(const QPixmap &pixmap, int width, int height, cv::InterpolationFlags flags); + +QPixmap smartScalePixmap(const QPixmap &pixmap, int width, int height) +{ + const int w = pixmap.width(); + const int h = pixmap.height(); + if ((w == width && h == height) || pixmap.isNull()) { + return pixmap; + } + + if (w <= width && h <= height) { // upscaling + return scalePixmapLanczos(pixmap, width, height); + } + + return pixmap; +} + +QPixmap scalePixmap(const QPixmap &pixmap, int width, int height, ScaleMethod method) +{ + const int w = pixmap.width(); + const int h = pixmap.height(); + if (w == width && h == height) { + return pixmap; + } + + switch (method) { + case ScaleMethod::QtFast: + return pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation); + case ScaleMethod::QtSmooth: + return pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + case ScaleMethod::Bicubic: + return scalePixmapBicubic(pixmap, width, height); + case ScaleMethod::Lanczos: + return scalePixmapLanczos(pixmap, width, height); + case ScaleMethod::Area: + return scalePixmapArea(pixmap, width, height); + } +} + +QPixmap scalePixmapBicubic(const QPixmap &pixmap, int width, int height) +{ + return scalePixmapOpenCV(pixmap, width, height, cv::INTER_CUBIC); +} + +QPixmap scalePixmapLanczos(const QPixmap &pixmap, int width, int height) +{ + return scalePixmapOpenCV(pixmap, width, height, cv::INTER_LANCZOS4); +} + +QPixmap scalePixmapArea(const QPixmap &pixmap, int width, int height) +{ + return scalePixmapOpenCV(pixmap, width, height, cv::INTER_AREA); +} + +QPixmap scalePixmapOpenCV(const QPixmap &pixmap, int width, int height, cv::InterpolationFlags flags) +{ + QImage qimage = pixmap.toImage(); + cv::Mat mat = cv::Mat(qimage.height(), qimage.width(), CV_8UC4, (void *)qimage.bits(), qimage.bytesPerLine()); + + cv::Mat resizedMat; + cv::resize(mat, resizedMat, cv::Size(width, height), 0, 0, flags); + + QImage resizedQImage((uchar *)resizedMat.data, resizedMat.cols, resizedMat.rows, resizedMat.step, QImage::Format_ARGB32); + QPixmap resizedPixmap = QPixmap::fromImage(resizedQImage); + + return resizedPixmap; +} diff --git a/image_processing/resize_image.h b/image_processing/resize_image.h new file mode 100644 index 00000000..17ea04a3 --- /dev/null +++ b/image_processing/resize_image.h @@ -0,0 +1,20 @@ +#ifndef RESIZE_IMAGE_H +#define RESIZE_IMAGE_H + +#include +#include +#include + +enum class ScaleMethod { + QtFast, + QtSmooth, // Bilinear + Bicubic, + Lanczos, + Area + +}; + +QPixmap smartScalePixmap(const QPixmap &pixmap, int width, int height); +QPixmap scalePixmap(const QPixmap &pixmap, int width, int height, ScaleMethod method = ScaleMethod::QtSmooth); + +#endif // RESIZE_IMAGE_H