From edbbc3c5775bc8ea08275966e8ae8d82721bf534 Mon Sep 17 00:00:00 2001 From: luisangelsm Date: Thu, 26 Feb 2026 16:41:43 +0100 Subject: [PATCH] Implement a small cache to avoid scaling continuously while painting --- YACReader/continuous_page_widget.cpp | 54 +++++++++++++++++++++++++-- YACReader/continuous_page_widget.h | 55 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/YACReader/continuous_page_widget.cpp b/YACReader/continuous_page_widget.cpp index 0f825cfb..fb509a7f 100644 --- a/YACReader/continuous_page_widget.cpp +++ b/YACReader/continuous_page_widget.cpp @@ -42,6 +42,7 @@ void ContinuousPageWidget::setViewModel(ContinuousViewModel *viewModel) } updateGeometry(); + scaledPageCache.invalidateAll(); update(); } @@ -49,6 +50,7 @@ void ContinuousPageWidget::reset() { setMinimumHeight(0); setMaximumHeight(QWIDGETSIZE_MAX); + scaledPageCache.invalidateAll(); updateGeometry(); update(); } @@ -86,6 +88,8 @@ void ContinuousPageWidget::onPageAvailable(int absolutePageIndex) return; } + scaledPageCache.invalidatePage(absolutePageIndex); + // repaint the region where this page lives if (absolutePageIndex < continuousViewModel->numPages()) { QSize scaled = continuousViewModel->scaledPageSize(absolutePageIndex); @@ -106,10 +110,17 @@ void ContinuousPageWidget::paintEvent(QPaintEvent *event) } QPainter painter(this); + scaledPageCache.invalidateForWidth(width()); QRect visibleRect = event->rect(); int firstPage = continuousViewModel->pageAtY(visibleRect.top()); int lastPage = continuousViewModel->pageAtY(visibleRect.bottom()); + firstPage = qBound(0, firstPage, continuousViewModel->numPages() - 1); + lastPage = qBound(0, lastPage, continuousViewModel->numPages() - 1); + + const int cacheMin = std::max(0, firstPage - 1); + const int cacheMax = std::min(continuousViewModel->numPages() - 1, lastPage + 1); + scaledPageCache.keepOnlyRange(cacheMin, cacheMax); int w = width(); for (int i = firstPage; i <= lastPage && i < continuousViewModel->numPages(); ++i) { @@ -124,10 +135,9 @@ void ContinuousPageWidget::paintEvent(QPaintEvent *event) const QImage *img = render->bufferedImage(i); if (img && !img->isNull()) { - if (img->size() != scaled) { - painter.drawImage(pageRect, img->scaled(scaled, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } else { - painter.drawImage(pageRect, *img); + const QImage *drawable = scaledImageForPaint(i, img, scaled, width()); + if (drawable) { + painter.drawImage(pageRect, *drawable); } } else { // placeholder @@ -145,3 +155,39 @@ void ContinuousPageWidget::resizeEvent(QResizeEvent *event) continuousViewModel->setViewportSize(width(), continuousViewModel->viewportHeight()); } } + +const QImage *ContinuousPageWidget::scaledImageForPaint(int pageIndex, const QImage *source, const QSize &targetSize, int effectiveWidth) +{ + if (!source || source->isNull() || targetSize.isEmpty()) { + return nullptr; + } + + if (source->size() == targetSize) { + return source; + } + + scaledPageCache.invalidateForWidth(effectiveWidth); + + auto it = scaledPageCache.pages.find(pageIndex); + const qint64 sourceKey = source->cacheKey(); + + if (it != scaledPageCache.pages.end()) { + const ScaledPageCacheEntry &entry = it.value(); + const bool validEntry = entry.sourceCacheKey == sourceKey + && entry.sourceSize == source->size() + && entry.targetSize == targetSize + && !entry.scaledImage.isNull(); + if (validEntry) { + return &it.value().scaledImage; + } + } + + ScaledPageCacheEntry entry; + entry.sourceCacheKey = sourceKey; + entry.sourceSize = source->size(); + entry.targetSize = targetSize; + entry.scaledImage = source->scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + scaledPageCache.pages.insert(pageIndex, std::move(entry)); + + return &scaledPageCache.pages[pageIndex].scaledImage; +} diff --git a/YACReader/continuous_page_widget.h b/YACReader/continuous_page_widget.h index d40d4bd6..819d7a19 100644 --- a/YACReader/continuous_page_widget.h +++ b/YACReader/continuous_page_widget.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "themable.h" @@ -33,8 +35,61 @@ protected: void applyTheme(const Theme &theme) override; private: + struct ScaledPageCacheEntry { + qint64 sourceCacheKey = 0; + QSize sourceSize; + QSize targetSize; + QImage scaledImage; + }; + + struct ScaledPageCache { + int effectiveWidth = -1; + QHash pages; + + void invalidateAll() + { + effectiveWidth = -1; + pages.clear(); + } + + void invalidateForWidth(int width) + { + if (effectiveWidth != width) { + effectiveWidth = width; + pages.clear(); + } + } + + void invalidatePage(int pageIndex) + { + pages.remove(pageIndex); + } + + void keepOnlyRange(int minPageIndex, int maxPageIndex) + { + if (pages.isEmpty()) { + return; + } + + QList keysToRemove; + keysToRemove.reserve(pages.size()); + for (auto it = pages.constBegin(); it != pages.constEnd(); ++it) { + if (it.key() < minPageIndex || it.key() > maxPageIndex) { + keysToRemove.append(it.key()); + } + } + + for (int key : keysToRemove) { + pages.remove(key); + } + } + }; + + const QImage *scaledImageForPaint(int pageIndex, const QImage *source, const QSize &targetSize, int effectiveWidth); + Render *render = nullptr; ContinuousViewModel *continuousViewModel = nullptr; + ScaledPageCache scaledPageCache; }; #endif // CONTINUOUS_PAGE_WIDGET_H