Commit ecbe4ea6 authored by Prince Gupta's avatar Prince Gupta Committed by Jean-Baptiste Kempf
Browse files

qt/roundimage: unify loading and round image generation stage

they were seperated to optimize conditions when the width and height
changes too frequently, but they occur seldomly, plus caching both
source image and round image costs double memory
parent c2f86a25
......@@ -111,7 +111,6 @@ Widgets.NavigableFocusScope {
width: VLCStyle.expandCover_music_width
radius: VLCStyle.expandCover_music_radius
source: model.cover || VLCStyle.noArtAlbum
sourceSize: Qt.size(width, height)
}
Widgets.ListCoverShadow {
......
......@@ -100,7 +100,6 @@ Widgets.NavigableFocusScope {
anchors.fill: parent
source: model.thumbnail || VLCStyle.noArtCover
sourceSize: Qt.size(width, height)
radius: VLCStyle.gridCover_radius
}
......
......@@ -29,6 +29,7 @@
#include <QBrush>
#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <QPen>
#include <QQuickWindow>
......@@ -43,6 +44,16 @@
namespace
{
class Image
{
public:
static std::unique_ptr<Image> getImage(const QUrl &source, const QSizeF &sourceSize);
virtual ~Image() = default;
// must be reentrant
virtual void paint(QPainter *painter, const QSizeF &size) = 0;
};
QString getPath(const QUrl &url)
{
QString path = url.isLocalFile() ? url.toLocalFile() : url.toString();
......@@ -86,6 +97,8 @@ RoundImage::RoundImage(QQuickItem *parent) : QQuickPaintedItem {parent}
void RoundImage::paint(QPainter *painter)
{
if (m_roundImage.isNull())
return;
painter->drawImage(QPointF {0., 0.}, m_roundImage, m_roundImage.rect());
}
......@@ -103,7 +116,9 @@ void RoundImage::componentComplete()
Q_ASSERT(!m_isComponentComplete); // classBegin is not called?
m_isComponentComplete = true;
if (!m_source.isEmpty())
updateSource();
regenerateRoundImage();
else
m_roundImage = {};
}
QUrl RoundImage::source() const
......@@ -116,11 +131,6 @@ qreal RoundImage::radius() const
return m_radius;
}
QSizeF RoundImage::sourceSize() const
{
return m_sourceSize;
}
void RoundImage::setSource(QUrl source)
{
if (m_source == source)
......@@ -128,7 +138,7 @@ void RoundImage::setSource(QUrl source)
m_source = source;
emit sourceChanged(m_source);
updateSource();
regenerateRoundImage();
}
void RoundImage::setRadius(qreal radius)
......@@ -141,16 +151,6 @@ void RoundImage::setRadius(qreal radius)
regenerateRoundImage();
}
void RoundImage::setSourceSize(QSizeF sourceSize)
{
if (m_sourceSize == sourceSize)
return;
m_sourceSize = sourceSize;
emit sourceSizeChanged(m_sourceSize);
updateSource();
}
void RoundImage::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
if (change == QQuickItem::ItemDevicePixelRatioHasChanged)
......@@ -165,35 +165,12 @@ void RoundImage::setDPR(const qreal value)
return;
m_dpr = value;
if (m_sourceSize.isValid())
updateSource(); // "effectiveSourceSize" is changed
else
regenerateRoundImage();
}
void RoundImage::updateSource()
{
if (!m_isComponentComplete)
return;
const QSizeF effectiveSourceSize = m_sourceSize.isValid() ? m_sourceSize * m_dpr : QSize {};
m_loader.reset(new Loader({m_source, effectiveSourceSize}));
connect(m_loader.get(), &BaseAsyncTask::result, this, [this]()
{
m_sourceImage = m_loader->takeResult();
m_loader.reset();
regenerateRoundImage();
});
m_loader->start(*QThreadPool::globalInstance());
regenerateRoundImage();
}
void RoundImage::regenerateRoundImage()
{
if (!m_isComponentComplete
|| m_enqueuedGeneration
|| m_loader /* when loader ends it will call regenerateRoundImage */)
if (!m_isComponentComplete || m_enqueuedGeneration)
return;
// use Qt::QueuedConnection to delay generation, so that dependent properties
......@@ -206,7 +183,7 @@ void RoundImage::regenerateRoundImage()
// Image is generated in size factor of `m_dpr` to avoid scaling artefacts when
// generated image is set with device pixel ratio
m_roundImageGenerator.reset(new RoundImageGenerator({width() * m_dpr, height() * m_dpr, radius() * m_dpr, m_sourceImage}));
m_roundImageGenerator.reset(new RoundImageGenerator(m_source, width() * m_dpr, height() * m_dpr, radius() * m_dpr));
connect(m_roundImageGenerator.get(), &BaseAsyncTask::result, this, [this]()
{
m_roundImage = m_roundImageGenerator->takeResult();
......@@ -220,27 +197,24 @@ void RoundImage::regenerateRoundImage()
}, Qt::QueuedConnection);
}
RoundImage::Loader::Loader(const Params &params) : params {params} {}
RoundImage::ImagePtr RoundImage::Loader::execute()
RoundImage::RoundImageGenerator::RoundImageGenerator(const QUrl &source, qreal width, qreal height, qreal radius)
: source(source)
, width(width)
, height(height)
, radius(radius)
{
return Image::getImage(params.source, params.sourceSize);
}
RoundImage::RoundImageGenerator::RoundImageGenerator(const RoundImage::RoundImageGenerator::Params &params) : params {params} {}
QImage RoundImage::RoundImageGenerator::execute()
{
if (params.width <= 0 || params.height <= 0)
if (width <= 0 || height <= 0)
return {};
QImage target(params.width, params.height, QImage::Format_ARGB32);
QImage target(width, height, QImage::Format_ARGB32);
if (target.isNull())
return target;
target.fill(Qt::transparent);
if (Q_UNLIKELY(!params.image))
return target;
QPainter painter;
painter.begin(&target);
......@@ -248,16 +222,18 @@ QImage RoundImage::RoundImageGenerator::execute()
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QPainterPath path;
path.addRoundedRect(0, 0, params.width, params.height, params.radius, params.radius);
path.addRoundedRect(0, 0, width, height, radius, radius);
painter.setClipPath(path);
params.image->paint(&painter, {params.width, params.height});
auto image = Image::getImage(source, {width, height});
image->paint(&painter, {width, height});
painter.end();
return target;
}
std::shared_ptr<RoundImage::Image> RoundImage::Image::getImage(const QUrl &source, const QSizeF &sourceSize)
std::unique_ptr<Image> Image::getImage(const QUrl &source, const QSizeF &sourceSize)
{
class QtImage : public Image
{
......@@ -319,6 +295,6 @@ std::shared_ptr<RoundImage::Image> RoundImage::Image::getImage(const QUrl &sourc
const QByteArray data = readFile(source);
if (source.toString().endsWith(".svg"))
return std::make_shared<SVGImage>(data);
return std::make_shared<QtImage>(data, sourceSize);
return std::make_unique<SVGImage>(data);
return std::make_unique<QtImage>(data, sourceSize);
}
......@@ -27,8 +27,7 @@
#include "util/asynctask.hpp"
#include <QPixmap>
#include <QPainter>
#include <QImage>
#include <QQuickPaintedItem>
#include <QUrl>
......@@ -39,9 +38,6 @@ class RoundImage : public QQuickPaintedItem
// url of the image
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
// sets the maximum number of pixels stored for the loaded image so that large images do not use more memory than necessary
Q_PROPERTY(QSizeF sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
public:
......@@ -54,12 +50,10 @@ public:
QUrl source() const;
qreal radius() const;
QSizeF sourceSize() const;
public slots:
void setSource(QUrl source);
void setRadius(qreal radius);
void setSourceSize(QSizeF sourceSize);
signals:
void sourceChanged(QUrl source);
......@@ -70,65 +64,27 @@ protected:
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
private:
class Image
{
public:
static std::shared_ptr<Image> getImage(const QUrl &source, const QSizeF &sourceSize);
virtual ~Image() = default;
// must be reentrant
virtual void paint(QPainter *painter, const QSizeF &size) = 0;
};
using ImagePtr = std::shared_ptr<Image>;
class Loader : public AsyncTask<ImagePtr>
{
public:
struct Params
{
QUrl source;
QSizeF sourceSize;
};
Loader(const Params &params);
ImagePtr execute();
private:
const Params params;
};
class RoundImageGenerator : public AsyncTask<QImage>
{
public:
struct Params
{
qreal width;
qreal height;
qreal radius;
ImagePtr image;
};
RoundImageGenerator(const Params &params);
RoundImageGenerator(const QUrl &source, qreal width, qreal height, qreal radius);
QImage execute();
private:
const Params params;
QUrl source;
qreal width;
qreal height;
qreal radius;
};
void setDPR(qreal value);
void updateSource();
void regenerateRoundImage();
QUrl m_source;
ImagePtr m_sourceImage;
qreal m_radius = 0.0;
QSizeF m_sourceSize;
qreal m_dpr = 1.0; // device pixel ratio
QImage m_roundImage;
TaskHandle<Loader> m_loader {};
TaskHandle<RoundImageGenerator> m_roundImageGenerator {};
bool m_enqueuedGeneration = false;
......
......@@ -37,7 +37,6 @@ RoundImage {
height: VLCStyle.listAlbumCover_height
width: VLCStyle.listAlbumCover_width
sourceSize: Qt.size(width, height)
Loader {
id: overlay
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment