[vlc-commits] [Git][videolan/vlc][master] 2 commits: qml: allow using external texture provider in `ImageExt`

Steve Lhomme (@robUx4) gitlab at videolan.org
Sun Nov 2 13:25:06 UTC 2025



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
135a9d44 by Fatih Uzunoglu at 2025-11-02T13:08:56+00:00
qml: allow using external texture provider in `ImageExt`

- - - - -
7a7bf4ea by Fatih Uzunoglu at 2025-11-02T13:08:56+00:00
qt: introduce `EnhancedImageExt.qml`

Supports both:
1) indirection for the original source image (to expose
sub-texturing).
2) providing interface for arbitrary texture providers
(to expose `ImageExt` features).

Which in turn makes these possible:
1) Making use of `ImageExt` features, such as rounding
and outlining for any arbitrary texture provider.
2) Providing sub-texturing for `ImageExt`, and at the
same time for any arbitrary texture provider.

- - - - -


4 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/meson.build
- + modules/gui/qt/widgets/qml/EnhancedImageExt.qml
- modules/gui/qt/widgets/qml/ImageExt.qml


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1304,6 +1304,7 @@ libqml_module_widgets_a_QML = \
 	widgets/qml/ViewHeader.qml \
 	widgets/qml/ProgressIndicator.qml \
 	widgets/qml/ImageExt.qml \
+	widgets/qml/EnhancedImageExt.qml \
 	widgets/qml/ScrollBarExt.qml \
 	widgets/qml/FastBlend.qml \
 	widgets/qml/RadioButtonExt.qml \


=====================================
modules/gui/qt/meson.build
=====================================
@@ -894,6 +894,7 @@ qml_modules += {
         'widgets/qml/ViewHeader.qml',
         'widgets/qml/ProgressIndicator.qml',
         'widgets/qml/ImageExt.qml',
+        'widgets/qml/EnhancedImageExt.qml',
         'widgets/qml/ScrollBarExt.qml',
         'widgets/qml/FastBlend.qml',
         'widgets/qml/RadioButtonExt.qml',


=====================================
modules/gui/qt/widgets/qml/EnhancedImageExt.qml
=====================================
@@ -0,0 +1,74 @@
+/*****************************************************************************
+ * Copyright (C) 2025 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
+import QtQuick.Window
+
+import VLC.Widgets
+import VLC.Util
+
+ImageExt {
+    id: root
+
+    textureProviderItem: textureProvider
+
+    // NOTE: Unlike `sourceClipRect`, `textureSubRect` acts as viewport for the texture,
+    //       thus faster. No manipulations are done either to the image or the texture.
+    //       Prefer using `textureSubRect` if the rectangle is not static, and prefer using
+    //       `sourceClipRect` otherwise to save system and video  memory. As a reminder,
+    //       implicit size reflects the texture size.
+    // WARNING: Using this property may be incompatible with certain filling modes.
+    property alias textureSubRect: textureProvider.textureSubRect
+
+    property alias textureProvider: textureProvider
+
+    // NOTE: Target is by default the texture provider `ImageExt` provides, but it can be
+    //       set to any texture provider. For example, `ShaderEffectSource` can be displayed
+    //       rounded this way.
+    property alias targetTextureProvider: textureProvider.source
+    targetTextureProvider: sourceTextureProviderItem
+
+    // No need to load images in this case:
+    loadImages: (targetTextureProvider === root.sourceTextureProviderItem)
+
+    TextureProviderItem {
+        id: textureProvider
+
+        // `Image` interface, as `ImageExt` needs it:
+        readonly property int status: (source instanceof Image ? ((source.status === Image.Ready && observer.isValid) ? Image.Ready : Image.Loading)
+                                                               : (observer.isValid ? Image.Ready : Image.Null))
+
+        implicitWidth: (source instanceof Image) ? source.implicitWidth : textureSize.width
+        implicitHeight: (source instanceof Image) ? source.implicitHeight : textureSize.height
+
+        property size textureSize
+
+        Connections {
+            target: root.Window.window
+            enabled: root.visible && textureProvider.source && !(textureProvider.source instanceof Image)
+
+            function onAfterAnimating() {
+                textureProvider.textureSize = observer.textureSize
+            }
+        }
+
+        TextureProviderObserver {
+            id: observer
+            source: textureProvider
+        }
+    }
+}


=====================================
modules/gui/qt/widgets/qml/ImageExt.qml
=====================================
@@ -51,10 +51,10 @@ Item {
     asynchronous: true
 
     property alias asynchronous: image.asynchronous
-    property alias source: image.source
+    property url source
     property alias sourceSize: image.sourceSize
     property alias sourceClipRect: image.sourceClipRect
-    property alias status: image.status
+    readonly property int status: shaderEffect.source.status
     property alias shaderStatus: shaderEffect.status
     property alias cache: image.cache
 
@@ -69,7 +69,17 @@ Item {
     // but rather as `Item`.
     // WARNING: Consumers who downcast this item to `Image` are doing this on their own
     //          discretion. It is discouraged, but not forbidden (or evil).
-    readonly property Item textureProviderItem: image
+    readonly property Item sourceTextureProviderItem: image
+    // NOTE:    It is allowed to adjust this property to use an external texture provider, where
+    //          in that case the texture provider item should provide a subset of `Image`'s
+    //          interface, that is, provide `status` and `implicitWidth`/`implicitHeight` that
+    //          reflect the texture size.
+    property Item textureProviderItem: sourceTextureProviderItem
+
+    // NOTE:    If the texture provider used does not require `ImageExt` to load the image, this
+    //          should be disabled.
+    // WARNING: In non-RHI mode, this setting is not respected.
+    property bool loadImages: (textureProviderItem === image)
 
     // Padding represents how much the content is shrunk. For now this is a readonly property.
     // Currently it only takes the `softEdgeMax` into calculation, as that's what the shader
@@ -107,6 +117,11 @@ Item {
     //       only one dimension set. Currently `fillMode` is
     //       preferred instead of `sourceSize` because we need
     //       to have control over both width and height.
+    // NOTE: Fill mode can be overridden, even when a foreign
+    //       texture provider is used. In that case, `ImageExt`
+    //       makes the required painted size calculations itself.
+    //       The mode is still subject to the same restrictions
+    //       mentioned above.
     property alias fillMode: image.fillMode
 
     // Unlike QQuickImage where it needs `clip: true` (clip node)
@@ -150,17 +165,21 @@ Item {
         anchors.alignWhenCentered: true
         anchors.centerIn: parent
 
-        implicitWidth: (image.status === Image.Ready) ? image.implicitWidth : 64
-        implicitHeight: (image.status === Image.Ready) ? image.implicitHeight : 64
+        implicitWidth: (source.status === Image.Ready) ? source.implicitWidth : 64
+        implicitHeight: (source.status === Image.Ready) ? source.implicitHeight : 64
 
-        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
+        width: ((source.status !== Image.Ready) || (root.fillMode === Image.PreserveAspectCrop)) ? root.width : effectivePaintedSize.width
+        height: ((source.status !== Image.Ready) || (root.fillMode === Image.PreserveAspectCrop)) ? root.height : effectivePaintedSize.height
 
         visible: readyForVisibility
 
-        readonly property bool readyForVisibility: (image.status === Image.Ready) /* TODO: investigate using TextureProviderObserver::isValid instead */ &&
+        readonly property bool readyForVisibility: (source.status === Image.Ready) &&
                                                    (GraphicsInfo.shaderType === GraphicsInfo.RhiShader) &&
-                                                   (root.radius > 0.0 || root.borderWidth > 0 || backgroundColor.a > 0.0 || root.fillMode === Image.PreserveAspectCrop)
+                                                   (root.radius > 0.0 ||
+                                                    root.borderWidth > 0 ||
+                                                    backgroundColor.a > 0.0 ||
+                                                    source !== image ||
+                                                    root.fillMode === Image.PreserveAspectCrop)
 
         smooth: root.smooth
 
@@ -196,28 +215,66 @@ Item {
                 return ret
 
             // No need to calculate if image is not ready
-            if (image.status !== Image.Ready)
+            if (source.status !== Image.Ready)
                 return ret
 
-            const implicitScale = implicitWidth / implicitHeight
-            const scale = width / height
+            const implicitRatio = implicitWidth / implicitHeight
+            const ratio = width / height
 
-            if (scale > implicitScale)
+            if (ratio > implicitRatio)
                 ret.height = (implicitHeight - implicitWidth) / 2 / implicitHeight
-            else if (scale < implicitScale)
+            else if (ratio < implicitRatio)
                 ret.width = (implicitWidth - implicitHeight) / 2 / implicitWidth
 
             return ret
         }
 
+        // If source is a foreign `Image`, its `paintedWidth`/`paintedHeight` would not be based on the root size and can not be used.
+        // In that case, we calculate the painted size ourselves (see `effectivePaintedSize`). We also allow overriding the source's
+        // fill mode in that case. If source is a foreign item we still do the same to allow overriding the fill mode here.
+        readonly property size effectivePaintedSize: {
+            let ret = Qt.size(0.0, 0.0)
+
+            // No need to calculate if foreign texture provider is not used:
+            if (source === image)
+                return Qt.size(source.paintedWidth, source.paintedHeight)
+
+            // No need to calculate if image is not ready
+            if (source.status !== Image.Ready)
+                return ret
+
+            // NOTE: Calculations are based on `QQuickImage`
+            // WARNING: Note that `PreserveAspectCrop` mode does not use painted size and therefore is not handled here (see `cropRate`).
+            if (root.fillMode === Image.PreserveAspectFit) {
+                const widthScale = root.width / implicitWidth
+                const heightScale = root.height / implicitHeight
+
+                if (widthScale <= heightScale) {
+                    ret.width = root.width
+                    ret.height = widthScale * implicitHeight
+                } else {
+                    ret.width = heightScale * implicitWidth
+                    ret.height = root.height
+                }
+            } else if (root.fillMode === Image.Pad) {
+                ret.width = implicitWidth
+                ret.height = implicitHeight
+            } else {
+                ret.width = root.width
+                ret.height = root.height
+            }
+
+            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 double borderRange: (source.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
+        readonly property Item source: root.textureProviderItem ?? image
 
         fragmentShader: (cropRate.width > 0.0 || cropRate.height > 0.0) || (root.borderWidth > 0) ? "qrc:///shaders/SDFAARoundedTexture_cropsupport_bordersupport.frag.qsb"
                                                                                                   : "qrc:///shaders/SDFAARoundedTexture.frag.qsb"
@@ -231,6 +288,8 @@ Item {
 
         smooth: root.smooth
 
+        source: (root.loadImages || (shaderEffect.GraphicsInfo.shaderType !== GraphicsInfo.RhiShader)) ? root.source : ""
+
         // Image should not be visible when there is rounding and RHI shader is supported.
         // This is simply when the shader effect is invisible. However, Do not use `!shaderEffect.visible`,
         // because the root item may be invisible (`grabToImage()` call on an invisible



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/3b7105ae14d0091cc537c6ed8e68ae5906e0b219...7a7bf4ea9766e4127908202a3a884c63f6709832

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