43 votes

Image coins arrondis en QML

À ma grande 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 depuis Qt 5 grâce au module QtGraphicalEffects et je suis assez surpris de constater que personne n'a fourni une solution aussi simple. Si vous ciblez Qt 6.x QtGraphicalEffects est malheureusement obsolète donc passez à la deuxième partie de la réponse qui propose une solution indépendante de QtGraphicalEffects.

Solution QtGraphicalEffects

Parmi les 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 en utilisant le calque:

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 joli résultat 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. Cela est possible grâce à l'utilisation d'un Item externe qui remplit la source et permet au véritable masque (le Rectangle interne) d'être dimensionné en conséquence. Vous pouvez bien sûr 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 jolie image de chat avec un format carré (à gauche), un format non carré avec adapt: true (au centre) et enfin un format non carré et adapt: false (à droite) :

entrer la description de l'image ici

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 (voir le code source QML pour OpacityMask qui peut être trouvé ici - SourceProxy renvoie simplement un ShaderEffectSource bien formé pour alimenter l'effet).

Solution sans dépendance

Si vous ne voulez pas - ou ne pouvez pas - dépendre du module QtGraphicalEffects (enfin, de la présence de OpacityMask.qml``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;
        }"
    }
}

entrer la description de l'image ici

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é précédemment.

1 votes

Super réponse, merci! Je veux souligner quelque chose dans votre première approche : 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é comme dans la première image, l'élément Item externe est tout 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: "your_image_file_path"

            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 que vous ne déplacez jamais l'image, un moyen rapide de réaliser des coins arrondis est de superposer votre Image avec une autre image (ou avec un BorderImage) qui ne dessine que les coins.

Si cette option n'est pas envisageable, mais que vous utilisez OpenGL, une autre possibilité est d'appliquer un masque à l'image via un pixel shader. Voir http://blog.qt.digia.com/blog/2011/05/03/qml-shadereffectitem-on-qgraphicsview/ pour un plugin qui fonctionne avec 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 par-dessus.

Image{
    id:img
}
Rectangle { // coins arrondis pour 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

Pendant que cette réponse fonctionne, je suis laissé avec des artefacts de rendu occasionnels ce qui signifie qu'il ne semble pas très professionnel par lui-même. Heureusement, les défauts ne concernent que les lignes verticales (sur win7) donc 2 rectangles bien placés peuvent nettoyer cela.

6voto

Max Paperno Points 850

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

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