[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