[vlc-commits] [Git][videolan/vlc][master] 10 commits: qml: preserve aspect ratio in `RoundImage.qml` and provide painted size

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Dec 20 15:01:16 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
7115721b by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: preserve aspect ratio in `RoundImage.qml` and provide painted size

- - - - -
4613828b by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: use the painted size for background in `MediaCover.qml`

- - - - -
659ec6aa by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: use the painted size in delegate shadow in `GridItem.qml`

- - - - -
dfc7d84c by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: provide `sourceClipRect` in `RoundImage.qml`

- - - - -
a7fe4201 by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: no longer override implicit size in `RoundImage.qml`

Height and width were bound to implicit size in order
to make sure that the overridden implicit size is used
instead of the default implicit size which would remain
(0, 0).

However, this seems to cause issues with layout. So,
do not override them anymore.

- - - - -
b07fcdaa by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: support `Image.PreserveAspectCrop` in `RoundImage.qml` without clip node

With a slight modification in the fragment shader, cropping is
directly done there instead of requiring a clip node (`clip: true`)
which has bad effect for performance in itself and breaks batch
rendering.

- - - - -
5e5b09ff by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: use `Image.PreserveAspectCrop` in AudioGridItem

- - - - -
8561fbef by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: support PreserveAspectCrop with non-RHI sg adaptation in RoundImage

Clipping is not a concern with the non-RHI scene graph adaptation, so
we can use it as QQuickImage suggests to have PreserveAspectCrop without
cropping with our custom shader as we are doing with the RHI case.

- - - - -
ac6c6507 by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: use PreserveAspectCrop with non-RHI sg adaptation in AudioGridItem

- - - - -
8c857503 by Fatih Uzunoglu at 2024-12-20T14:38:02+00:00
qml: use `readyForVisibility` without probing first in RoundImage.qml

When the merge request that introduced `effectiveRadius` was merged,
another merge request which introduced `readyForVisibility` was not
merged yet. Instead of depending, I opted in to probe availability
of the property.

Now that `readyForVisibility` is available, we don't need to check
availability anymore.

- - - - -


9 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/medialibrary/qml/AudioGridItem.qml
- modules/gui/qt/shaders/SDFAARoundedTexture.frag
- + modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag
- modules/gui/qt/shaders/meson.build
- modules/gui/qt/shaders/shaders.qrc
- modules/gui/qt/widgets/qml/GridItem.qml
- modules/gui/qt/widgets/qml/MediaCover.qml
- modules/gui/qt/widgets/qml/RoundImage.qml


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1343,7 +1343,8 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \
                           shaders/PlayerBlurredBackground.frag \
                           shaders/HollowRectangularGlow.frag \
                           shaders/RectangularGlow.frag \
-                          shaders/SDFAARoundedTexture.frag
+                          shaders/SDFAARoundedTexture.frag \
+                          shaders/SDFAARoundedTexture_cropsupport.frag
 if ENABLE_QT
 
 libqt_plugin_la_LIBADD += libqml_module_dialogs.a \


=====================================
modules/gui/qt/medialibrary/qml/AudioGridItem.qml
=====================================
@@ -30,6 +30,16 @@ 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
+
     title: model.title || qsTr("Unknown title")
     subtitle: model.main_artist || qsTr("Unknown artist")
     pictureWidth: VLCStyle.gridCover_music_width


=====================================
modules/gui/qt/shaders/SDFAARoundedTexture.frag
=====================================
@@ -40,6 +40,9 @@ layout(std140, binding = 0) uniform buf {
     float qt_Opacity;
     vec4 qt_SubRect_source;
     vec2 size;
+#ifdef CROP_SUPPORT
+    vec2 cropRate;
+#endif
     float radiusTopRight;
     float radiusBottomRight;
     float radiusTopLeft;
@@ -77,7 +80,35 @@ void main()
     // Signed distance:
     float dist = sdRoundBox(p, vec2(size.x / size.y, 1.0), vec4(radiusTopRight, radiusBottomRight, radiusTopLeft, radiusBottomLeft));
 
+#ifdef CROP_SUPPORT
+    vec2 texCoord;
+
+    // if (cropRate.x > 0.0)
+    {
+        float normalCropRate = qt_SubRect_source.z * cropRate.x;
+
+        float k = qt_SubRect_source.z + qt_SubRect_source.x - normalCropRate;
+        float l = qt_SubRect_source.x + normalCropRate;
+
+        texCoord.x = (k - l) / (qt_SubRect_source.z) * (qt_TexCoord0.x - qt_SubRect_source.x) + l;
+    }
+    // else { texCoord.x = qt_TexCoord0.x; }
+
+    // if (cropRate.y > 0.0)
+    {
+        float normalCropRate = qt_SubRect_source.w * cropRate.y;
+
+        float k = qt_SubRect_source.w + qt_SubRect_source.y - normalCropRate;
+        float l = qt_SubRect_source.y + normalCropRate;
+
+        texCoord.y = (k - l) / (qt_SubRect_source.w) * (qt_TexCoord0.y - qt_SubRect_source.y) + l;
+    }
+    // else { texCoord.y = qt_TexCoord0.y; }
+
+    vec4 texel = texture(source, texCoord);
+#else
     vec4 texel = texture(source, qt_TexCoord0);
+#endif
 
     // Soften the outline, as recommended by the Valve paper, using smoothstep:
     // "Improved Alpha-Tested Magnification for Vector Textures and Special Effects"


=====================================
modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag
=====================================
@@ -0,0 +1,131 @@
+#version 440
+
+// TODO: Build system support for preprocessor defines, like CMake's `qt6_add_shaders()`.
+// NOTE: Currently the build system does not support defines for the shaders,
+//       So, the following is imported manually from SDFAARoundedTexture.frag.
+// FIXME: Remove this file when build system starts supporting defines for the
+//        shaders.
+
+#define CROP_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
+//          SAME IN SDFAARoundedTexture.frag.
+
+/*****************************************************************************
+ * 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;
+#ifdef CROP_SUPPORT
+    vec2 cropRate;
+#endif
+    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));
+
+#ifdef CROP_SUPPORT
+    vec2 texCoord;
+
+    // if (cropRate.x > 0.0)
+    {
+        float normalCropRate = qt_SubRect_source.z * cropRate.x;
+
+        float k = qt_SubRect_source.z + qt_SubRect_source.x - normalCropRate;
+        float l = qt_SubRect_source.x + normalCropRate;
+
+        texCoord.x = (k - l) / (qt_SubRect_source.z) * (qt_TexCoord0.x - qt_SubRect_source.x) + l;
+    }
+    // else { texCoord.x = qt_TexCoord0.x; }
+
+    // if (cropRate.y > 0.0)
+    {
+        float normalCropRate = qt_SubRect_source.w * cropRate.y;
+
+        float k = qt_SubRect_source.w + qt_SubRect_source.y - normalCropRate;
+        float l = qt_SubRect_source.y + normalCropRate;
+
+        texCoord.y = (k - l) / (qt_SubRect_source.w) * (qt_TexCoord0.y - qt_SubRect_source.y) + l;
+    }
+    // else { texCoord.y = qt_TexCoord0.y; }
+
+    vec4 texel = texture(source, texCoord);
+#else
+    vec4 texel = texture(source, qt_TexCoord0);
+#endif
+
+    // 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
=====================================
@@ -16,7 +16,8 @@ shader_sources = [
     'PlayerBlurredBackground.frag',
     'HollowRectangularGlow.frag',
     'RectangularGlow.frag',
-    'SDFAARoundedTexture.frag'
+    'SDFAARoundedTexture.frag',
+    'SDFAARoundedTexture_cropsupport.frag'
 ]
 
 shader_files = files(shader_sources)


=====================================
modules/gui/qt/shaders/shaders.qrc
=====================================
@@ -11,5 +11,6 @@
         <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>
     </qresource>
 </RCC>


=====================================
modules/gui/qt/widgets/qml/GridItem.qml
=====================================
@@ -53,12 +53,15 @@ T.ItemDelegate {
     property alias cacheImage: picture.cacheImage
     property alias fallbackImage: picture.fallbackImageSource
 
+    property alias fillMode: picture.fillMode
+
     property alias title: titleLabel.text
     property alias subtitle: subtitleTxt.text
     property alias subtitleVisible: subtitleTxt.visible
     property alias playCoverShowPlay: picture.playCoverShowPlay
     property alias playIconSize: picture.playIconSize
     property alias pictureRadius: picture.radius
+    property alias effectiveRadius: picture.effectiveRadius
     property alias pictureOverlay: picture.imageOverlay
 
     property alias selectedShadow: selectedShadow
@@ -300,12 +303,13 @@ T.ItemDelegate {
 
                 sourceItem: parent
 
-                width: parent.width + viewportHorizontalOffset
-                height: parent.height + viewportVerticalOffset
+                width: picture.paintedWidth + viewportHorizontalOffset
+                height: picture.paintedHeight + viewportVerticalOffset
 
                 rectWidth: sourceSize.width
                 rectHeight: sourceSize.height
 
+                // TODO: Apply painted size's aspect ratio (constant) in source size
                 sourceSize: Qt.size(128, 128)
             }
 
@@ -319,12 +323,13 @@ T.ItemDelegate {
 
                 sourceItem: parent
 
-                width: parent.width + viewportHorizontalOffset
-                height: parent.height + viewportVerticalOffset
+                width: picture.paintedWidth + viewportHorizontalOffset
+                height: picture.paintedHeight + viewportVerticalOffset
 
                 rectWidth: sourceSize.width
                 rectHeight: sourceSize.height
 
+                // TODO: Apply painted size's aspect ratio (constant) in source size
                 sourceSize: Qt.size(128, 128)
 
                 primaryVerticalOffset: VLCStyle.dp(6, VLCStyle.scale)


=====================================
modules/gui/qt/widgets/qml/MediaCover.qml
=====================================
@@ -40,12 +40,12 @@ Item {
                                                           : (fallbackImage.visible ? fallbackImage.effectiveRadius
                                                                                    : 0.0)
 
+    // Aliases
+
     property alias radius: image.radius
 
     property alias color: background.color
 
-    // Aliases
-
     property alias source: image.source
 
     property alias cacheImage: image.cache
@@ -62,6 +62,11 @@ Item {
     required property int pictureWidth
     required property int pictureHeight
 
+    readonly property real paintedWidth: fallbackImage.visible ? fallbackImage.paintedWidth : image.paintedWidth
+    readonly property real paintedHeight: fallbackImage.visible ? fallbackImage.paintedHeight : image.paintedHeight
+
+    property alias fillMode: image.fillMode
+
     // Signals
 
     signal playIconClicked(var point)
@@ -78,7 +83,13 @@ Item {
 
     Rectangle {
         id: background
-        anchors.fill: parent
+
+        anchors.centerIn: parent
+        anchors.alignWhenCentered: true
+
+        width: root.paintedWidth
+        height: root.paintedHeight
+
         radius: root.effectiveRadius
     }
 
@@ -117,6 +128,8 @@ Item {
 
         radius: root.radius
 
+        fillMode: root.fillMode
+
         visible: image.source.toString() === "" //RoundImage.source is a QUrl
                  || image.status === Image.Error
                  || (image.status === Image.Loading && root._loadTimeout)


=====================================
modules/gui/qt/widgets/qml/RoundImage.qml
=====================================
@@ -20,25 +20,63 @@ 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
+    // 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
 
     asynchronous: true
 
     property alias asynchronous: image.asynchronous
     property alias source: image.source
     property alias sourceSize: image.sourceSize
+    property alias sourceClipRect: image.sourceClipRect
     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
+
+    readonly property real paintedWidth: (shaderEffect.readyForVisibility) ? shaderEffect.width
+                                                                           : (image.clip ? image.width : image.paintedWidth)
+    readonly property real paintedHeight: (shaderEffect.readyForVisibility) ? shaderEffect.height
+                                                                            : (image.clip ? image.height : image.paintedHeight)
+
+    // NOTE: Fill mode is not guaranteed to be supported,
+    //       it is supported to the extent QQuickImage
+    //       provides properly filled texture, paintedWidth/Height,
+    //       and applies attributes such as tiling (QSGTexture::Repeat)
+    //       to the texture itself WHILE being invisible.
+    //       Invisible items usually are not requested to
+    //       update their paint node, so it is not clear if QQuickImage
+    //       would synchronize QSGTexture attributes with its
+    //       node when it is invisible (rounding is active).
+    // NOTE: Experiments show that preserve aspect ratio can
+    //       be supported, because QQuickImage provides
+    //       appropriate painted size when it is invisible,
+    //       and the generated texture is pre-filled. In
+    //       the future, Qt Quick may prefer doing this
+    //       within its shader, but for now, we should be
+    //       able to use it. Currently, as of Qt 6.8,
+    //       PreserveAspectCrop, PreserveAspectFit, and
+    //       Stretch can be considered supported.
+    // NOTE: If you need a more guaranteed way to preserve
+    //       the aspect ratio, you can use `sourceSize` with
+    //       only one dimension set. Currently `fillMode` is
+    //       preferred instead of `sourceSize` because we need
+    //       to have control over both width and height.
+    property alias fillMode: image.fillMode
+
+    // Unlike QQuickImage where it needs `clip: true` (clip node)
+    // for `PreserveAspectCrop`, with the custom shader we do not
+    // need that. This makes it feasible to use `PreserveAspectCrop`
+    // in delegate, where we want to have effective batching. Note
+    // that such option is still not free, because the fragment
+    // shader has to do additional calculations that way. Also note
+    // that a clip node is still necessary if radius is 0, as in
+    // that case the default image is used directly.
+    fillMode: Image.PreserveAspectFit
 
     property real radius
-    readonly property real effectiveRadius: (shaderEffect.readyForVisibility ?? shaderEffect.visible) ? radius : 0.0
+    readonly property real effectiveRadius: shaderEffect.readyForVisibility ? radius : 0.0
 
     // NOTE: Note the distinction between ShaderEffect and
     //       ShaderEffectSource. ShaderEffect is no different
@@ -49,7 +87,14 @@ Item {
     ShaderEffect {
         id: shaderEffect
 
-        anchors.fill: parent
+        anchors.alignWhenCentered: true
+        anchors.centerIn: parent
+
+        implicitWidth: image.implicitWidth
+        implicitHeight: image.implicitHeight
+
+        width: (image.fillMode === Image.PreserveAspectCrop) ? root.width : image.paintedWidth
+        height: (image.fillMode === Image.PreserveAspectCrop) ? root.height : image.paintedHeight
 
         visible: readyForVisibility
 
@@ -74,12 +119,32 @@ Item {
         readonly property double softEdgeMin: -0.01
         readonly property double softEdgeMax:  0.01
 
+        readonly property size cropRate: {
+            let ret = Qt.size(0.0, 0.0)
+
+            // No need to calculate if PreserveAspectCrop is not used
+            if (root.fillMode !== Image.PreserveAspectCrop)
+                return ret
+
+            const implicitScale = implicitWidth / implicitHeight
+            const scale = width / height
+
+            if (scale > implicitScale)
+                ret.height = (implicitHeight - implicitWidth) / 2 / implicitHeight
+            else if (scale < implicitScale)
+                ret.width = (implicitWidth - implicitHeight) / 2 / implicitWidth
+
+            return ret
+        }
+
         // 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"
+        fragmentShader: (cropRate.width > 0.0 || cropRate.height > 0.0) ? "qrc:///shaders/SDFAARoundedTexture_cropsupport.frag.qsb"
+                                                                        : "qrc:///shaders/SDFAARoundedTexture.frag.qsb"
+
     }
 
     Image {
@@ -94,6 +159,10 @@ Item {
         // would appear in the grabbed image.
         visible: !shaderEffect.readyForVisibility
 
-        fillMode: Image.PreserveAspectCrop
+        // Clipping is not a big concern for rendering performance
+        // with the software or openvg scene graph adaptations.
+        // We can use clipping as QQuickImage suggests with
+        // PreserveAspectCrop in that case:
+        clip: (GraphicsInfo.shaderType !== GraphicsInfo.RhiShader) && (fillMode === Image.PreserveAspectCrop)
     }
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7ee26e6fa72ce6b4dab5ad5d7ad4c31a3e036133...8c857503f6b5c224f0bdd6dd3d0580073060c691

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