Une solution officielle intégrée existe à partir de Qt 5 grâce au module QtGraphicalEffects
et je suis assez surpris de constater que personne n'a proposé une solution aussi simple. Si vous ciblez Qt 6.x, QtGraphicalEffects
est malheureusement obsolète, passez donc à la deuxième partie de la réponse qui propose une solution indépendante de QtGraphicalEffects
.
Solution QtGraphicalEffects
Entre autres effets, OpacityMask
est le type à exploiter à cette fin. L'idée est de masquer l'Image source avec un Rectangle ayant un rayon
correctement défini. Voici le plus simple exemple utilisant le superposition:
Image {
id: img
property bool rounded: true
property bool adapt: true
layer.enabled: rounded
layer.effect: OpacityMask {
maskSource: Item {
width: img.width
height: img.height
Rectangle {
anchors.centerIn: parent
width: img.adapt ? img.width : Math.min(img.width, img.height)
height: img.adapt ? img.height : width
radius: Math.min(width, height)
}
}
}
}
Ce code minimal produit un résultat agréable pour les images carrées mais prend également en compte les images non carrées via la variable adapt
. En définissant le drapeau sur false
, le masque produit sera toujours un cercle, indépendamment de la taille de l'image. C'est possible grâce à l'utilisation d'un Item
externe qui remplit la source et permet au vrai masque (le Rectangle interne) d'être dimensionné à plaît. Vous pouvez évidemment vous débarrasser de l'Item externe si vous visez simplement un masque qui remplit la source, indépendamment de son rapport hauteur-largeur.
Voici une image de chat mignonne avec un format carré (gauche), un format non carré avec adapt: true
(centre) et enfin un format non carré et adapt: false
(droite):
Les détails de mise en œuvre de cette solution sont très similaires à ceux de la réponse basée sur les shaders dans l'autre bonne réponse (cf. le code source QML pour OpacityMask
qui peut être trouvé ici - SourceProxy
retourne simplement un ShaderEffectSource
bien formé pour alimenter l'effet).
Solution sans dépendance
Si vous ne souhaitez pas - ou ne pouvez pas - dépendre du module QtGraphicalEffects
(en fait, de la présence de OpacityMask.qml
), vous pouvez réimplémenter l'effet avec des shaders. Outre la solution déjà fournie, une autre approche consiste à utiliser les fonctions step
, smoothstep
et fwidth
. Voici le code :
import QtQuick 2.5
Image {
id: image
property bool rounded: true
property bool adapt: true
layer.enabled: rounded
layer.effect: ShaderEffect {
property real adjustX: image.adapt ? Math.max(width / height, 1) : 1
property real adjustY: image.adapt ? Math.max(1 / (width / height), 1) : 1
fragmentShader: "
#ifdef GL_ES
precision lowp float;
#endif // GL_ES
varying highp vec2 qt_TexCoord0;
uniform highp float qt_Opacity;
uniform lowp sampler2D source;
uniform lowp float adjustX;
uniform lowp float adjustY;
void main(void) {
lowp float x, y;
x = (qt_TexCoord0.x - 0.5) * adjustX;
y = (qt_TexCoord0.y - 0.5) * adjustY;
float delta = adjustX != 1.0 ? fwidth(y) / 2.0 : fwidth(x) / 2.0;
gl_FragColor = texture2D(source, qt_TexCoord0).rgba
* step(x * x + y * y, 0.25)
* smoothstep((x * x + y * y) , 0.25 + delta, 0.25)
* qt_Opacity;
}"
}
}
De manière similaire à la première approche, les propriétés rounded
et adapt
sont ajoutées pour contrôler l'apparence visuelle de l'effet comme discuté ci-dessus.