【问题标题】:Image rounded corners in QMLQML中的图像圆角
【发布时间】:2022-02-09 00:46:50
【问题描述】:

令我惊讶的是,Image 组件没有 radius 属性。我尝试通过将图像放在圆角Rectangle 中来模拟圆角,但它不会剪切角。

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

如何正确创建带圆角的图像?

【问题讨论】:

    标签: qml qt5 qtquick2


    【解决方案1】:

    由于QtGraphicalEffects 模块,Qt 5 存在内置的官方解决方案,我很惊讶地发现没有人提供如此简单的解决方案。如果您的目标是 Qt 6.x QtGraphicalEffects 不幸被弃用,请跳到答案的第二部分,该部分提出了一个独立于 QtGraphicalEffects 的解决方案。

    QtGraphicalEffects解决方案

    在其他影响中OpacityMask 是用于此目的的类型。这个想法是用具有正确设置的radiusRectangle 来掩盖源Image。这是使用layering 的最简单示例:

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

    这个最小代码对方形图像产生了很好的结果,但是 它还通过adapt 变量考虑非方形图像。通过将标志设置为false,生成的蒙版将始终为圆形,而与图像大小无关。这是可能的,因为使用了外部Item 填充源并允许真正的掩码(内部Rectangle)调整大小。 你显然可以摆脱外部Item,如果你只是瞄准一个填充源的掩码,不管它的纵横比如何

    这是一张可爱的猫咪图片,正方形格式(left),非正方形格式adapt: true居中),最后是非正方形格式和adapt: false):

    此解决方案的实现细节与其他nice answer 中基于着色器的答案非常相似(参见OpacityMask 的QML 源代码,可在here - SourceProxy 中找到),只需返回一个格式良好的ShaderEffectSource 来提供效果)。

    无深度解决方案

    如果您不想 - 或不能 - 依赖 QtGraphicalEffects 模块(好吧,实际上是在 OpacityMask.qml 的存在上),您可以使用着色器重新实现效果.除了已经提供的解决方案之外,另一种方法是使用stepsmoothstepfwidth 函数。代码如下:

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

    与第一种方法类似,添加了roundedadapt 属性来控制效果的视觉外观,如上所述。

    【讨论】:

    • 很好的答案,谢谢!我想在您的第一种方法中指出一点,如果您在项目中使用单个矩形,则不需要声明该项目并且可以直接使用矩形,因为矩形继承了项目。
    • 为什么将Rectangle 包裹在Item 中?
    • @Phrogz 效果填充源,即使我将adapt 设置为true,我也无法在第一个系列的第三张图片中获得结果。如果你有像第一张那样的方形图片,那么外面的Item根本没用。
    • 想添加,如果你定位移动设备你应该设置layer.textureSize,所以你保持全分辨率
    • QtGraphicalEffects 在 Qt6 中已停产
    【解决方案2】:

    这段代码可以帮助你

    Rectangle {
        width: 200
        height: 200
    
        color: "transparent"
    
        //this Rectangle is needed to keep the source image's fillMode
        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;
                    }
                "
            }
    
        }
    
        // only draw border line
        Rectangle {
            anchors.fill: parent
    
            radius: parent.width / 2
    
            border.color: "black"
            border.width: 2
    
            color: "transparent"
        }
    }
    

    【讨论】:

      【解决方案3】:

      当您的背景是纯色或从不移动图像时,制作圆角的一种快速方法是将您的 Image 与仅绘制角的另一个(或 BorderImage)重叠.

      如果这不是一个选项,但您使用的是 OpenGL,那么另一种方法是通过像素着色器将遮罩应用于图像。请参阅 http://blog.qt.digia.com/blog/2011/05/03/qml-shadereffectitem-on-qgraphicsview/ 了解在 Qt 4 之上工作的插件。

      最后,还可以编写一个 QDeclarativeImageProvider 来预处理您的图像以使角变圆。

      【讨论】:

        【解决方案4】:

        如果你有一个单色背景,你可以在顶部画一个圆角矩形的边框。

        Image{
            id:img
        }
        Rectangle { // rounded corners for img
            anchors.fill: img
            color: "transparent"
            border.color: "blue" // color of background
            border.width: 4
            radius: 4
        }
        

        【讨论】:

        • 只要半径大于border.width就可以工作
        • 虽然这个答案确实有效,但我偶尔会遇到渲染伪影,这意味着它本身看起来并不专业。幸运的是,故障只是垂直线(在 win7 上),所以 2 个放置良好的矩形可以清理它。
        【解决方案5】:

        虽然接受的答案和the one from @fury 对我来说同样适用(Qt 5.9.3),但它们在应用于光栅图像时都会在角落留下一些像差(没有那些带有 SVG 的图像)。在所有情况下对我最有效的方法是将OpacityMask 应用于周围的项目,例如就像原始帖子中的矩形一样。

        Rectangle {
            id: root;
            anchors.right: rectContentBg.left
            anchors.top: rectContentBg.top
            anchors.margins: 8
        
            radius: 8
        
            width: 64
            height: 64
        
            // apply rounded corners mask
            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"
            }
        }
        

        【讨论】:

          【解决方案6】:

          QML 目前只支持矩形裁剪,但你可能想看看 qt-components 项目中的 DeclarativeMaskedImage:

          http://qt.gitorious.org/qt-components/qt-components/blobs/master/src/symbian/sdeclarativemaskedimage.h

          【讨论】:

          • 他们真的应该做到这一点,而不是他们现在正在做的花哨的 3D 东西:/
          • DeclarativeMaskedImage 链接
          【解决方案7】:

          我知道我参加聚会有点晚了,但我是通过谷歌搜索到这里的,所以我想我会帮助后代:) QtGraphicalEffects OpacityMask 应该更简单地做到这一点(我对图层效果方法有疑问)

          Image {
              id: imgAuthor
          
              width: 64
              height: 64
          
              source: "qrc:/res/sample_avatar.jpg"
          
              visible: false // this is needed or the corners of the image will be visible underneath the opacity mask
          }
          
          OpacityMask {
              anchors.fill: imgAuthor
              source: imgAuthor
              maskSource: Rectangle {
                  width: imgAuthor.width
                  height: imgAuthor.height
                  radius: 8
                  visible: false // this also needs to be invisible or it will cover up the image
              }
          }
          

          【讨论】:

          • 不适用于Qt6,QtGraphicalEffects模块已停产
          【解决方案8】:

          如果你有一个单色背景,你可以在顶部画一个圆角矩形的边框。

          Item {
              property int radius: 0
              property color bgColor: "#000000"
              property int drawRadius: radius > 0 ? radius : width/2
          
              Image {
                  anchors.fill: parent
                  sourceSize: Qt.size(width, height)
                  asynchronous: true
              }
          
              Canvas {
                  anchors.fill: parent
                  antialiasing: true
                  onPaint: {
                      var ctx = getContext("2d")
                      ctx.fillStyle = bgColor
                      ctx.beginPath()
                      ctx.rect(0, 0, width, height)
                      ctx.fill()
          
                      ctx.beginPath()
                      ctx.globalCompositeOperation = 'source-out'
                      ctx.roundedRect(0, 0, width, height, drawRadius, drawRadius)
                      ctx.fill()
                  }
              }
          }

          【讨论】:

          • Qt6 的正确答案之一,其中 QtGraphicalEffects 模块只是旧版
          猜你喜欢
          • 2017-07-14
          • 2019-05-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-30
          • 1970-01-01
          相关资源
          最近更新 更多