[vlc-commits] [Git][videolan/vlc][master] 4 commits: qt: introduce `SDFAARoundedTexture.frag`
Steve Lhomme (@robUx4)
gitlab at videolan.org
Sat Nov 30 05:34:46 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
2c1fd3c4 by Fatih Uzunoglu at 2024-11-30T05:19:11+00:00
qt: introduce `SDFAARoundedTexture.frag`
Rounded rectangular texture, with edges
smoothed out using signed distance function.
Smoothing the edges effectively acts as
anti aliasing.
The distance function is provided in the
subsequent commit.
- - - - -
daf9d472 by Fatih Uzunoglu at 2024-11-30T05:19:11+00:00
qt: provide the signed distance function in `SDFAARoundedTexture.frag`
- - - - -
40dcb6a4 by Fatih Uzunoglu at 2024-11-30T05:19:11+00:00
qt: introduce `RoundImage.qml` that uses `SDFAARoundedTexture.frag`
- - - - -
9b2907f5 by Fatih Uzunoglu at 2024-11-30T05:19:11+00:00
qt: get rid of obsolete `RoundImage`
Signed distance field based anti-aliasing is
used now, which made `QPainter` based RoundImage
obsolete.
- - - - -
15 changed files:
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/medialibrary/qml/MusicAlbumsGridExpandDelegate.qml
- modules/gui/qt/medialibrary/qml/MusicArtistDelegate.qml
- modules/gui/qt/medialibrary/qml/VideoInfoExpandPanel.qml
- modules/gui/qt/meson.build
- + modules/gui/qt/shaders/SDFAARoundedTexture.frag
- modules/gui/qt/shaders/meson.build
- modules/gui/qt/shaders/shaders.qrc
- − modules/gui/qt/widgets/native/roundimage.cpp
- − modules/gui/qt/widgets/native/roundimage.hpp
- − modules/gui/qt/widgets/native/roundimage_p.hpp
- modules/gui/qt/widgets/qml/DragItem.qml
- modules/gui/qt/widgets/qml/MediaCover.qml
- + modules/gui/qt/widgets/qml/RoundImage.qml
Changes:
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -348,7 +348,6 @@ libqt_plugin_la_SOURCES = \
widgets/native/mlfolderseditor.hpp \
widgets/native/qvlcframe.cpp \
widgets/native/qvlcframe.hpp \
- widgets/native/roundimage.cpp widgets/native/roundimage.hpp widgets/native/roundimage_p.hpp \
widgets/native/searchlineedit.cpp widgets/native/searchlineedit.hpp \
widgets/native/viewblockingrectangle.cpp widgets/native/viewblockingrectangle.hpp \
widgets/native/doubleclickignoringitem.hpp
@@ -496,8 +495,6 @@ nodist_libqt_plugin_la_SOURCES = \
widgets/native/interface_widgets.moc.cpp \
widgets/native/navigation_attached.moc.cpp \
widgets/native/mlfolderseditor.moc.cpp \
- widgets/native/roundimage.moc.cpp \
- widgets/native/roundimage_p.moc.cpp \
widgets/native/searchlineedit.moc.cpp \
widgets/native/viewblockingrectangle.moc.cpp
@@ -1289,7 +1286,8 @@ libqml_module_widgets_a_QML = \
widgets/qml/PartialEffect.qml \
widgets/qml/ViewHeader.qml \
widgets/qml/ProgressIndicator.qml \
- widgets/qml/RectangularGlow.qml
+ widgets/qml/RectangularGlow.qml \
+ widgets/qml/RoundImage.qml
if HAVE_QT65
libqml_module_widgets_a_QML += \
widgets/qml/DynamicShadow.qml \
@@ -1341,7 +1339,8 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \
shaders/SubTexture.vert \
shaders/PlayerBlurredBackground.frag \
shaders/HollowRectangularGlow.frag \
- shaders/RectangularGlow.frag
+ shaders/RectangularGlow.frag \
+ shaders/SDFAARoundedTexture.frag
if ENABLE_QT
libqt_plugin_la_LIBADD += libqml_module_dialogs.a \
=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -60,7 +60,6 @@
#include "menus/qml_menu_wrapper.hpp"
#include "widgets/native/csdthemeimage.hpp"
-#include "widgets/native/roundimage.hpp"
#include "widgets/native/navigation_attached.hpp"
#include "widgets/native/viewblockingrectangle.hpp"
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
@@ -346,7 +345,6 @@ void MainUI::registerQMLTypes()
const int versionMinor = 0;
// @uri VLC.Widgets
- qmlRegisterType<RoundImage>( uri, versionMajor, versionMinor, "RoundImage" );
qmlRegisterType<CSDThemeImage>(uri, versionMajor, versionMinor, "CSDThemeImage");
qmlRegisterType<ViewBlockingRectangle>( uri, versionMajor, versionMinor, "ViewBlockingRectangle" );
=====================================
modules/gui/qt/medialibrary/qml/MusicAlbumsGridExpandDelegate.qml
=====================================
@@ -133,7 +133,7 @@ FocusScope {
sourceItem: parent
- visible: (parent.status === Widgets.RoundImage.Ready)
+ visible: (parent.status === Image.Ready)
}
}
}
=====================================
modules/gui/qt/medialibrary/qml/MusicArtistDelegate.qml
=====================================
@@ -158,8 +158,7 @@ T.ItemDelegate {
spacing: VLCStyle.margin_xsmall
Widgets.RoundImage {
- implicitWidth: VLCStyle.play_cover_small
- implicitHeight: VLCStyle.play_cover_small
+ Layout.preferredHeight: VLCStyle.play_cover_small
Layout.fillHeight: true
Layout.preferredWidth: height
@@ -175,6 +174,9 @@ T.ItemDelegate {
Rectangle {
anchors.fill: parent
+ anchors.margins: -border.width
+ z: -1
+
radius: VLCStyle.play_cover_small
color: "transparent"
=====================================
modules/gui/qt/medialibrary/qml/VideoInfoExpandPanel.qml
=====================================
@@ -122,7 +122,7 @@ FocusScope {
sourceItem: parent
- visible: (parent.status === Widgets.RoundImage.Ready)
+ visible: (parent.status === Image.Ready)
}
}
}
=====================================
modules/gui/qt/meson.build
=====================================
@@ -150,8 +150,6 @@ moc_headers = files(
'widgets/native/interface_widgets.hpp',
'widgets/native/navigation_attached.hpp',
'widgets/native/mlfolderseditor.hpp',
- 'widgets/native/roundimage.hpp',
- 'widgets/native/roundimage_p.hpp',
'widgets/native/searchlineedit.hpp',
'widgets/native/viewblockingrectangle.hpp',
)
@@ -497,8 +495,6 @@ some_sources = files(
'widgets/native/mlfolderseditor.hpp',
'widgets/native/qvlcframe.cpp',
'widgets/native/qvlcframe.hpp',
- 'widgets/native/roundimage.cpp',
- 'widgets/native/roundimage.hpp',
'widgets/native/searchlineedit.cpp',
'widgets/native/searchlineedit.hpp',
'widgets/native/viewblockingrectangle.cpp',
@@ -900,6 +896,7 @@ qml_modules += {
'widgets/qml/ProgressIndicator.qml',
qml_dynamicshadow_file,
qml_blureffect_file,
+ 'widgets/qml/RoundImage.qml',
),
}
=====================================
modules/gui/qt/shaders/SDFAARoundedTexture.frag
=====================================
@@ -0,0 +1,88 @@
+#version 440
+
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Copyright (C) 2015 Inigo Quilez
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *****************************************************************************/
+
+layout(location = 0) in vec2 qt_TexCoord0;
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ vec4 qt_SubRect_source;
+ vec2 size;
+ float radiusTopRight;
+ float radiusBottomRight;
+ float radiusTopLeft;
+ float radiusBottomLeft;
+ float softEdgeMin;
+ float softEdgeMax;
+};
+
+layout(binding = 1) uniform sampler2D source;
+
+// Signed distance function by Inigo Quilez (https://iquilezles.org/articles/distfunctions2d)
+// b.x = width
+// b.y = height
+// r.x = roundness top-right
+// r.y = roundness boottom-right
+// r.z = roundness top-left
+// r.w = roundness bottom-left
+float sdRoundBox( in vec2 p, in vec2 b, in vec4 r )
+{
+ r.xy = (p.x>0.0)?r.xy : r.zw;
+ r.x = (p.y>0.0)?r.x : r.y;
+ vec2 q = abs(p)-b+r.x;
+ return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x;
+}
+
+void main()
+{
+ // The signed distance function works when the primitive is centered.
+ // If the texture is in the atlas, this condition is not satisfied.
+ // Therefore, we have to normalize the coordinate for the distance
+ // function to [0, 1]:
+ vec2 normalCoord = vec2(1.0, 1.0) / (qt_SubRect_source.zw) * (qt_TexCoord0 - (qt_SubRect_source.zw + qt_SubRect_source.xy)) + vec2(1.0, 1.0);
+
+ vec2 p = (size.xy * ((2.0 * normalCoord) - 1)) / size.y;
+ // Signed distance:
+ float dist = sdRoundBox(p, vec2(size.x / size.y, 1.0), vec4(radiusTopRight, radiusBottomRight, radiusTopLeft, radiusBottomLeft));
+
+ vec4 texel = texture(source, qt_TexCoord0);
+
+ // Soften the outline, as recommended by the Valve paper, using smoothstep:
+ // "Improved Alpha-Tested Magnification for Vector Textures and Special Effects"
+ // NOTE: The whole texel is multiplied, because of premultiplied alpha.
+ texel *= 1.0 - smoothstep(softEdgeMin, softEdgeMax, dist);
+
+ fragColor = texel * qt_Opacity;
+}
=====================================
modules/gui/qt/shaders/meson.build
=====================================
@@ -15,7 +15,8 @@ shader_sources = [
'SubTexture.vert',
'PlayerBlurredBackground.frag',
'HollowRectangularGlow.frag',
- 'RectangularGlow.frag'
+ 'RectangularGlow.frag',
+ 'SDFAARoundedTexture.frag'
]
shader_files = files(shader_sources)
=====================================
modules/gui/qt/shaders/shaders.qrc
=====================================
@@ -10,5 +10,6 @@
<file alias="PlayerBlurredBackground.frag.qsb">PlayerBlurredBackground.frag.qsb</file>
<file alias="HollowRectangularGlow.frag.qsb">HollowRectangularGlow.frag.qsb</file>
<file alias="RectangularGlow.frag.qsb">RectangularGlow.frag.qsb</file>
+ <file alias="SDFAARoundedTexture.frag.qsb">SDFAARoundedTexture.frag.qsb</file>
</qresource>
</RCC>
=====================================
modules/gui/qt/widgets/native/roundimage.cpp deleted
=====================================
@@ -1,685 +0,0 @@
-/*****************************************************************************
- * roundimage.cpp: Custom widgets
- ****************************************************************************
- * Copyright (C) 2021 the VideoLAN team
- *
- * Authors: Prince Gupta <guptaprince8832 at gmail.com>
- *
- * 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 2 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include <vlc_access.h>
-#include <vlc_stream.h>
-
-#include "qt.hpp"
-
-#include "roundimage.hpp"
-#include "roundimage_p.hpp"
-#include "util/asynctask.hpp"
-#include "util/qsgroundedrectangularimagenode.hpp"
-#include "util/vlcaccess_image_provider.hpp"
-
-#include <qhashfunctions.h>
-
-#include <QBuffer>
-#include <QFile>
-#include <QImage>
-#include <QImageReader>
-#include <QPainter>
-#include <QPainterPath>
-#include <QQuickWindow>
-#include <QGuiApplication>
-#include <QSGImageNode>
-#include <QIODevice>
-#include <QtQml>
-#include <QQmlFile>
-
-
-bool operator ==(const ImageCacheKey &lhs, const ImageCacheKey &rhs)
-{
- return lhs.radius == rhs.radius && lhs.size == rhs.size && lhs.url == rhs.url;
-}
-
-uint qHash(const ImageCacheKey &key, uint seed)
-{
- QtPrivate::QHashCombine hash;
- seed = hash(seed, key.url);
- seed = hash(seed, key.size.width());
- seed = hash(seed, key.size.height());
- seed = hash(seed, key.radius);
- return seed;
-}
-
-namespace
-{
- QImage prepareImage(const QSize &targetSize, const qreal radius, const QImage sourceImage)
- {
- if (qFuzzyIsNull(radius))
- {
- return sourceImage.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
- }
-
- 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;
- }
-
- // 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
- {
- if (result.isNull() && errStr.isEmpty())
- return QStringLiteral("Unspecified error.");
- 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() override
- {
- return prepareImage(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())
- {
- // source response failed, signal to parent
- emit finished();
- 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;
- };
-
-// global RoundImage cache
-RoundImageCache g_imageCache = {};
-
-}
-
-// RoundImageCache
-
-RoundImageCache::RoundImageCache()
- : m_imageCache(32 * 1024 * 1024) // 32 MiB
-{}
-
-std::shared_ptr<RoundImageRequest> RoundImageCache::requestImage(const ImageCacheKey& key, qreal dpr, QQmlEngine *engine)
-{
- //do we already have a pending request?
- auto it = m_pendingRequests.find(key);
- if (it != m_pendingRequests.end())
- {
- std::shared_ptr<RoundImageRequest> request = it->second.lock();
- if (request)
- return request;
- }
- auto request = std::make_shared<RoundImageRequest>(key, dpr, engine);
- if (request->getStatus() == RoundImage::Status::Error)
- return {};
- m_pendingRequests[key] = request;
- return request;
-}
-
-void RoundImageCache::removeRequest(const ImageCacheKey& key)
-{
- m_pendingRequests.erase(key);
-}
-
-// RoundImageRequest
-
-RoundImageRequest::RoundImageRequest(const ImageCacheKey& key, qreal dpr, QQmlEngine *engine)
- : m_key(key)
- , m_dpr(dpr)
-{
- m_imageResponse = getAsyncImageResponse(key.url, key.size, key.radius, engine);
- if (m_imageResponse)
- connect(m_imageResponse, &QQuickImageResponse::finished, this, &RoundImageRequest::handleImageResponseFinished);
- else
- m_status = RoundImage::Error;
-}
-
-RoundImageRequest::~RoundImageRequest()
-{
- if (m_imageResponse)
- {
- if (m_cancelOnDelete)
- {
- disconnect(m_imageResponse, &QQuickImageResponse::finished, this, &RoundImageRequest::handleImageResponseFinished);
- connect(m_imageResponse, &QQuickImageResponse::finished, m_imageResponse, &QObject::deleteLater);
- m_imageResponse->cancel();
- }
- else
- {
- m_imageResponse->deleteLater();
- }
- }
- g_imageCache.removeRequest(m_key);
-}
-
-void RoundImageRequest::saveInCache()
-{
- m_saveInCache = true;
-}
-
-void RoundImageRequest::handleImageResponseFinished()
-{
- m_cancelOnDelete = false;
- g_imageCache.removeRequest(m_key);
-
- const QString error = m_imageResponse->errorString();
- QImage image;
-
- if (auto textureFactory = m_imageResponse->textureFactory())
- {
- image = textureFactory->image();
- delete textureFactory;
- }
-
- if (image.isNull())
- {
- qDebug() << "failed to get image, error" << error << m_key.url;
- m_status = RoundImage::Status::Error;
- emit requestCompleted(m_status, {});
- return;
- }
-
- image.setDevicePixelRatio(m_dpr);
-
- if (m_saveInCache)
- g_imageCache.insert(m_key, new QImage(image), image.sizeInBytes());
- emit requestCompleted(RoundImage::Status::Ready, image);
-}
-
-
-QQuickImageResponse* RoundImageRequest::getAsyncImageResponse(const QUrl &url, const QSize &requestedSize, const qreal radius, QQmlEngine *engine)
-{
- if (url.scheme() == QStringLiteral("image") && url.host() != "vlcaccess")
- {
- auto provider = engine->imageProvider(url.host());
- if (!provider)
- return nullptr;
-
- assert(provider->imageType() == QQmlImageProviderBase::ImageResponse);
-
- const auto imageId = url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);
-
- if (provider->imageType() == QQmlImageProviderBase::ImageResponse)
- {
- auto rawImageResponse = static_cast<QQuickAsyncImageProvider *>(provider)->requestImageResponse(imageId, requestedSize);
- return new ImageResponseRadiusAdaptor(rawImageResponse, radius);
- }
-
- return nullptr;
- }
-
- VLCAccessImageProvider vlcAccessImageProvider([radius](QImage& img, const QSize &requestedSize) -> QImage {
- return prepareImage(requestedSize, radius, img);
- });
- QString wrappedUri = VLCAccessImageProvider::wrapUri(url.toString(QUrl::FullyEncoded));
-
- return vlcAccessImageProvider.requestImageResponse(wrappedUri, requestedSize);
-}
-
-// RoundImage
-
-RoundImage::RoundImage(QQuickItem *parent) : QQuickItem {parent}
-{
- if (Q_LIKELY(qGuiApp))
- setDPR(qGuiApp->devicePixelRatio());
-
- connect(this, &QQuickItem::windowChanged, this, [this](const QQuickWindow* const window) {
- if (window)
- setDPR(window->effectiveDevicePixelRatio());
- else if (Q_LIKELY(qGuiApp))
- setDPR(qGuiApp->devicePixelRatio());
- });
-
- connect(this, &QQuickItem::windowChanged, this, &RoundImage::adjustQSGCustomGeometry);
-}
-
-RoundImage::~RoundImage()
-{
-}
-
-QSGNode *RoundImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
-{
- auto customImageNode = dynamic_cast<QSGRoundedRectangularImageNode*>(oldNode);
- auto imageNode = dynamic_cast<QSGImageNode*>(oldNode);
-
- if (Q_UNLIKELY(oldNode && ((m_QSGCustomGeometry && !customImageNode)
- || (!m_QSGCustomGeometry && !imageNode))))
- {
- // This must be extremely unlikely.
- // Assigned to different window with different renderer?
- delete oldNode;
- oldNode = nullptr;
- }
-
- if (m_roundImage.isNull())
- {
- delete oldNode;
- m_dirty = false;
- return nullptr;
- }
-
- if (!oldNode)
- {
- if (m_QSGCustomGeometry)
- {
- customImageNode = new QSGRoundedRectangularImageNode;
- }
- else
- {
- assert(window());
- imageNode = window()->createImageNode();
- assert(imageNode);
- imageNode->setOwnsTexture(true);
- }
- }
-
- if (m_dirty)
- {
- m_dirty = false;
- assert(window());
-
- QQuickWindow::CreateTextureOptions flags = QQuickWindow::TextureCanUseAtlas;
-
- if (!m_roundImage.hasAlphaChannel())
- flags |= QQuickWindow::TextureIsOpaque;
-
- if (std::unique_ptr<QSGTexture> texture { window()->createTextureFromImage(m_roundImage, flags) })
- {
- if (m_QSGCustomGeometry)
- {
- customImageNode->setTexture(std::move(texture));
- }
- else
- {
- // No need to delete the old texture manually as it is owned by the node.
- imageNode->setTexture(texture.release());
- }
- }
- else
- {
- qmlWarning(this) << "Could not generate texture from " << m_roundImage;
- }
- }
-
- // Geometry:
- if (m_QSGCustomGeometry)
- {
- customImageNode->setShape({boundingRect(), radius()});
- customImageNode->setSmooth(smooth());
- assert(customImageNode->geometry() && customImageNode->material());
- return customImageNode;
- }
- else
- {
- imageNode->setRect(boundingRect());
- imageNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
- return imageNode;
- }
-}
-
-void RoundImage::componentComplete()
-{
- QQuickItem::componentComplete();
-
- if (!m_source.isEmpty())
- regenerateRoundImage();
-}
-
-QUrl RoundImage::source() const
-{
- return m_source;
-}
-
-QSize RoundImage::sourceSize() const
-{
- return m_sourceSize;
-}
-
-qreal RoundImage::radius() const
-{
- return m_radius;
-}
-
-RoundImage::Status RoundImage::status() const
-{
- return m_status;
-}
-
-bool RoundImage::cache() const
-{
- return m_cache;
-}
-
-void RoundImage::setSource(const QUrl& source)
-{
- if (m_source == source)
- return;
-
- m_source = source;
- emit sourceChanged(m_source);
- regenerateRoundImage();
-}
-
-void RoundImage::setSourceSize(const QSize& size)
-{
- if (m_sourceSize == size)
- return;
-
- m_sourceSize = size;
- emit sourceSizeChanged(size);
- regenerateRoundImage();
-}
-
-void RoundImage::setRadius(qreal radius)
-{
- if (m_radius == radius)
- return;
-
- m_radius = radius;
- emit radiusChanged(m_radius);
-}
-
-void RoundImage::setCache(bool cache)
-{
- if (m_cache == cache)
- return;
- m_cache = cache;
- emit cacheChanged();
-}
-
-void RoundImage::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
-{
- if (change == QQuickItem::ItemDevicePixelRatioHasChanged)
- setDPR(value.realValue);
-
- QQuickItem::itemChange(change, value);
-}
-
-void RoundImage::setDPR(const qreal value)
-{
- if (m_dpr == value)
- return;
-
- m_dpr = value;
- regenerateRoundImage();
-}
-
-void RoundImage::load()
-{
- // NOTE: at this point we still have old content displayed
-
- m_enqueuedGeneration = false;
-
- if (m_source.isEmpty())
- {
- // nothing to load, clear old content
- setStatus(Status::Null);
- setRoundImage({});
-
- return;
- }
-
- auto engine = qmlEngine(this);
- if (!engine || !m_sourceSize.isValid() || m_sourceSize.isEmpty())
- {
- onRequestCompleted(Status::Error, {});
- return;
- }
-
- const qreal scaledWidth = m_sourceSize.width() * m_dpr;
- const qreal scaledHeight = m_sourceSize.height() * m_dpr;
- const qreal scaledRadius = m_QSGCustomGeometry ? 0.0 : (this->radius() * m_dpr);
-
- const ImageCacheKey key {source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius};
-
- if (auto image = g_imageCache.object(key)) // should only by called in mainthread
- {
- onRequestCompleted(Status::Ready, *image);
- return;
- }
-
- m_activeImageResponse = g_imageCache.requestImage(key, m_dpr, engine);
- if (!m_activeImageResponse)
- {
- onRequestCompleted(RoundImage::Error, {});
- return;
- }
-
- if (m_cache)
- m_activeImageResponse->saveInCache();
-
- connect(m_activeImageResponse.get(), &RoundImageRequest::requestCompleted, this, &RoundImage::onRequestCompleted);
-
- //at this point m_activeImageResponse is either in Loading or Error status
- onRequestCompleted(RoundImage::Loading, {});
-}
-
-void RoundImage::onRequestCompleted(Status status, const QImage& image)
-{
- setRoundImage(image);
- switch (status)
- {
- case RoundImage::Error:
- setStatus(Status::Error);
- m_activeImageResponse.reset();
- break;
- case RoundImage::Ready:
- {
- if (image.isNull())
- setStatus(Status::Error);
- else
- setStatus(Status::Ready);
- m_activeImageResponse.reset();
- break;
- }
- case RoundImage::Loading:
- setStatus(Status::Loading);
- break;
- case RoundImage::Null:
- //requests should not be yield this state
- vlc_assert_unreachable();
- }
-}
-
-void RoundImage::setRoundImage(QImage image)
-{
- if (m_roundImage.isNull() && image.isNull())
- return;
-
- m_dirty = true;
- m_roundImage = image;
-
- // remove old contents, setting ItemHasContent to false will
- // inhibit updatePaintNode() call and old content will remain
- if (image.isNull())
- update();
-
- setFlag(ItemHasContents, !image.isNull());
- update();
-}
-
-void RoundImage::setStatus(const RoundImage::Status status)
-{
- if (status == m_status)
- return;
-
- m_status = status;
- emit statusChanged();
-}
-
-void RoundImage::regenerateRoundImage()
-{
- if (!isComponentComplete() || m_enqueuedGeneration)
- return;
-
- m_activeImageResponse.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);
-}
-
-void RoundImage::adjustQSGCustomGeometry(const QQuickWindow* const window)
-{
- if (!window) return;
-
- // No need to check if the scene graph is initialized according to docs.
-
- const auto enableCustomGeometry = [this, window]() {
- if (m_QSGCustomGeometry)
- return;
-
- // Favor custom geometry instead of clipping the image.
- // This allows making the texture opaque, as long as
- // source image is also opaque, for optimization
- // purposes.
- if (window->format().samples() >= 2)
- m_QSGCustomGeometry = true;
- // No need to regenerate as transparent part will not
- // matter. However, in order for the material to not
- // require blending, a regeneration is necessary.
- // We could force the material to not require blending
- // for the outer transparent part which is not within the
- // geometry, but then inherently transparent images would
- // not be rendered correctly due to the alpha channel.
-
- QMetaObject::invokeMethod(this,
- &RoundImage::regenerateRoundImage,
- Qt::QueuedConnection);
-
- // It might be tempting to not regenerate the image on size
- // change. However;
- // If upscaled, we don't know if the image can be provided
- // in a higher resolution.
- // If downscaled, we would like to free some used memory.
- // On the other hand, there is no need to regenerate the
- // image when the radius changes. This behavior does not
- // mean that the radius can be animated. Although possible,
- // the custom geometry node is not designed to handle animations.
- disconnect(this, &RoundImage::radiusChanged, this, &RoundImage::regenerateRoundImage);
- connect(this, &RoundImage::radiusChanged, this, &QQuickItem::update);
- };
-
- const auto disableCustomGeometry = [this]() {
- if (!m_QSGCustomGeometry)
- return;
-
- m_QSGCustomGeometry = false;
-
- QMetaObject::invokeMethod(this,
- &RoundImage::regenerateRoundImage,
- Qt::QueuedConnection);
-
- connect(this, &RoundImage::radiusChanged, this, &RoundImage::regenerateRoundImage);
- disconnect(this, &RoundImage::radiusChanged, this, &QQuickItem::update);
- };
-
-
- if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::GraphicsApi::OpenGL)
- {
- // Direct OpenGL, enable custom geometry:
- enableCustomGeometry();
- }
- else
- {
- // QSG(Opaque)TextureMaterial supports Qt RHI,
- // so there is no obstacle using custom geometry
- // if Qt RHI is in use.
- if (QSGRendererInterface::isApiRhiBased(window->rendererInterface()->graphicsApi()))
- enableCustomGeometry();
- else
- disableCustomGeometry();
- }
-}
=====================================
modules/gui/qt/widgets/native/roundimage.hpp deleted
=====================================
@@ -1,116 +0,0 @@
-/*****************************************************************************
- * roundimage.hpp: Custom widgets
- ****************************************************************************
- * Copyright (C) 2021 the VideoLAN team
- *
- * Authors: Prince Gupta <guptaprince8832 at gmail.com>
- *
- * 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 2 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#ifndef VLC_QT_ROUNDIMAGE_HPP
-#define VLC_QT_ROUNDIMAGE_HPP
-
-#include <QImage>
-#include <QQuickItem>
-#include <QUrl>
-#include <memory>
-
-class QQuickImageResponse;
-class RoundImageRequest;
-
-class RoundImage : public QQuickItem
-{
- Q_OBJECT
-
- // url of the image
- Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged FINAL)
-
- Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged FINAL)
-
- Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
-
- Q_PROPERTY(Status status READ status NOTIFY statusChanged FINAL)
-
- Q_PROPERTY(bool cache READ cache WRITE setCache NOTIFY cacheChanged FINAL)
-
-public:
- enum Status
- {
- Null,
- Ready,
- Loading,
- Error
- };
-
- Q_ENUM(Status)
-
- RoundImage(QQuickItem *parent = nullptr);
- ~RoundImage();
-
- void componentComplete() override;
-
- QUrl source() const;
- qreal radius() const;
- Status status() const;
- bool cache() const;
- QSize sourceSize() const;
-
-public slots:
- void setSource(const QUrl& source);
- void setRadius(qreal radius);
- void setCache(bool cache);
- void setSourceSize(const QSize&);
-
-signals:
- void sourceChanged(const QUrl&);
- void radiusChanged(qreal);
- void statusChanged();
- void cacheChanged();;
- void sourceSizeChanged(const QSize&);
-
-protected:
- void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
- QSGNode* updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override;
-
-private:
- void setDPR(qreal value);
- void handleImageResponseFinished();
- void load();
- void setRoundImage(QImage image);
- void setStatus(const Status status);
- void regenerateRoundImage();
-
-private slots:
- void adjustQSGCustomGeometry(const QQuickWindow* const window);
- void onRequestCompleted(Status status, const QImage& image);
-
-private:
- Status m_status = Status::Null;
- bool m_QSGCustomGeometry = false;
- bool m_dirty = false;
- bool m_enqueuedGeneration = false;
-
- QUrl m_source;
- QSize m_sourceSize;
- qreal m_radius = 0.0;
- qreal m_dpr = 1.0; // device pixel ratio
- bool m_cache = true;
-
- QImage m_roundImage;
- std::shared_ptr<RoundImageRequest> m_activeImageResponse;
-};
-
-#endif
=====================================
modules/gui/qt/widgets/native/roundimage_p.hpp deleted
=====================================
@@ -1,164 +0,0 @@
-/*****************************************************************************
- * roundimage.cpp: Custom widgets
- ****************************************************************************
- * Copyright (C) 2023 the VideoLAN team
- *
- * 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 2 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#ifndef VLC_ROUNDIMAGE_P_HPP
-#define VLC_ROUNDIMAGE_P_HPP
-
-#include <QObject>
-#include <QUrl>
-#include <QCache>
-#include <QQuickImageProvider>
-
-#include <unordered_map>
-#include <memory>
-
-#include "roundimage.hpp"
-
-struct ImageCacheKey
-{
- ImageCacheKey(QUrl url, QSize size, qreal radius)
- : url(url)
- , size(size)
- , radius(radius)
- {}
-
- QUrl url;
- QSize size;
- qreal radius;
-};
-
-bool operator ==(const ImageCacheKey &lhs, const ImageCacheKey &rhs);
-uint qHash(const ImageCacheKey &key, uint seed);
-
-//std hash version is required for unordered_map
-template<>
-struct std::hash<ImageCacheKey>
-{
- std::size_t operator()(const ImageCacheKey& s) const noexcept
- {
- return qHash(s, 0); // or use boost::hash_combine
- }
-};
-
-
-/**
- * @brief The RoundImageRequest class represent a request for a RoundImage generation
- * it will be shared amongst the diffent RoundImage instance that preforms the same request
- * simultaneously, and will notify upon completion or failure through the requestCompleted signal
- */
-class RoundImageRequest : public QObject
-{
- Q_OBJECT
-public:
- RoundImageRequest(const ImageCacheKey& key, qreal dpr, QQmlEngine *engine);
-
- ~RoundImageRequest();
-
- inline RoundImage::Status getStatus() const
- {
- return m_status;
- }
-
- void handleImageResponseFinished();
-
- void saveInCache();
-
-signals:
- void requestCompleted(RoundImage::Status status, QImage image);
-
-private:
- QQuickImageResponse *getAsyncImageResponse(const QUrl &url, const QSize &requestedSize, const qreal radius, QQmlEngine *engine);
-
- bool m_cancelOnDelete = true;
- const ImageCacheKey m_key;
- qreal m_dpr;
- QQuickImageResponse* m_imageResponse = nullptr;
- RoundImage::Status m_status = RoundImage::Status::Loading;
- bool m_saveInCache = false;
-};
-
-/**
- * @brief The RoundImageCache class contains the cache of generated round images
- *
- * principle is the folowing:
- *
- * @startuml
- * (*) --> if image already in cache then
- * --> [true] "use image from the cache"
- * --> "set image in Ready state"
- * --> (*)
- * else
- * --> [false] "set image Loading state"
- * --> if pending requests already exists for the source
- * --> [true] reference pending request
- * --> "wait for request completion" as wait
- * else
- * --> [false] create a new request
- * --> wait
- * endif
- * --> if request succeed
- * --> [true] "store image result in cache"
- * --> "use request image result"
- * --> "set image in Ready state"
- * --> (*)
- * else
- * --> [false] "set the image in Error state"
- * --> (*)
- * endif
- * endif
- * @enduml
- *
- * notes that:
- * - requests are refcounted, if no image reference a request anymore,
- * the request is canceled and destroyed
- *
- * - failed atempts are not stored, if the same resource is requested latter on,
- * a new request will be created and executed for this request
- */
-class RoundImageCache
-{
-public:
- RoundImageCache();
-
- inline QImage* object(const ImageCacheKey& key) const
- {
- return m_imageCache.object(key);
- }
-
- inline void insert(const ImageCacheKey& key, QImage* image, int size)
- {
- m_imageCache.insert(key, image, size);
- }
-
- std::shared_ptr<RoundImageRequest> requestImage(const ImageCacheKey& key, qreal dpr, QQmlEngine *engine);
-
- void removeRequest(const ImageCacheKey& key);
-
-private:
- //images are cached (result of RoundImageGenerator) with the cost calculated from QImage::sizeInBytes
- QCache<ImageCacheKey, QImage> m_imageCache;
-
- //contains the pending request, we use a weak ptr here as the request may be canceled and destroyed
- //when all RoundImage that requested the image drop the request. user should check for validity before
- //accessing the RoundImageRequest
- std::unordered_map<ImageCacheKey, std::weak_ptr<RoundImageRequest>> m_pendingRequests;
-};
-
-#endif // VLC_ROUNDIMAGE_P_HPP
=====================================
modules/gui/qt/widgets/qml/DragItem.qml
=====================================
@@ -474,9 +474,9 @@ Item {
sourceSize: dragItem.imageSourceSize ?? Qt.size(width, height)
onStatusChanged: {
- if (status === Widgets.RoundImage.Ready)
+ if (status === Image.Ready)
coverRepeater.notReadyCount -= 1
- else if (status === Widgets.RoundImage.Error) {
+ else if (status === Image.Error) {
const fallbackSource = modelData.fallback ?? defaultCover
if (source === fallbackSource)
coverRepeater.notReadyCount -= 1
=====================================
modules/gui/qt/widgets/qml/MediaCover.qml
=====================================
@@ -86,7 +86,7 @@ Rectangle {
sourceSize.height: root.pictureHeight
onStatusChanged: {
- if (status === Widgets.RoundImage.Loading) {
+ if (status === Image.Loading) {
root._loadTimeout = false
timer.start()
} else {
@@ -105,8 +105,8 @@ Rectangle {
radius: root.radius
visible: image.source.toString() === "" //RoundImage.source is a QUrl
- || image.status === Widgets.RoundImage.Error
- || (image.status === Widgets.RoundImage.Loading && root._loadTimeout)
+ || image.status === Image.Error
+ || (image.status === Image.Loading && root._loadTimeout)
// we only keep this image till there is no main image
// try to release the resources otherwise
=====================================
modules/gui/qt/widgets/qml/RoundImage.qml
=====================================
@@ -0,0 +1,91 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQuick
+
+Item {
+ id: root
+
+ readonly property real implicitWidth: image.implicitHeight
+ readonly property real implicitHeight: image.implicitHeight
+
+ // Might need this as we have overridden implicit sizes as readonly
+ height: root.implicitHeight
+ width: root.implicitWidth
+
+ asynchronous: true
+
+ property alias asynchronous: image.asynchronous
+ property alias source: image.source
+ property alias sourceSize: image.sourceSize
+ property alias status: image.status
+ property alias cache: image.cache
+ // fillMode is not reflected in the texture,
+ // so it is not provided as an alias here
+
+ property real radius
+
+ // NOTE: Note the distinction between ShaderEffect and
+ // ShaderEffectSource. ShaderEffect is no different
+ // than any other item, including Image. ShaderEffectSource
+ // on the other hand breaks batching, uses video memory
+ // to store the texture of the source item that are
+ // (re)rendered in a framebuffer or offscreen surface.
+ ShaderEffect {
+ id: shaderEffect
+
+ anchors.fill: parent
+
+ visible: (root.radius > 0.0) && (GraphicsInfo.shaderType === GraphicsInfo.RhiShader)
+
+ supportsAtlasTextures: true
+
+ blending: true
+
+ // FIXME: Culling seems to cause issues, such as when the view is layered due to
+ // fading edge effec, this is most likely a Qt bug.
+ // cullMode: ShaderEffect.BackFaceCulling
+
+ readonly property real radius: Math.min(1.0, Math.max(root.radius / (Math.min(width, height) / 2), 0.0))
+ readonly property real radiusTopRight: radius
+ readonly property real radiusBottomRight: radius
+ readonly property real radiusTopLeft: radius
+ readonly property real radiusBottomLeft: radius
+
+ readonly property size size: Qt.size(width, height)
+
+ readonly property double softEdgeMin: -0.01
+ readonly property double softEdgeMax: 0.01
+
+ // QQuickImage as texture provider, no need for ShaderEffectSource.
+ // In this case, we simply ask the Image to provide its texture,
+ // so that we can make use of our custom shader.
+ readonly property Image source: image
+
+ fragmentShader: "qrc:///shaders/SDFAARoundedTexture.frag.qsb"
+ }
+
+ Image {
+ id: image
+
+ anchors.fill: parent
+
+ visible: !shaderEffect.visible
+
+ fillMode: Image.PreserveAspectCrop
+ }
+}
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/30fc5564e66dd9afa0092c3299542fed2e3a1678...9b2907f59f03a619d92ec0d309dbbde5ff0da238
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/30fc5564e66dd9afa0092c3299542fed2e3a1678...9b2907f59f03a619d92ec0d309dbbde5ff0da238
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