commit 58103277
Add prefetch in fullscreen
Changed files
| M | src/imageviewwidget.cpp before |
| M | src/imageviewwidget.h before |
| M | src/mainwindow.cpp before |
| M | src/mainwindow.h before |
diff --git a/src/imageviewwidget.cpp b/src/imageviewwidget.cpp
index 1f8b39e..aa06e7e 100644
--- a/src/imageviewwidget.cpp
+++ b/src/imageviewwidget.cpp
@@ -6,6 +6,35 @@
#include <QImageReader>
#include <QPalette>
#include <algorithm>
+#include <iostream>
+
+// --- ImageLoadWorker ---
+
+ImageLoadWorker::ImageLoadWorker(const QString &path, QObject *parent)
+ : QThread(parent), m_path(path)
+{
+}
+
+void ImageLoadWorker::run()
+{
+ QImageReader reader(m_path);
+ reader.setAutoTransform(true);
+ reader.setDecideFormatFromContent(true);
+ reader.setAllocationLimit(0);
+ QImage image = reader.read();
+
+ QPixmap pixmap;
+ if (!image.isNull()) {
+ if (image.format() != QImage::Format_RGB32
+ && image.format() != QImage::Format_ARGB32
+ && image.format() != QImage::Format_ARGB32_Premultiplied)
+ image = image.convertToFormat(QImage::Format_ARGB32);
+ pixmap = QPixmap::fromImage(image);
+ }
+ emit imageLoaded(m_path, pixmap);
+}
+
+// --- ImageViewWidget ---
ImageViewWidget::ImageViewWidget(QWidget *parent)
: QWidget(parent)
@@ -15,29 +44,93 @@ ImageViewWidget::ImageViewWidget(QWidget *parent)
setBackgroundColor("black");
}
-void ImageViewWidget::setImage(const QString &path)
+QPixmap ImageViewWidget::loadImageFromDisk(const QString &path)
{
QImageReader reader(path);
reader.setAutoTransform(true);
reader.setDecideFormatFromContent(true);
- // Disable the allocation limit so very large images can be viewed.
- // The default 128 MB limit rejects high-resolution photos.
reader.setAllocationLimit(0);
QImage image = reader.read();
if (!image.isNull()) {
- // Ensure the image is in a high-quality format
- if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied)
+ if (image.format() != QImage::Format_RGB32
+ && image.format() != QImage::Format_ARGB32
+ && image.format() != QImage::Format_ARGB32_Premultiplied)
image = image.convertToFormat(QImage::Format_ARGB32);
- m_pixmap = QPixmap::fromImage(image);
+ return QPixmap::fromImage(image);
+ }
+ return QPixmap();
+}
+
+void ImageViewWidget::setImage(const QString &path)
+{
+ m_currentPath = path;
+
+ // Check cache first
+ if (m_cache.contains(path)) {
+ m_pixmap = m_cache[path];
+ } else {
+ m_pixmap = loadImageFromDisk(path);
+ m_cache[path] = m_pixmap;
}
- else
- m_pixmap = QPixmap();
m_zoomMode = FitToScreen;
m_zoomFactor = 1.0;
m_offset = QPoint(0, 0);
update();
+
+ // Trigger prefetch of neighbors if they have been set
+ if (!m_neighborPaths.isEmpty()) {
+ // Evict entries that are no longer in the neighbor window
+ QSet<QString> keep(m_neighborPaths.begin(), m_neighborPaths.end());
+ keep.insert(m_currentPath);
+ for (auto it = m_cache.begin(); it != m_cache.end(); ) {
+ if (!keep.contains(it.key()))
+ it = m_cache.erase(it);
+ else
+ ++it;
+ }
+
+ // Prefetch neighbors that are not yet cached
+ for (const QString &neighbor : m_neighborPaths) {
+ prefetchImage(neighbor);
+ }
+ }
+}
+
+void ImageViewWidget::prefetchImage(const QString &path)
+{
+ if (m_cache.contains(path) || m_pendingLoads.contains(path))
+ return;
+
+ m_pendingLoads.insert(path);
+ auto *worker = new ImageLoadWorker(path, this);
+ connect(worker, &ImageLoadWorker::imageLoaded, this, &ImageViewWidget::onImageLoaded);
+ connect(worker, &QThread::finished, worker, &QObject::deleteLater);
+ worker->start();
+}
+
+void ImageViewWidget::onImageLoaded(const QString &path, const QPixmap &pixmap)
+{
+ m_pendingLoads.remove(path);
+
+ // Only cache if path is still relevant (current or a neighbor)
+ QSet<QString> keep(m_neighborPaths.begin(), m_neighborPaths.end());
+ keep.insert(m_currentPath);
+ if (keep.contains(path)) {
+ m_cache[path] = pixmap;
+ }
+}
+
+void ImageViewWidget::setNeighborPaths(const QStringList &paths)
+{
+ m_neighborPaths = paths;
+}
+
+void ImageViewWidget::clearCache()
+{
+ m_cache.clear();
+ m_neighborPaths.clear();
}
void ImageViewWidget::setBackgroundColor(const QString &color)
diff --git a/src/imageviewwidget.h b/src/imageviewwidget.h
index 201d498..b7ebca4 100644
--- a/src/imageviewwidget.h
+++ b/src/imageviewwidget.h
@@ -5,12 +5,31 @@
#include <QPixmap>
#include <QString>
#include <QPoint>
+#include <QMap>
+#include <QStringList>
+#include <QThread>
+
+class ImageLoadWorker : public QThread
+{
+ Q_OBJECT
+public:
+ explicit ImageLoadWorker(const QString &path, QObject *parent = nullptr);
+ void run() override;
+signals:
+ void imageLoaded(const QString &path, const QPixmap &pixmap);
+private:
+ QString m_path;
+};
class ImageViewWidget : public QWidget
{
Q_OBJECT
public:
+ // Number of images to read ahead and keep behind the current image
+ static constexpr int CacheReadAhead = 2;
+ static constexpr int CacheKeepBehind = 2;
+
enum ZoomMode { FitToScreen, OriginalSize, CustomZoom };
explicit ImageViewWidget(QWidget *parent = nullptr);
@@ -22,6 +41,10 @@ public:
void zoomOriginal();
void zoomFitToScreen();
+ void prefetchImage(const QString &path);
+ void setNeighborPaths(const QStringList &paths);
+ void clearCache();
+
signals:
void closeRequested();
void nextRequested();
@@ -37,11 +60,19 @@ protected:
private:
void clampOffset(int imgW, int imgH);
+ QPixmap loadImageFromDisk(const QString &path);
+ void onImageLoaded(const QString &path, const QPixmap &pixmap);
QPixmap m_pixmap;
ZoomMode m_zoomMode = FitToScreen;
double m_zoomFactor = 1.0;
QString m_backgroundColor = "black";
+ QString m_currentPath;
+
+ // Image cache: path -> pixmap
+ QMap<QString, QPixmap> m_cache;
+ QStringList m_neighborPaths; // ordered list of paths around current image
+ QSet<QString> m_pendingLoads; // paths currently being loaded in background
// Panning state
QPoint m_offset; // current pan offset (pixels)
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index e5f1520..3c558ce 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -289,6 +289,7 @@ void MainWindow::enterSingleImageMode(const QModelIndex &index)
m_imageView->setBackgroundColor(m_backgroundColorPreference);
QString path = m_imageModel->filePath(index);
+ m_imageView->setNeighborPaths(computeNeighborPaths(index));
m_imageView->setImage(path);
m_stack->setCurrentWidget(m_imageView);
m_imageView->setFocus();
@@ -305,6 +306,7 @@ void MainWindow::leaveSingleImageMode()
m_imageViewWasFullScreen = true;
setFullScreenMode(false);
}
+ m_imageView->clearCache();
m_stack->setCurrentWidget(m_browserPage);
m_menuBar->show();
if (m_folderDockWasVisible)
@@ -314,6 +316,34 @@ void MainWindow::leaveSingleImageMode()
m_listView->setFocus();
}
+QStringList MainWindow::computeNeighborPaths(const QModelIndex &index) const
+{
+ QStringList paths;
+ int row = index.row();
+
+ // Collect up to CacheKeepBehind non-folder images before current
+ int count = 0;
+ for (int i = row - 1; i >= 0 && count < ImageViewWidget::CacheKeepBehind; --i) {
+ QModelIndex idx = m_imageModel->index(i);
+ if (!m_imageModel->isFolder(idx)) {
+ paths.prepend(m_imageModel->filePath(idx));
+ ++count;
+ }
+ }
+
+ // Collect up to CacheReadAhead non-folder images after current
+ count = 0;
+ for (int i = row + 1; i < m_imageModel->rowCount() && count < ImageViewWidget::CacheReadAhead; ++i) {
+ QModelIndex idx = m_imageModel->index(i);
+ if (!m_imageModel->isFolder(idx)) {
+ paths.append(m_imageModel->filePath(idx));
+ ++count;
+ }
+ }
+
+ return paths;
+}
+
void MainWindow::setFullScreenMode(bool fullScreen)
{
if (fullScreen == isFullScreen())
@@ -459,9 +489,4 @@ void MainWindow::resetLayoutToDefault()
// Show both docks
m_folderDock->show();
m_previewDock->show();
-
- // Reset window to normal state
- /*if (isMaximized() || isFullScreen()) {
- showNormal();
- }*/
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 0dbbcf0..e981315 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -36,6 +36,7 @@ private:
void showPreview(const QModelIndex &index);
void enterSingleImageMode(const QModelIndex &index);
void leaveSingleImageMode();
+ QStringList computeNeighborPaths(const QModelIndex &index) const;
void keyPressEvent(QKeyEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;