[vlc-commits] [Git][videolan/vlc][master] qt: improve round image generation in roundimage
Jean-Baptiste Kempf (@jbk)
gitlab at videolan.org
Sat Jun 18 16:28:24 UTC 2022
Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC
Commits:
1cc9f502 by Prince Gupta at 2022-06-18T16:11:27+00:00
qt: improve round image generation in roundimage
adapt qquickimageresponse to generate round image directly instead of
doing it as a separate task
this saves extra qobject allocation and main loop interruptions
- - - - -
2 changed files:
- modules/gui/qt/widgets/native/roundimage.cpp
- modules/gui/qt/widgets/native/roundimage.hpp
Changes:
=====================================
modules/gui/qt/widgets/native/roundimage.cpp
=====================================
@@ -26,6 +26,7 @@
#endif
#include "roundimage.hpp"
+#include "util/asynctask.hpp"
#include <qhashfunctions.h>
@@ -76,21 +77,41 @@ namespace
// images are cached (result of RoundImageGenerator) with the cost calculated from QImage::sizeInBytes
QCache<ImageCacheKey, QImage> imageCache(32 * 1024 * 1024); // 32 MiB
- QRectF doPreserveAspectCrop(const QSizeF &sourceSize, const QSizeF &size)
+ QImage applyRadius(const QSize &targetSize, const qreal radius, const QImage sourceImage)
{
- const qreal ratio = std::max(size.width() / sourceSize.width(), size.height() / sourceSize.height());
- const QSizeF imageSize = sourceSize * ratio;
- const QPointF alignedCenteredTopLeft {(size.width() - imageSize.width()) / 2., (size.height() - imageSize.height()) / 2.};
- return {alignedCenteredTopLeft, imageSize};
+ QImage target(targetSize, QImage::Format_ARGB32_Premultiplied);
+ if (target.isNull())
+ return target;
+
+ target.fill(Qt::transparent);
+
+ {
+ QPainter painter(&target);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+ QPainterPath path;
+ path.addRoundedRect(0, 0, targetSize.width(), targetSize.height(), radius, radius);
+ painter.setClipPath(path);
+
+ // do PreserveAspectCrop
+ const auto imageSize = sourceImage.size();
+ const QPointF alignedCenteredTopLeft {(targetSize.width() - imageSize.width()) / 2.
+ , (targetSize.height() - imageSize.height()) / 2.};
+ painter.drawImage(QRectF {alignedCenteredTopLeft, imageSize}, sourceImage);
+ }
+
+ return target;
}
class ImageReader : public AsyncTask<QImage>
{
public:
// requestedSize is only taken as hint, the Image is resized with PreserveAspectCrop
- ImageReader(QIODevice *device, QSize requestedSize)
+ ImageReader(QIODevice *device, QSize requestedSize, const qreal radius)
: device {device}
, requestedSize {requestedSize}
+ , radius {radius}
{
}
@@ -103,26 +124,31 @@ namespace
const QSize sourceSize = reader.size();
if (requestedSize.isValid())
- reader.setScaledSize(doPreserveAspectCrop(sourceSize, requestedSize).size().toSize());
+ reader.setScaledSize(sourceSize.scaled(requestedSize, Qt::KeepAspectRatioByExpanding));
auto img = reader.read();
errorStr = reader.errorString();
+
+ if (!errorStr.isEmpty())
+ img = applyRadius(requestedSize.isValid() ? requestedSize : img.size(), radius, img);
+
return img;
}
private:
QIODevice *device;
QSize requestedSize;
+ qreal radius;
QString errorStr;
};
class LocalImageResponse : public QQuickImageResponse
{
public:
- LocalImageResponse(const QString &fileName, const QSize &requestedSize)
+ LocalImageResponse(const QString &fileName, const QSize &requestedSize, const qreal radius)
{
auto file = new QFile(fileName);
- reader.reset(new ImageReader(file, requestedSize));
+ reader.reset(new ImageReader(file, requestedSize, radius));
file->setParent(reader.get());
connect(reader.get(), &ImageReader::result, this, &LocalImageResponse::handleImageRead);
@@ -159,7 +185,10 @@ namespace
class NetworkImageResponse : public QQuickImageResponse
{
public:
- NetworkImageResponse(QNetworkReply *reply, QSize requestedSize) : reply {reply}, requestedSize {requestedSize}
+ NetworkImageResponse(QNetworkReply *reply, QSize requestedSize, const qreal radius)
+ : reply {reply}
+ , requestedSize {requestedSize}
+ , radius {radius}
{
QObject::connect(reply, &QNetworkReply::finished
, this, &NetworkImageResponse::handleNetworkReplyFinished);
@@ -193,7 +222,7 @@ namespace
return;
}
- reader.reset(new ImageReader(reply, requestedSize));
+ reader.reset(new ImageReader(reply, requestedSize, radius));
QObject::connect(reader.get(), &ImageReader::result, this, [this]()
{
result = reader->takeResult();
@@ -208,6 +237,7 @@ namespace
QNetworkReply *reply;
QSize requestedSize;
+ qreal radius;
TaskHandle<ImageReader> reader;
QImage result;
QString error;
@@ -217,9 +247,9 @@ namespace
class ImageProviderAsyncAdaptor : public QQuickImageResponse
{
public:
- ImageProviderAsyncAdaptor(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize)
+ ImageProviderAsyncAdaptor(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize, const qreal radius)
{
- task.reset(new ProviderImageGetter(provider, id, requestedSize));
+ task.reset(new ProviderImageGetter(provider, id, requestedSize, radius));
connect(task.get(), &ProviderImageGetter::result, this, [this]()
{
result = task->takeResult();
@@ -240,22 +270,34 @@ namespace
class ProviderImageGetter : public AsyncTask<QImage>
{
public:
- ProviderImageGetter(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize)
+ ProviderImageGetter(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize, const qreal radius)
: provider {provider}
, id{id}
, requestedSize{requestedSize}
+ , radius {radius}
{
}
QImage execute() override
{
- return provider->requestImage(id, &sourceSize, requestedSize);
+ auto img = provider->requestImage(id, &sourceSize, requestedSize);
+ if (!img.isNull())
+ {
+ QSize targetSize = sourceSize;
+ if (requestedSize.isValid())
+ targetSize.scale(requestedSize, Qt::KeepAspectRatioByExpanding);
+
+ applyRadius(targetSize, radius, img);
+ }
+
+ return img;
}
private:
QQuickImageProvider *provider;
QString id;
QSize requestedSize;
+ qreal radius;
QSize sourceSize;
};
@@ -263,7 +305,77 @@ namespace
QImage result;
};
- QQuickImageResponse *getAsyncImageResponse(const QUrl &url, const QSize &requestedSize, QQmlEngine *engine)
+
+ // adapts a given QQuickImageResponse to produce a image with radius
+ class ImageResponseRadiusAdaptor : public QQuickImageResponse
+ {
+ public:
+ ImageResponseRadiusAdaptor(QQuickImageResponse *response, const qreal radius) : response {response}, radius {radius}
+ {
+ response->setParent(this);
+ connect(response, &QQuickImageResponse::finished
+ , this, &ImageResponseRadiusAdaptor::handleResponseFinished);
+ }
+
+ QString errorString() const override { return errStr; }
+
+ QQuickTextureFactory *textureFactory() const override
+ {
+ return !result.isNull() ? QQuickTextureFactory::textureFactoryForImage(result) : nullptr;
+ }
+
+ private:
+ class RoundImageGenerator : public AsyncTask<QImage>
+ {
+ public:
+ RoundImageGenerator(const QImage &img, const qreal radius) : sourceImg {img}, radius{radius}
+ {
+ }
+
+ QImage execute()
+ {
+ return applyRadius(sourceImg.size(), radius, sourceImg);
+ }
+
+ private:
+ QImage sourceImg;
+ qreal radius;
+ };
+
+ void handleResponseFinished()
+ {
+ errStr = response->errorString();
+ auto textureFactory = std::unique_ptr<QQuickTextureFactory>(response->textureFactory());
+ auto img = !textureFactory ? QImage {} : textureFactory->image();
+ if (!textureFactory || img.isNull())
+ return;
+
+ response->disconnect(this);
+ response->deleteLater();
+ response = nullptr;
+
+ generator.reset(new RoundImageGenerator(img, radius));
+ connect(generator.get(), &RoundImageGenerator::result
+ , this, &ImageResponseRadiusAdaptor::handleGeneratorFinished);
+
+ generator->start(*QThreadPool::globalInstance());
+ }
+
+ void handleGeneratorFinished()
+ {
+ result = generator->takeResult();
+
+ emit finished();
+ }
+
+ QQuickImageResponse *response;
+ QString errStr;
+ qreal radius;
+ QImage result;
+ TaskHandle<RoundImageGenerator> generator;
+ };
+
+ QQuickImageResponse *getAsyncImageResponse(const QUrl &url, const QSize &requestedSize, const qreal radius, QQmlEngine *engine)
{
if (url.scheme() == QStringLiteral("image"))
{
@@ -277,15 +389,18 @@ namespace
const auto imageId = url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);;
if (provider->imageType() == QQmlImageProviderBase::Image)
- return new ImageProviderAsyncAdaptor(static_cast<QQuickImageProvider *>(provider), imageId, requestedSize);
+ return new ImageProviderAsyncAdaptor(static_cast<QQuickImageProvider *>(provider), imageId, requestedSize, radius);
if (provider->imageType() == QQmlImageProviderBase::ImageResponse)
- return static_cast<QQuickAsyncImageProvider *>(provider)->requestImageResponse(imageId, requestedSize);
+ {
+ auto rawImageResponse = static_cast<QQuickAsyncImageProvider *>(provider)->requestImageResponse(imageId, requestedSize);
+ return new ImageResponseRadiusAdaptor(rawImageResponse, radius);
+ }
return nullptr;
}
else if (QQmlFile::isLocalFile(url))
{
- return new LocalImageResponse(QQmlFile::urlToLocalFileOrQrc(url), requestedSize);
+ return new LocalImageResponse(QQmlFile::urlToLocalFileOrQrc(url), requestedSize, radius);
}
#ifdef QT_NETWORK_LIB
else
@@ -293,7 +408,7 @@ namespace
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
auto reply = engine->networkAccessManager()->get(request);
- return new NetworkImageResponse(reply, requestedSize);
+ return new NetworkImageResponse(reply, requestedSize, radius);
}
#endif
}
@@ -433,35 +548,15 @@ void RoundImage::handleImageRequestFinished()
return;
}
+ image.setDevicePixelRatio(m_dpr);
+ setRoundImage(image);
+
const qreal scaledWidth = this->width() * m_dpr;
const qreal scaledHeight = this->height() * m_dpr;
const qreal scaledRadius = this->radius() * m_dpr;
const ImageCacheKey key {source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius};
-
- // 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(image, scaledWidth, scaledHeight, scaledRadius));
- connect(m_roundImageGenerator.get(), &BaseAsyncTask::result, this, [this, key]()
- {
- const auto image = new QImage(m_roundImageGenerator->takeResult());
-
- m_roundImageGenerator.reset();
-
- if (image->isNull())
- {
- delete image;
- setRoundImage({});
- return;
- }
-
- image->setDevicePixelRatio(m_dpr);
- setRoundImage(*image);
-
- imageCache.insert(key, image, image->sizeInBytes());
- });
-
- m_roundImageGenerator->start(*QThreadPool::globalInstance());
+ imageCache.insert(key, new QImage(image), image.sizeInBytes());
}
void RoundImage::resetImageRequest()
@@ -477,7 +572,6 @@ void RoundImage::resetImageRequest()
void RoundImage::load()
{
m_enqueuedGeneration = false;
- assert(!m_roundImageGenerator);
auto engine = qmlEngine(this);
if (!engine || m_source.isEmpty() || !size().isValid() || size().isEmpty())
@@ -494,7 +588,7 @@ void RoundImage::load()
return;
}
- m_activeImageRequest = getAsyncImageResponse(source(), QSizeF {scaledWidth, scaledHeight}.toSize(), engine);
+ m_activeImageRequest = getAsyncImageResponse(source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius, engine);
connect(m_activeImageRequest, &QQuickImageResponse::finished, this, &RoundImage::handleImageRequestFinished);
}
@@ -522,48 +616,9 @@ void RoundImage::regenerateRoundImage()
resetImageRequest();
- m_roundImageGenerator.reset();
-
// use Qt::QueuedConnection to delay generation, so that dependent properties
// subsequent updates can be merged, f.e when VLCStyle.scale changes
m_enqueuedGeneration = true;
QMetaObject::invokeMethod(this, &RoundImage::load, Qt::QueuedConnection);
}
-
-RoundImage::RoundImageGenerator::RoundImageGenerator(const QImage &sourceImage, qreal width, qreal height, qreal radius)
- : sourceImage(sourceImage)
- , width(width)
- , height(height)
- , radius(radius)
-{
-}
-
-QImage RoundImage::RoundImageGenerator::execute()
-{
- if (width <= 0 || height <= 0 || sourceImage.isNull())
- return {};
-
- QImage target(width, height, QImage::Format_ARGB32_Premultiplied);
- if (target.isNull())
- return target;
-
- target.fill(Qt::transparent);
-
- {
- QPainter painter(&target);
- painter.setRenderHint(QPainter::Antialiasing, true);
- painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
-
- QPainterPath path;
- path.addRoundedRect(0, 0, width, height, radius, radius);
- painter.setClipPath(path);
-
- // do PreserveAspectCrop
- const auto imageSize = sourceImage.size();
- const QPointF alignedCenteredTopLeft {(width - imageSize.width()) / 2., (height - imageSize.height()) / 2.};
- painter.drawImage(QRectF {alignedCenteredTopLeft, imageSize}, sourceImage);
- }
-
- return target;
-}
=====================================
modules/gui/qt/widgets/native/roundimage.hpp
=====================================
@@ -25,8 +25,6 @@
#include "qt.hpp"
-#include "util/asynctask.hpp"
-
#include <QImage>
#include <QQuickItem>
#include <QUrl>
@@ -64,20 +62,6 @@ protected:
QSGNode* updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override;
private:
- class RoundImageGenerator : public AsyncTask<QImage>
- {
- public:
- RoundImageGenerator(const QImage &sourceImage, qreal width, qreal height, qreal radius);
-
- QImage execute();
-
- private:
- QImage sourceImage;
- qreal width;
- qreal height;
- qreal radius;
- };
-
void setDPR(qreal value);
void handleImageRequestFinished();
void resetImageRequest();
@@ -92,7 +76,6 @@ private:
QImage m_roundImage;
bool m_dirty = false;
- TaskHandle<RoundImageGenerator> m_roundImageGenerator {};
QQuickImageResponse *m_activeImageRequest {};
bool m_enqueuedGeneration = false;
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/1cc9f502eae0d7ff2162aa7a5c448707d8208103
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/1cc9f502eae0d7ff2162aa7a5c448707d8208103
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list