[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