git repos / SagoImageBrowser

blame: src/thumbnailworker.cpp

normal view · raw

14cf904f Poul Sander 2026-06-22 18:18 1
/*
14cf904f Poul Sander 2026-06-22 18:18 2
MIT License
14cf904f Poul Sander 2026-06-22 18:18 3
14cf904f Poul Sander 2026-06-22 18:18 4
Copyright (c) 2026 Poul Sander
14cf904f Poul Sander 2026-06-22 18:18 5
14cf904f Poul Sander 2026-06-22 18:18 6
Permission is hereby granted, free of charge, to any person obtaining a copy
14cf904f Poul Sander 2026-06-22 18:18 7
of this software and associated documentation files (the "Software"), to deal
14cf904f Poul Sander 2026-06-22 18:18 8
in the Software without restriction, including without limitation the rights
14cf904f Poul Sander 2026-06-22 18:18 9
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14cf904f Poul Sander 2026-06-22 18:18 10
copies of the Software, and to permit persons to whom the Software is
14cf904f Poul Sander 2026-06-22 18:18 11
furnished to do so, subject to the following conditions:
14cf904f Poul Sander 2026-06-22 18:18 12
14cf904f Poul Sander 2026-06-22 18:18 13
The above copyright notice and this permission notice shall be included in all
14cf904f Poul Sander 2026-06-22 18:18 14
copies or substantial portions of the Software.
14cf904f Poul Sander 2026-06-22 18:18 15
14cf904f Poul Sander 2026-06-22 18:18 16
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14cf904f Poul Sander 2026-06-22 18:18 17
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14cf904f Poul Sander 2026-06-22 18:18 18
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14cf904f Poul Sander 2026-06-22 18:18 19
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14cf904f Poul Sander 2026-06-22 18:18 20
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14cf904f Poul Sander 2026-06-22 18:18 21
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
14cf904f Poul Sander 2026-06-22 18:18 22
SOFTWARE.
14cf904f Poul Sander 2026-06-22 18:18 23
14cf904f Poul Sander 2026-06-22 18:18 24
*/
14cf904f Poul Sander 2026-06-22 18:18 25
cec3b5da Poul Sander 2026-02-19 19:17 26
#include "thumbnailworker.h"
ac476ca8 Poul Sander 2026-05-18 16:47 27
ac476ca8 Poul Sander 2026-05-18 16:47 28
#include "thumbnailcache.h"
ac476ca8 Poul Sander 2026-05-18 16:47 29
80a9926d Poul Sander 2026-05-19 20:28 30
#include <QFile>
cec3b5da Poul Sander 2026-02-19 19:17 31
#include <QImageReader>
ac476ca8 Poul Sander 2026-05-18 16:47 32
80a9926d Poul Sander 2026-05-19 20:28 33
#include <fcntl.h>
80a9926d Poul Sander 2026-05-19 20:28 34
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 35
ac476ca8 Poul Sander 2026-05-18 16:47 36
namespace
ac476ca8 Poul Sander 2026-05-18 16:47 37
{
ac476ca8 Poul Sander 2026-05-18 16:47 38
constexpr int kDisplaySize = 128;
ac476ca8 Poul Sander 2026-05-18 16:47 39
4d6b7476 Poul Sander 2026-05-30 16:39 40
// THREADING: Pure function called from a worker thread.  No shared mutable state.
ac476ca8 Poul Sander 2026-05-18 16:47 41
QImage downscaleForDisplay(const QImage &src)
ac476ca8 Poul Sander 2026-05-18 16:47 42
{
ac476ca8 Poul Sander 2026-05-18 16:47 43
    if (src.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 44
        return src;
ac476ca8 Poul Sander 2026-05-18 16:47 45
    if (src.width() <= kDisplaySize && src.height() <= kDisplaySize)
ac476ca8 Poul Sander 2026-05-18 16:47 46
        return src;
ac476ca8 Poul Sander 2026-05-18 16:47 47
    return src.scaled(kDisplaySize, kDisplaySize,
ac476ca8 Poul Sander 2026-05-18 16:47 48
                      Qt::KeepAspectRatio,
ac476ca8 Poul Sander 2026-05-18 16:47 49
                      Qt::SmoothTransformation);
ac476ca8 Poul Sander 2026-05-18 16:47 50
}
ac476ca8 Poul Sander 2026-05-18 16:47 51
} // namespace
cec3b5da Poul Sander 2026-02-19 19:17 52
80a9926d Poul Sander 2026-05-19 20:28 53
ThumbnailWorker::ThumbnailWorker(const QByteArray &path,
6fcd6c90 Poul Sander 2026-02-19 21:12 54
                                 int row,
ac476ca8 Poul Sander 2026-05-18 16:47 55
                                 ThumbnailCache::Size size,
6fcd6c90 Poul Sander 2026-02-19 21:12 56
                                 std::atomic_bool *cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 57
    : m_path(path),
6fcd6c90 Poul Sander 2026-02-19 21:12 58
      m_row(row),
ac476ca8 Poul Sander 2026-05-18 16:47 59
      m_size(size),
6fcd6c90 Poul Sander 2026-02-19 21:12 60
      m_cancelFlag(cancelFlag)
cec3b5da Poul Sander 2026-02-19 19:17 61
{
cec3b5da Poul Sander 2026-02-19 19:17 62
    setAutoDelete(true);
cec3b5da Poul Sander 2026-02-19 19:17 63
}
cec3b5da Poul Sander 2026-02-19 19:17 64
4d6b7476 Poul Sander 2026-05-30 16:39 65
// THREADING: This function runs on a QThreadPool worker thread, NOT the main
4d6b7476 Poul Sander 2026-05-30 16:39 66
// thread.  It must not access any ImageModel state.  The only shared data it
4d6b7476 Poul Sander 2026-05-30 16:39 67
// touches is the atomic m_cancelFlag (for cooperative cancellation).  All other
4d6b7476 Poul Sander 2026-05-30 16:39 68
// members (m_path, m_row, m_size) are immutable after construction.  Results
4d6b7476 Poul Sander 2026-05-30 16:39 69
// are delivered to the main thread via the finished() signal connected with
4d6b7476 Poul Sander 2026-05-30 16:39 70
// Qt::QueuedConnection.
cec3b5da Poul Sander 2026-02-19 19:17 71
void ThumbnailWorker::run()
cec3b5da Poul Sander 2026-02-19 19:17 72
{
6fcd6c90 Poul Sander 2026-02-19 21:12 73
    if (*m_cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 74
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 75
ac476ca8 Poul Sander 2026-05-18 16:47 76
    // Per the spec, never thumbnail files that live inside the cache itself.
ac476ca8 Poul Sander 2026-05-18 16:47 77
    if (ThumbnailCache::isInsideCache(m_path))
ac476ca8 Poul Sander 2026-05-18 16:47 78
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 79
ac476ca8 Poul Sander 2026-05-18 16:47 80
    const int target = static_cast<int>(m_size);
ac476ca8 Poul Sander 2026-05-18 16:47 81
ac476ca8 Poul Sander 2026-05-18 16:47 82
    QImage cached = ThumbnailCache::load(m_path, m_size);
ac476ca8 Poul Sander 2026-05-18 16:47 83
    if (*m_cancelFlag)
ac476ca8 Poul Sander 2026-05-18 16:47 84
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 85
ac476ca8 Poul Sander 2026-05-18 16:47 86
    if (!cached.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 87
    {
ac476ca8 Poul Sander 2026-05-18 16:47 88
        emit finished(m_row, downscaleForDisplay(cached));
ac476ca8 Poul Sander 2026-05-18 16:47 89
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 90
    }
ac476ca8 Poul Sander 2026-05-18 16:47 91
80a9926d Poul Sander 2026-05-19 20:28 92
    // Open the file via POSIX open() so that paths with non-UTF-8 bytes
80a9926d Poul Sander 2026-05-19 20:28 93
    // (e.g. Latin-1 encoded filenames) are handled correctly.
80a9926d Poul Sander 2026-05-19 20:28 94
    int fd = ::open(m_path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 95
    if (fd < 0)
80a9926d Poul Sander 2026-05-19 20:28 96
        return;
80a9926d Poul Sander 2026-05-19 20:28 97
80a9926d Poul Sander 2026-05-19 20:28 98
    QFile file;
80a9926d Poul Sander 2026-05-19 20:28 99
    if (!file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle))
80a9926d Poul Sander 2026-05-19 20:28 100
    {
80a9926d Poul Sander 2026-05-19 20:28 101
        ::close(fd);
80a9926d Poul Sander 2026-05-19 20:28 102
        return;
80a9926d Poul Sander 2026-05-19 20:28 103
    }
80a9926d Poul Sander 2026-05-19 20:28 104
80a9926d Poul Sander 2026-05-19 20:28 105
    QImageReader reader(&file);
cec3b5da Poul Sander 2026-02-19 19:17 106
    reader.setAutoTransform(true);
cec3b5da Poul Sander 2026-02-19 19:17 107
7889168c Poul Sander 2026-02-26 18:27 108
    // Scale during decoding to avoid hitting the allocation limit
7889168c Poul Sander 2026-02-26 18:27 109
    // on very large images (Qt 6 defaults to 128 MB).
7889168c Poul Sander 2026-02-26 18:27 110
    QSize fullSize = reader.size();
ac476ca8 Poul Sander 2026-05-18 16:47 111
    if (fullSize.isValid())
ac476ca8 Poul Sander 2026-05-18 16:47 112
    {
ac476ca8 Poul Sander 2026-05-18 16:47 113
        QSize scaled = fullSize.scaled(target, target, Qt::KeepAspectRatio);
ac476ca8 Poul Sander 2026-05-18 16:47 114
        reader.setScaledSize(scaled);
7889168c Poul Sander 2026-02-26 18:27 115
    }
7889168c Poul Sander 2026-02-26 18:27 116
cec3b5da Poul Sander 2026-02-19 19:17 117
    QImage image = reader.read();
6fcd6c90 Poul Sander 2026-02-19 21:12 118
6fcd6c90 Poul Sander 2026-02-19 21:12 119
    if (*m_cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 120
        return;
cec3b5da Poul Sander 2026-02-19 19:17 121
ac476ca8 Poul Sander 2026-05-18 16:47 122
    if (image.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 123
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 124
ac476ca8 Poul Sander 2026-05-18 16:47 125
    // Ensure the longest side is exactly the target (the decoder hint may have
ac476ca8 Poul Sander 2026-05-18 16:47 126
    // been ignored for some formats). Aspect ratio preserved, no padding canvas.
ac476ca8 Poul Sander 2026-05-18 16:47 127
    QImage thumb;
ac476ca8 Poul Sander 2026-05-18 16:47 128
    if (image.width() > target || image.height() > target)
ac476ca8 Poul Sander 2026-05-18 16:47 129
        thumb = image.scaled(target, target,
ac476ca8 Poul Sander 2026-05-18 16:47 130
                             Qt::KeepAspectRatio,
ac476ca8 Poul Sander 2026-05-18 16:47 131
                             Qt::SmoothTransformation);
ac476ca8 Poul Sander 2026-05-18 16:47 132
    else
ac476ca8 Poul Sander 2026-05-18 16:47 133
        thumb = image;
ac476ca8 Poul Sander 2026-05-18 16:47 134
ac476ca8 Poul Sander 2026-05-18 16:47 135
    ThumbnailCache::save(m_path, m_size, thumb);
ac476ca8 Poul Sander 2026-05-18 16:47 136
ac476ca8 Poul Sander 2026-05-18 16:47 137
    if (*m_cancelFlag)
ac476ca8 Poul Sander 2026-05-18 16:47 138
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 139
ac476ca8 Poul Sander 2026-05-18 16:47 140
    emit finished(m_row, downscaleForDisplay(thumb));
cec3b5da Poul Sander 2026-02-19 19:17 141
}
1970-01-01 00:00 142