43 votes

Image coins arrondis en QML

À ma surprise, le composant Image n'a pas de propriété radius. J'ai essayé d'émuler les coins arrondis en mettant l'image dans un Rectangle arrondi, mais cela ne clipse pas les coins.

Rectangle {
    anchors.right: rectContentBg.left
    anchors.top: rectContentBg.top
    anchors.margins: 8

    radius: 8

    width: 64
    height: 64

    Image {
        id: imgAuthor

        opacity: 1
        smooth: false

        anchors.fill: parent

        source: "qrc:/res/sample_avatar.jpg"
    }
}

Comment puis-je créer une image avec des coins arrondis correctement?

65voto

BaCaRoZzo Points 5883

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):

description de l'image

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;
        }"
    }
}

description de l'image

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.

1 votes

Super réponse, merci! Je veux souligner quelque chose dans votre première approche que si vous utilisez un seul rectangle dans un élément, vous n'avez pas besoin de déclarer l'élément et pouvez utiliser directement le rectangle car le rectangle hérite de l'élément.

0 votes

Pourquoi emballez-vous le Rectangle dans un Item?

2 votes

@Phrogz l'effet remplit la source et même si je définis adapt à true je ne peux pas obtenir le résultat dans la troisième image de la première série. Si vous avez une image carrée comme dans la première image, l'élément extérieur Item est simplement inutile.

8voto

DenimPowell Points 943

Ce code vous aiderait

Rectangle {
    width: 200
    height: 200

    color: "transparent"

    //ce Rectangle est nécessaire pour conserver le fillMode de l'image source
    Rectangle {
        id: imageSource

        anchors.fill: parent
        Image {
            anchors.fill: parent
            source: "votre_chemin_d'accès_de_fichier_image"

            fillMode: Image.PreserveAspectCrop
        }
        visible: false

        layer.enabled: true
    }

    Rectangle {
        id: maskLayer
        anchors.fill: parent
        radius: parent.width / 2

        color: "red"

        border.color: "black"

        layer.enabled: true
        layer.samplerName: "maskSource"
        layer.effect: ShaderEffect {

            property var colorSource: imageSource
            fragmentShader: "
                uniform lowp sampler2D colorSource;
                uniform lowp sampler2D maskSource;
                uniform lowp float qt_Opacity;
                varying highp vec2 qt_TexCoord0;
                void main() {
                    gl_FragColor =
                        texture2D(colorSource, qt_TexCoord0)
                        * texture2D(maskSource, qt_TexCoord0).a
                        * qt_Opacity;
                }
            "
        }

    }

    // ne dessine que la ligne de bordure
    Rectangle {
        anchors.fill: parent

    radius: parent.width / 2

        border.color: "black"
        border.width: 2

        color: "transparent"
    }
}

7voto

Lorsque votre arrière-plan est d'une couleur unie ou lorsque vous ne déplacez jamais l'image, un moyen rapide de créer des coins arrondis est de superposer votre Image avec une autre (ou avec un BorderImage) qui ne dessine que les coins.

Lorsque ce n'est pas une option, mais que vous utilisez OpenGL, une autre méthode consiste à appliquer un masque à l'image à travers un pixel shader. Voir http://blog.qt.digia.com/blog/2011/05/03/qml-shadereffectitem-on-qgraphicsview/ pour un plugin qui fonctionne sur Qt 4.

Enfin, il est également possible d'écrire un QDeclarativeImageProvider qui prétraite votre image pour arrondir les coins.

6voto

Helmut S Points 104

Si vous avez un arrière-plan unicolore, vous pouvez dessiner avec la bordure d'un rectangle arrondi au-dessus.

Image{
    id:img
}
Rectangle { // coins arrondis pour l'img
    anchors.fill: img
    color: "transparent"
    border.color: "blue" // couleur de l'arrière-plan
    border.width: 4
    radius: 4
}

1 votes

Cela fonctionne tant que le rayon est plus grand que la largeur de la bordure

1 votes

Bien que cette réponse fonctionne, je suis confronté à des artefacts d'affichage occasionnels, ce qui ne donne pas un aspect très professionnel. Heureusement, les défauts ne concernent que les lignes verticales (sur win7), donc 2 rectangles bien placés peuvent résoudre le problème.

6voto

Max Paperno Points 850

Alors que la réponse acceptée et celle de @fury ont fonctionné également bien pour moi (Qt 5.9.3), elles ont toutes deux laissé quelques aberrations dans les coins lorsqu'elles ont été appliquées aux images raster (je n'en avais pas avec SVG). Ce qui a le mieux fonctionné pour moi dans tous les cas était d'appliquer le OpacityMask à un élément environnant, par exemple comme le rectangle dans le message d'origine.

Rectangle {
    id: root;
    anchors.right: rectContentBg.left
    anchors.top: rectContentBg.top
    anchors.margins: 8

    radius: 8

    width: 64
    height: 64

    // appliquer le masque aux coins arrondis
    layer.enabled: true
    layer.effect: OpacityMask {
        maskSource: Rectangle {
            x: root.x; y: root.y
            width: root.width
            height: root.height
            radius: root.radius
        }
    }

    Image {
        id: imgAuthor
        opacity: 1
        smooth: false
        anchors.fill: parent
        source: "qrc:/res/sample_avatar.jpg"
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X