git repos / SagoImageBrowser

blame: src/thumbnailworker.cpp

normal view · raw

cec3b5da Poul Sander 2026-02-19 19:17 1
#include "thumbnailworker.h"
ac476ca8 Poul Sander 2026-05-18 16:47 2
ac476ca8 Poul Sander 2026-05-18 16:47 3
#include "thumbnailcache.h"
ac476ca8 Poul Sander 2026-05-18 16:47 4
80a9926d Poul Sander 2026-05-19 20:28 5
#include <QFile>
cec3b5da Poul Sander 2026-02-19 19:17 6
#include <QImageReader>
ac476ca8 Poul Sander 2026-05-18 16:47 7
80a9926d Poul Sander 2026-05-19 20:28 8
#include <fcntl.h>
80a9926d Poul Sander 2026-05-19 20:28 9
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 10
ac476ca8 Poul Sander 2026-05-18 16:47 11
namespace
ac476ca8 Poul Sander 2026-05-18 16:47 12
{
ac476ca8 Poul Sander 2026-05-18 16:47 13
constexpr int kDisplaySize = 128;
ac476ca8 Poul Sander 2026-05-18 16:47 14
4d6b7476 Poul Sander 2026-05-30 16:39 15
// THREADING: Pure function called from a worker thread.  No shared mutable state.
ac476ca8 Poul Sander 2026-05-18 16:47 16
QImage downscaleForDisplay(const QImage &src)
ac476ca8 Poul Sander 2026-05-18 16:47 17
{
ac476ca8 Poul Sander 2026-05-18 16:47 18
    if (src.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 19
        return src;
ac476ca8 Poul Sander 2026-05-18 16:47 20
    if (src.width() <= kDisplaySize && src.height() <= kDisplaySize)
ac476ca8 Poul Sander 2026-05-18 16:47 21
        return src;
ac476ca8 Poul Sander 2026-05-18 16:47 22
    return src.scaled(kDisplaySize, kDisplaySize,
ac476ca8 Poul Sander 2026-05-18 16:47 23
                      Qt::KeepAspectRatio,
ac476ca8 Poul Sander 2026-05-18 16:47 24
                      Qt::SmoothTransformation);
ac476ca8 Poul Sander 2026-05-18 16:47 25
}
ac476ca8 Poul Sander 2026-05-18 16:47 26
} // namespace
cec3b5da Poul Sander 2026-02-19 19:17 27
80a9926d Poul Sander 2026-05-19 20:28 28
ThumbnailWorker::ThumbnailWorker(const QByteArray &path,
6fcd6c90 Poul Sander 2026-02-19 21:12 29
                                 int row,
ac476ca8 Poul Sander 2026-05-18 16:47 30
                                 ThumbnailCache::Size size,
6fcd6c90 Poul Sander 2026-02-19 21:12 31
                                 std::atomic_bool *cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 32
    : m_path(path),
6fcd6c90 Poul Sander 2026-02-19 21:12 33
      m_row(row),
ac476ca8 Poul Sander 2026-05-18 16:47 34
      m_size(size),
6fcd6c90 Poul Sander 2026-02-19 21:12 35
      m_cancelFlag(cancelFlag)
cec3b5da Poul Sander 2026-02-19 19:17 36
{
cec3b5da Poul Sander 2026-02-19 19:17 37
    setAutoDelete(true);
cec3b5da Poul Sander 2026-02-19 19:17 38
}
cec3b5da Poul Sander 2026-02-19 19:17 39
4d6b7476 Poul Sander 2026-05-30 16:39 40
// THREADING: This function runs on a QThreadPool worker thread, NOT the main
4d6b7476 Poul Sander 2026-05-30 16:39 41
// thread.  It must not access any ImageModel state.  The only shared data it
4d6b7476 Poul Sander 2026-05-30 16:39 42
// touches is the atomic m_cancelFlag (for cooperative cancellation).  All other
4d6b7476 Poul Sander 2026-05-30 16:39 43
// members (m_path, m_row, m_size) are immutable after construction.  Results
4d6b7476 Poul Sander 2026-05-30 16:39 44
// are delivered to the main thread via the finished() signal connected with
4d6b7476 Poul Sander 2026-05-30 16:39 45
// Qt::QueuedConnection.
cec3b5da Poul Sander 2026-02-19 19:17 46
void ThumbnailWorker::run()
cec3b5da Poul Sander 2026-02-19 19:17 47
{
6fcd6c90 Poul Sander 2026-02-19 21:12 48
    if (*m_cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 49
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 50
ac476ca8 Poul Sander 2026-05-18 16:47 51
    // Per the spec, never thumbnail files that live inside the cache itself.
ac476ca8 Poul Sander 2026-05-18 16:47 52
    if (ThumbnailCache::isInsideCache(m_path))
ac476ca8 Poul Sander 2026-05-18 16:47 53
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 54
ac476ca8 Poul Sander 2026-05-18 16:47 55
    const int target = static_cast<int>(m_size);
ac476ca8 Poul Sander 2026-05-18 16:47 56
ac476ca8 Poul Sander 2026-05-18 16:47 57
    QImage cached = ThumbnailCache::load(m_path, m_size);
ac476ca8 Poul Sander 2026-05-18 16:47 58
    if (*m_cancelFlag)
ac476ca8 Poul Sander 2026-05-18 16:47 59
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 60
ac476ca8 Poul Sander 2026-05-18 16:47 61
    if (!cached.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 62
    {
ac476ca8 Poul Sander 2026-05-18 16:47 63
        emit finished(m_row, downscaleForDisplay(cached));
ac476ca8 Poul Sander 2026-05-18 16:47 64
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 65
    }
ac476ca8 Poul Sander 2026-05-18 16:47 66
80a9926d Poul Sander 2026-05-19 20:28 67
    // Open the file via POSIX open() so that paths with non-UTF-8 bytes
80a9926d Poul Sander 2026-05-19 20:28 68
    // (e.g. Latin-1 encoded filenames) are handled correctly.
80a9926d Poul Sander 2026-05-19 20:28 69
    int fd = ::open(m_path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 70
    if (fd < 0)
80a9926d Poul Sander 2026-05-19 20:28 71
        return;
80a9926d Poul Sander 2026-05-19 20:28 72
80a9926d Poul Sander 2026-05-19 20:28 73
    QFile file;
80a9926d Poul Sander 2026-05-19 20:28 74
    if (!file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle))
80a9926d Poul Sander 2026-05-19 20:28 75
    {
80a9926d Poul Sander 2026-05-19 20:28 76
        ::close(fd);
80a9926d Poul Sander 2026-05-19 20:28 77
        return;
80a9926d Poul Sander 2026-05-19 20:28 78
    }
80a9926d Poul Sander 2026-05-19 20:28 79
80a9926d Poul Sander 2026-05-19 20:28 80
    QImageReader reader(&file);
cec3b5da Poul Sander 2026-02-19 19:17 81
    reader.setAutoTransform(true);
cec3b5da Poul Sander 2026-02-19 19:17 82
7889168c Poul Sander 2026-02-26 18:27 83
    // Scale during decoding to avoid hitting the allocation limit
7889168c Poul Sander 2026-02-26 18:27 84
    // on very large images (Qt 6 defaults to 128 MB).
7889168c Poul Sander 2026-02-26 18:27 85
    QSize fullSize = reader.size();
ac476ca8 Poul Sander 2026-05-18 16:47 86
    if (fullSize.isValid())
ac476ca8 Poul Sander 2026-05-18 16:47 87
    {
ac476ca8 Poul Sander 2026-05-18 16:47 88
        QSize scaled = fullSize.scaled(target, target, Qt::KeepAspectRatio);
ac476ca8 Poul Sander 2026-05-18 16:47 89
        reader.setScaledSize(scaled);
7889168c Poul Sander 2026-02-26 18:27 90
    }
7889168c Poul Sander 2026-02-26 18:27 91
cec3b5da Poul Sander 2026-02-19 19:17 92
    QImage image = reader.read();
6fcd6c90 Poul Sander 2026-02-19 21:12 93
6fcd6c90 Poul Sander 2026-02-19 21:12 94
    if (*m_cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 95
        return;
cec3b5da Poul Sander 2026-02-19 19:17 96
ac476ca8 Poul Sander 2026-05-18 16:47 97
    if (image.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 98
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 99
ac476ca8 Poul Sander 2026-05-18 16:47 100
    // Ensure the longest side is exactly the target (the decoder hint may have
ac476ca8 Poul Sander 2026-05-18 16:47 101
    // been ignored for some formats). Aspect ratio preserved, no padding canvas.
ac476ca8 Poul Sander 2026-05-18 16:47 102
    QImage thumb;
ac476ca8 Poul Sander 2026-05-18 16:47 103
    if (image.width() > target || image.height() > target)
ac476ca8 Poul Sander 2026-05-18 16:47 104
        thumb = image.scaled(target, target,
ac476ca8 Poul Sander 2026-05-18 16:47 105
                             Qt::KeepAspectRatio,
ac476ca8 Poul Sander 2026-05-18 16:47 106
                             Qt::SmoothTransformation);
ac476ca8 Poul Sander 2026-05-18 16:47 107
    else
ac476ca8 Poul Sander 2026-05-18 16:47 108
        thumb = image;
ac476ca8 Poul Sander 2026-05-18 16:47 109
ac476ca8 Poul Sander 2026-05-18 16:47 110
    ThumbnailCache::save(m_path, m_size, thumb);
ac476ca8 Poul Sander 2026-05-18 16:47 111
ac476ca8 Poul Sander 2026-05-18 16:47 112
    if (*m_cancelFlag)
ac476ca8 Poul Sander 2026-05-18 16:47 113
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 114
ac476ca8 Poul Sander 2026-05-18 16:47 115
    emit finished(m_row, downscaleForDisplay(thumb));
cec3b5da Poul Sander 2026-02-19 19:17 116
}
1970-01-01 00:00 117