[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