[vlc-commits] [Git][videolan/vlc][master] 10 commits: qt: support outlining (border) in `SDFAARoundedTexture.frag`

Steve Lhomme (@robUx4) gitlab at videolan.org
Mon Mar 24 10:48:55 UTC 2025



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
df0245b1 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qt: support outlining (border) in `SDFAARoundedTexture.frag`

- - - - -
889e1835 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: support outlining (border) in `RoundImage.qml`

- - - - -
dd3d7479 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: use outlining of `RoundImage` in `ArtistTopBanner`

It should be acceptable to not have background rectangle
with non-RHI graphics backend (no shader support).

- - - - -
67b90d21 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: use outlining of `RoundImage` and remove the border rectangle in `MusicArtistDelegate`

It should be acceptable to not have background rectangle
with non-RHI graphics backend (no shader support).

- - - - -
089271fd by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: workaround for MusicArtistDelegate image not getting rendered

- - - - -
88ff25ee by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: use custom shader if background coloring is requested in RoundImage

- - - - -
2d70a156 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: remove background rectangle in `MediaCover`

It should be acceptable to not have background rectangle
with non-RHI graphics backend (no shader support).

- - - - -
931e0e50 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: use custom shader if preserve aspect crop fill mode is requested in RoundImage

- - - - -
acd4b5b2 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qt: rename `RoundImage.qml` to `ImageExt.qml`

- - - - -
d5b32237 by Fatih Uzunoglu at 2025-03-24T10:18:29+00:00
qml: gracefully handle the case when image is not available in `ImageExt`

- Use root size for the shader if image is not available, as in
  that case the image would not have a sensible size. The implicit
  size is chosen arbitrarily to be 64x64 instead of 0x0, which
  is mainly relevant when root does not have an overridden size.
- Do not bother calculating the crop rate if there is no image.
- Do not do outlining if there is no image, as in that case it
  means there is nothing to outline.

This ensures that background is shown while the image is loading.

`ShaderEffect` uses a transparent dummy texture with repeat wrap
mode when the texture is not available. With source over blending
of the texture into the background, a fully transparent source
yields the destination color, which is the background color.

- - - - -


14 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
- modules/gui/qt/medialibrary/qml/AudioGridItem.qml
- 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/SDFAARoundedTexture_cropsupport.frag → modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport_bordersupport.frag
- modules/gui/qt/shaders/meson.build
- modules/gui/qt/shaders/shaders.qrc
- modules/gui/qt/widgets/qml/DragItem.qml
- modules/gui/qt/widgets/qml/RoundImage.qml → modules/gui/qt/widgets/qml/ImageExt.qml
- modules/gui/qt/widgets/qml/MediaCover.qml


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1300,7 +1300,7 @@ libqml_module_widgets_a_QML = \
 	widgets/qml/ViewHeader.qml \
 	widgets/qml/ProgressIndicator.qml \
 	widgets/qml/RectangularGlow.qml \
-	widgets/qml/RoundImage.qml
+	widgets/qml/ImageExt.qml
 if HAVE_QT65
 libqml_module_widgets_a_QML += \
 	widgets/qml/DynamicShadow.qml \
@@ -1354,7 +1354,7 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \
                           shaders/HollowRectangularGlow.frag \
                           shaders/RectangularGlow.frag \
                           shaders/SDFAARoundedTexture.frag \
-                          shaders/SDFAARoundedTexture_cropsupport.frag \
+                          shaders/SDFAARoundedTexture_cropsupport_bordersupport.frag \
                           shaders/DitheredTexture.frag
 if ENABLE_QT
 


=====================================
modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
=====================================
@@ -144,22 +144,16 @@ FocusScope {
             implicitHeight: VLCStyle.cover_normal
             implicitWidth: VLCStyle.cover_normal
 
-            Widgets.RoundImage {
+            Widgets.ImageExt {
                 id: roundImage
                 source: artist.cover || VLCStyle.noArtArtist
                 sourceSize: Qt.size(width * eDPR, height * eDPR)
                 anchors.fill: parent
                 radius: VLCStyle.cover_normal
+                borderColor: theme.border
+                borderWidth: VLCStyle.dp(1, VLCStyle.scale)
                 readonly property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window)
             }
-
-            Rectangle {
-                anchors.fill: parent
-                radius: roundImage.effectiveRadius
-                color: "transparent"
-                border.width: VLCStyle.dp(1, VLCStyle.scale)
-                border.color: theme.border
-            }
         }
 
         ColumnLayout {


=====================================
modules/gui/qt/medialibrary/qml/AudioGridItem.qml
=====================================
@@ -30,15 +30,8 @@ Widgets.GridItem {
     image: model.cover || ""
     fallbackImage: VLCStyle.noArtAlbumCover
 
-    // NOTE: If radius is 0.0, `QQuickImage` is used, and it
-    //       requires a clip node (`clip: true`) to display
-    //       `PreserveAspectCrop` where we can not have in a
-    //       delegate. So instead, use `PreserveAspectFit` in
-    //       that case. If non-RHI scene graph adaptation is
-    //       used, a clip node is not a concern, so in that
-    //       case `PreserveAspectCrop` can be used as well.
-    fillMode: ((GraphicsInfo.shaderType !== GraphicsInfo.RhiShader) || (effectiveRadius > 0.0)) ? Image.PreserveAspectCrop
-                                                                                                : Image.PreserveAspectFit
+    fillMode: (GraphicsInfo.shaderType === GraphicsInfo.RhiShader) ? Image.PreserveAspectCrop
+                                                                   : Image.PreserveAspectFit
 
     title: model.title || qsTr("Unknown title")
     subtitle: model.main_artist || qsTr("Unknown artist")


=====================================
modules/gui/qt/medialibrary/qml/MusicAlbumsGridExpandDelegate.qml
=====================================
@@ -116,7 +116,7 @@ FocusScope {
         Component {
             id: cover
 
-            Widgets.RoundImage {
+            Widgets.ImageExt {
                 id: expand_cover_id
 
                 property int cover_height: parent.cover_height


=====================================
modules/gui/qt/medialibrary/qml/MusicArtistDelegate.qml
=====================================
@@ -159,7 +159,7 @@ T.ItemDelegate {
     contentItem: RowLayout {
         spacing: VLCStyle.margin_xsmall
 
-        Widgets.RoundImage {
+        Widgets.ImageExt {
             id: roundImage
 
             Layout.preferredHeight: VLCStyle.play_cover_small
@@ -176,23 +176,23 @@ T.ItemDelegate {
 
             readonly property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window)
 
-            Rectangle {
-                anchors.centerIn: parent
-                anchors.alignWhenCentered: false
+            borderColor: (isCurrent || _isHover) ? theme.accent
+                                                 : theme.border
 
-                implicitWidth: roundImage.paintedWidth + border.width
-                implicitHeight: roundImage.paintedHeight + border.width
+            borderWidth: VLCStyle.dp(1, VLCStyle.scale)
 
+            // FIXME: Qt bug (observed 6.2 and 6.8): Without an alpha node beneath the image, the image does not get rendered.
+            Rectangle {
                 z: -1
 
-                radius: roundImage.effectiveRadius
+                anchors.centerIn: parent
 
-                color: "transparent"
+                width: 1
+                height: 1
 
-                border.width: VLCStyle.dp(1, VLCStyle.scale)
+                opacity: 0.01
 
-                border.color: (isCurrent || _isHover) ? theme.accent
-                                                      : theme.border
+                color: "black"
             }
         }
 


=====================================
modules/gui/qt/medialibrary/qml/VideoInfoExpandPanel.qml
=====================================
@@ -109,7 +109,7 @@ FocusScope {
                         width: VLCStyle.gridCover_video_width
 
                         /* A bigger cover for the album */
-                        Widgets.RoundImage {
+                        Widgets.ImageExt {
                             id: expand_cover_id
 
                             anchors.fill: parent


=====================================
modules/gui/qt/meson.build
=====================================
@@ -899,7 +899,7 @@ qml_modules += {
         'widgets/qml/RectangularGlow.qml',
         qml_dynamicshadow_file,
         qml_blureffect_file,
-        'widgets/qml/RoundImage.qml',
+        'widgets/qml/ImageExt.qml',
     ),
 }
 


=====================================
modules/gui/qt/shaders/SDFAARoundedTexture.frag
=====================================
@@ -49,6 +49,10 @@ layout(std140, binding = 0) uniform buf {
 #endif
 #ifdef BACKGROUND_SUPPORT
     vec4 backgroundColor;
+#endif
+#ifdef BORDER_SUPPORT
+    vec4 borderColor;
+    float borderRange;
 #endif
     float radiusTopRight;
     float radiusBottomRight;
@@ -124,6 +128,22 @@ void main()
     texel = texel + backgroundColor * (1.0 - texel.a);
 #endif
 
+#ifdef BORDER_SUPPORT
+    if (borderRange > 0.0)
+    {
+        // Solid border:
+        float borderStep = step(-borderRange, dist);
+        vec4 border = borderStep * borderColor;
+#ifdef ANTIALIASING
+        // Inner AA (Outer AA is handled below, regardless of the border):
+        // This is additive, solid and AA part do not intersect:
+        border += (smoothstep(-borderRange - fwidth(dist) * 1.5, -borderRange, dist)) * (1.0 - borderStep) * borderColor;
+#endif
+        // Source over blending (S + D * (1 - S.a)):
+        texel = border + texel * (1.0 - border.a);
+    }
+#endif
+
 #ifdef ANTIALIASING
 #ifndef CUSTOM_SOFTEDGE
     float softEdgeMax = fwidth(dist) * 0.75;


=====================================
modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag → modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport_bordersupport.frag
=====================================
@@ -7,6 +7,7 @@
 //        shaders.
 
 #define CROP_SUPPORT
+#define BORDER_SUPPORT
 
 // WARNING: The contents of this file must be in sync with SDFAARoundedTexture.frag
 //          for maintenance purposes. IF YOU EDIT THIS FILE, MAKE SURE TO DO THE
@@ -61,6 +62,10 @@ layout(std140, binding = 0) uniform buf {
 #endif
 #ifdef BACKGROUND_SUPPORT
     vec4 backgroundColor;
+#endif
+#ifdef BORDER_SUPPORT
+    vec4 borderColor;
+    float borderRange;
 #endif
     float radiusTopRight;
     float radiusBottomRight;
@@ -136,6 +141,22 @@ void main()
     texel = texel + backgroundColor * (1.0 - texel.a);
 #endif
 
+#ifdef BORDER_SUPPORT
+    if (borderRange > 0.0)
+    {
+        // Solid border:
+        float borderStep = step(-borderRange, dist);
+        vec4 border = borderStep * borderColor;
+#ifdef ANTIALIASING
+        // Inner AA (Outer AA is handled below, regardless of the border):
+        // This is additive, solid and AA part do not intersect:
+        border += (smoothstep(-borderRange - fwidth(dist) * 1.5, -borderRange, dist)) * (1.0 - borderStep) * borderColor;
+#endif
+        // Source over blending (S + D * (1 - S.a)):
+        texel = border + texel * (1.0 - border.a);
+    }
+#endif
+
 #ifdef ANTIALIASING
 #ifndef CUSTOM_SOFTEDGE
     float softEdgeMax = fwidth(dist) * 0.75;


=====================================
modules/gui/qt/shaders/meson.build
=====================================
@@ -17,7 +17,7 @@ shader_sources = [
     'HollowRectangularGlow.frag',
     'RectangularGlow.frag',
     'SDFAARoundedTexture.frag',
-    'SDFAARoundedTexture_cropsupport.frag',
+    'SDFAARoundedTexture_cropsupport_bordersupport.frag',
     'DitheredTexture.frag'
 ]
 


=====================================
modules/gui/qt/shaders/shaders.qrc
=====================================
@@ -11,7 +11,7 @@
         <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>
-        <file alias="SDFAARoundedTexture_cropsupport.frag.qsb">SDFAARoundedTexture_cropsupport.frag.qsb</file>
+        <file alias="SDFAARoundedTexture_cropsupport_bordersupport.frag.qsb">SDFAARoundedTexture_cropsupport_bordersupport.frag.qsb</file>
         <file alias="DitheredTexture.frag.qsb">DitheredTexture.frag.qsb</file>
     </qresource>
 </RCC>


=====================================
modules/gui/qt/widgets/qml/DragItem.qml
=====================================
@@ -464,7 +464,7 @@ Item {
                 }
             }
 
-            Widgets.RoundImage {
+            Widgets.ImageExt {
                 id: artworkCover
 
                 anchors.centerIn: parent


=====================================
modules/gui/qt/widgets/qml/RoundImage.qml → modules/gui/qt/widgets/qml/ImageExt.qml
=====================================
@@ -17,14 +17,23 @@
  *****************************************************************************/
 import QtQuick
 
+// NOTE: ImageExt behaves exactly like Image, except when at least one of these features are used:
+//       - `PreserveAspectCrop` fill mode without requiring a clip node.
+//       - Rounded rectangular shaping.
+//       - Background coloring.
+//       - Outlining (border).
+//       - TODO: Custom padding.
+// NOTE: Extra features are only available with the RHI graphics backend,
+//       particularly when shaders are supported.
+// NOTE: Do not use this type if none of the extra features are used.
 Item {
     id: root
 
     // Implicit size used to be overridden as readonly, but that needed
     // binding width/height to the new properties which in turn caused
     // problems with layouts.
-    implicitWidth: image.implicitWidth
-    implicitHeight: image.implicitHeight
+    implicitWidth: shaderEffect.readyForVisibility ? shaderEffect.implicitWidth : image.implicitWidth
+    implicitHeight: shaderEffect.readyForVisibility ? shaderEffect.implicitHeight : image.implicitHeight
 
     // WARNING: We can not override QQuickItem's antialiasing
     //          property as readonly because Qt 6.6 marks it
@@ -97,6 +106,17 @@ Item {
     readonly property real effectiveRadius: shaderEffect.readyForVisibility ? radius : 0.0
     readonly property color effectiveBackgroundColor: shaderEffect.readyForVisibility ? backgroundColor : "transparent"
 
+    // Border:
+    // NOTE: The border is an overlay for the texture (the
+    //       texture does not shrink).
+    // NOTE: Border uses source-over blending. Therefore if
+    //       it is translucent, the image would get exposed.
+    // NOTE: The unit of width is not specified. It is
+    //       recommended to do only relative adjustments.
+    property color borderColor: "black"
+    property int borderWidth: 0
+    readonly property int effectiveBorderWidth: shaderEffect.readyForVisibility ? borderWidth : 0
+
     // NOTE: Note the distinction between ShaderEffect and
     //       ShaderEffectSource. ShaderEffect is no different
     //       than any other item, including Image. ShaderEffectSource
@@ -109,15 +129,16 @@ Item {
         anchors.alignWhenCentered: true
         anchors.centerIn: parent
 
-        implicitWidth: image.implicitWidth
-        implicitHeight: image.implicitHeight
+        implicitWidth: (image.status === Image.Ready) ? image.implicitWidth : 64
+        implicitHeight: (image.status === Image.Ready) ? image.implicitHeight : 64
 
-        width: (image.fillMode === Image.PreserveAspectCrop) ? root.width : image.paintedWidth
-        height: (image.fillMode === Image.PreserveAspectCrop) ? root.height : image.paintedHeight
+        width: ((image.status !== Image.Ready) || (image.fillMode === Image.PreserveAspectCrop)) ? root.width : image.paintedWidth
+        height: ((image.status !== Image.Ready) || (image.fillMode === Image.PreserveAspectCrop)) ? root.height : image.paintedHeight
 
         visible: readyForVisibility
 
-        readonly property bool readyForVisibility: (root.radius > 0.0) && (GraphicsInfo.shaderType === GraphicsInfo.RhiShader)
+        readonly property bool readyForVisibility: (GraphicsInfo.shaderType === GraphicsInfo.RhiShader) &&
+                                                   (root.radius > 0.0 || root.borderWidth > 0 || backgroundColor.a > 0.0 || root.fillMode === Image.PreserveAspectCrop)
 
         smooth: root.smooth
 
@@ -151,6 +172,10 @@ Item {
             if (root.fillMode !== Image.PreserveAspectCrop)
                 return ret
 
+            // No need to calculate if image is not ready
+            if (image.status !== Image.Ready)
+                return ret
+
             const implicitScale = implicitWidth / implicitHeight
             const scale = width / height
 
@@ -162,13 +187,17 @@ Item {
             return ret
         }
 
+        // (2 / width) seems to be a good coefficient to make it similar to `Rectangle.border`:
+        readonly property double borderRange: (image.status === Image.Ready) ? (root.borderWidth / width * 2.) : 0.0 // no need for outlining if there is no image (nothing to outline)
+        readonly property color borderColor: root.borderColor
+
         // 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: (cropRate.width > 0.0 || cropRate.height > 0.0) ? "qrc:///shaders/SDFAARoundedTexture_cropsupport.frag.qsb"
-                                                                        : "qrc:///shaders/SDFAARoundedTexture.frag.qsb"
+        fragmentShader: (cropRate.width > 0.0 || cropRate.height > 0.0) || (root.borderWidth > 0) ? "qrc:///shaders/SDFAARoundedTexture_cropsupport_bordersupport.frag.qsb"
+                                                                                                  : "qrc:///shaders/SDFAARoundedTexture.frag.qsb"
 
     }
 


=====================================
modules/gui/qt/widgets/qml/MediaCover.qml
=====================================
@@ -44,7 +44,7 @@ Item {
 
     property alias radius: image.radius
 
-    property alias color: background.color
+    property alias color: image.backgroundColor
 
     property alias source: image.source
 
@@ -83,20 +83,6 @@ Item {
 
     // Children
 
-    Rectangle {
-        id: background
-
-        anchors.centerIn: parent
-        anchors.alignWhenCentered: true
-
-        width: root.paintedWidth
-        height: root.paintedHeight
-
-        radius: root.effectiveRadius
-
-        visible: !Qt.colorEqual(image.effectiveBackgroundColor, color)
-    }
-
     //delay placeholder showing up
     Timer {
         id: timer
@@ -105,7 +91,7 @@ Item {
         onTriggered: root._loadTimeout = true
     }
 
-    Widgets.RoundImage {
+    Widgets.ImageExt {
         id: image
 
         anchors.fill: parent
@@ -113,8 +99,6 @@ Item {
         sourceSize: Qt.size(root.pictureWidth * root.eDPR,
                             root.pictureHeight * root.eDPR)
 
-        backgroundColor: root.color
-
         onStatusChanged: {
             if (status === Image.Loading) {
                 root._loadTimeout = false
@@ -125,7 +109,7 @@ Item {
         }
     }
 
-    Widgets.RoundImage {
+    Widgets.ImageExt {
         id: fallbackImage
 
         anchors.fill: parent



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/d668d601905b945a00c3e92740c7d505126a1c0f...d5b322377a3cafd5f957fece51e915ab1d8a5ade

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/d668d601905b945a00c3e92740c7d505126a1c0f...d5b322377a3cafd5f957fece51e915ab1d8a5ade
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