Artifact 0c9c731f2af5dd10576c440391533efc7341c99e:

  • File src/BaseImage.cxx — part of check-in [0fe7876a1d] at 2018-10-24 22:21:24 on branch trunk — Make image transformation asynchronous (user: fifr size: 4420)

/*
 * Copyright (c) 2018 Frank Fischer <frank-fischer@shadow-soft.de>
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  <http://www.gnu.org/licenses/>
 */

#include "BaseImage.hxx"

#include <QtCore/QThread>
#include <QtGui/QPainter>

struct BaseImage::Data {
    qreal painted_width = 0;          ///< width of the painted image (after scaling)
    qreal painted_height = 0;         ///< height of the painted image (after scaling)
    QImage source_image;              ///< cache the original image before the transformation
    QImage image;                     ///< the transformed image
    BaseImage* base_image = nullptr;  ///< pointer to the source image producer

    QThread thread;
    bool thread_running = false;
    bool restart_thread = true;

    ~Data()
    {
        thread.quit();
        thread.wait();
    }
};

BaseImage::BaseImage() : d(new Data)
{
    connect(this, &BaseImage::imageChanged, [this]() { this->update(); });

    auto worker = new BaseImageTransformWorker(this);
    worker->moveToThread(&d->thread);
    connect(&d->thread, &QThread::finished, worker, &QObject::deleteLater);
    connect(this, &BaseImage::startTransform, worker, &BaseImageTransformWorker::doTransform);
    connect(worker, &BaseImageTransformWorker::resultReady, this, &BaseImage::finishTransform);
    d->thread.start();
}

BaseImage::~BaseImage() {}

void BaseImage::paint(QPainter* painter)
{
    if (d->image.width() > 0 && d->image.height() > 0) {
        auto wratio = static_cast<qreal>(width()) / d->image.width();
        auto hratio = static_cast<qreal>(height()) / d->image.height();
        auto ratio = std::min(wratio, hratio);
        d->painted_width = d->image.width() * ratio;
        d->painted_height = d->image.height() * ratio;
        emit paintedSizeChanged();
        painter->drawImage(QRectF{(width() - d->painted_width) / 2,
                                  (height() - d->painted_height) / 2,
                                  d->painted_width,
                                  d->painted_height},
                           d->image);
    }
}

qreal BaseImage::paintedWidth() const
{
    return d->painted_width;
}

qreal BaseImage::paintedHeight() const
{
    return d->painted_height;
}

void BaseImage::setSource(BaseImage* base_image)
{
    if (base_image == d->base_image) return;

    if (d->base_image) {
        disconnect(d->base_image, &BaseImage::imageChanged, this, &BaseImage::updateImage);
    }

    d->base_image = base_image;

    if (d->base_image) {
        d->source_image = d->base_image->image();
        qDebug() << "Connect " << (void*)d->base_image << " " << (void*)this;
        connect(d->base_image, &BaseImage::imageChanged, this, &BaseImage::updateImage);
        updateImage();
    } else {
        d->source_image = {};
    }

    emit sourceChanged();
}

BaseImage* BaseImage::source()
{
    return d->base_image;
}

QImage BaseImage::sourceImage() const
{
    return d->source_image;
}

QImage BaseImage::image() const
{
    return d->image;
}

void BaseImage::updateImage()
{
    emit imageChanging();
    if (d->base_image) d->source_image = d->base_image->image();
    if (!d->thread_running) {
        d->restart_thread = false;
        d->thread_running = true;
        emit startTransform(d->source_image);
    } else {
        d->restart_thread = true;
    }
}

void BaseImage::finishTransform(const QImage& image)
{
    d->image = image;
    emit imageChanged();
    update();
    if (d->restart_thread) {
        d->restart_thread = false;
        emit startTransform(d->source_image);
    } else {
        d->thread_running = false;
    }
}

BaseImageTransformWorker::BaseImageTransformWorker(BaseImage* base_image) : base_image_(base_image)
{
}

void BaseImageTransformWorker::doTransform(const QImage& image)
{
    emit resultReady(base_image_->transform(image));
}