git repos / SagoImageBrowser

src/thumbnailworker.cpp

browsing at commit = 80a9926d20e6e662214fd2964baf7377b4d9710a

raw · blame · history

#include "thumbnailworker.h"

#include "thumbnailcache.h"

#include <QFile>
#include <QImageReader>

#include <fcntl.h>
#include <unistd.h>

namespace
{
constexpr int kDisplaySize = 128;

QImage downscaleForDisplay(const QImage &src)
{
    if (src.isNull())
        return src;
    if (src.width() <= kDisplaySize && src.height() <= kDisplaySize)
        return src;
    return src.scaled(kDisplaySize, kDisplaySize,
                      Qt::KeepAspectRatio,
                      Qt::SmoothTransformation);
}
} // namespace

ThumbnailWorker::ThumbnailWorker(const QByteArray &path,
                                 int row,
                                 ThumbnailCache::Size size,
                                 std::atomic_bool *cancelFlag)
    : m_path(path),
      m_row(row),
      m_size(size),
      m_cancelFlag(cancelFlag)
{
    setAutoDelete(true);
}

void ThumbnailWorker::run()
{
    if (*m_cancelFlag)
        return;

    // Per the spec, never thumbnail files that live inside the cache itself.
    if (ThumbnailCache::isInsideCache(m_path))
        return;

    const int target = static_cast<int>(m_size);

    QImage cached = ThumbnailCache::load(m_path, m_size);
    if (*m_cancelFlag)
        return;

    if (!cached.isNull())
    {
        emit finished(m_row, downscaleForDisplay(cached));
        return;
    }

    // Open the file via POSIX open() so that paths with non-UTF-8 bytes
    // (e.g. Latin-1 encoded filenames) are handled correctly.
    int fd = ::open(m_path.constData(), O_RDONLY | O_CLOEXEC);
    if (fd < 0)
        return;

    QFile file;
    if (!file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle))
    {
        ::close(fd);
        return;
    }

    QImageReader reader(&file);
    reader.setAutoTransform(true);

    // Scale during decoding to avoid hitting the allocation limit
    // on very large images (Qt 6 defaults to 128 MB).
    QSize fullSize = reader.size();
    if (fullSize.isValid())
    {
        QSize scaled = fullSize.scaled(target, target, Qt::KeepAspectRatio);
        reader.setScaledSize(scaled);
    }

    QImage image = reader.read();

    if (*m_cancelFlag)
        return;

    if (image.isNull())
        return;

    // Ensure the longest side is exactly the target (the decoder hint may have
    // been ignored for some formats). Aspect ratio preserved, no padding canvas.
    QImage thumb;
    if (image.width() > target || image.height() > target)
        thumb = image.scaled(target, target,
                             Qt::KeepAspectRatio,
                             Qt::SmoothTransformation);
    else
        thumb = image;

    ThumbnailCache::save(m_path, m_size, thumb);

    if (*m_cancelFlag)
        return;

    emit finished(m_row, downscaleForDisplay(thumb));
}