最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

svg - Dynamically zoomable vector image in QML - Stack Overflow

programmeradmin1浏览0评论

I have a vector graphics file (here a svg) that I would like to display in a window and be able to zoom in and out of at runtime (using CTRL +/-/0) without loosing quality. Unfortunately Qt seems to be loading the image with fixed size and therefore not vectorized. When zooming in, the image gets pixelized.

I am using an Image, a Flickable and resizeContent() + returnToBounds(). I could not find any work around online, the only solution I found was to set the sourceSize to zoom in/out but this does not work at runtime.

Here is my code :

import QtQuick
import QtQuick.Layouts
import QtQuick.Pdf


Window {
    id:root
    visibility:Window.Maximized
    visible: true
    title: qsTr("XXX")
    property bool ctrlPressed : false
    property real maxScale : 5
    property real minScale:0.95

    RowLayout{
        anchors.fill:parent
        anchors.leftMargin: 10
        anchors.rightMargin : 10
        spacing:0
        Rectangle{
            id:menucolumn
            color:"transparent"
            width:340
            height:50
        }
        Item{
            id:container
            Layout.alignment: Qt.AlignTop
            Layout.fillHeight: true
            Layout.preferredWidth: root.width - menucolumn.width - bunkerButton.width
            focus:true
            Flickable{
                id:flickArea
                anchors.fill:parent
                clip:true

                contentWidth: container.width
                contentHeight: container.height
                flickableDirection:Flickable.HorizontalAndVerticalFlick
                property real zoom : 0.95
                onZoomChanged: {
                    text1.text = "Zoom : " + Math.round(flickArea.zoom*100) + "%"
                    var zoomPoint = Qt.point(flickArea.width/2 + flickArea.contentX,
                                                 flickArea.height/2+flickArea.contentY);

                    flickArea.resizeContent((vueEnsembleImg.width * flickArea.zoom), (vueEnsembleImg.height * flickArea.zoom), zoomPoint);
                    //vueEnsembleImg.sourceSize.height =container.height*flickArea.zoom
                    //vueEnsembleImg.sourceSize.width=container.width*flickArea.zoom
                    flickArea.returnToBounds();
                }
                
            Image{
                id:vueEnsembleImg
                source: "image.svg"
                anchors.centerIn:parent
                fillMode: Image.PreserveAspectFit
                scale:flickArea.zoom
                smooth:false
                antialiasing:false
                transform: Scale {
                    id: scaleID ;
                    origin.x: flickArea.contentX + flickArea.width * flickArea.visibleArea.widthRatio / 2
                    origin.y: flickArea.contentY + flickArea.height * flickArea.visibleArea.heightRatio / 2
                }
               
            }

        }
        Text{
            id:text1
            visible:false
            anchors.top:parent.top
            anchors.left:parent.left
            text:"Zoom : " + Math.round(flickArea.zoom*100) + "%"
        }

        Keys.onPressed: (event)=> {
            if(event.key === Qt.Key_Control){
                root.ctrlPressed = true
                text1.visible = true
            }

            if((event.key === Qt.Key_Plus) & (root.ctrlPressed)){
                if(flickArea.zoom < root.maxScale){//if(vueEnsembleImg.scale < root.maxScale){
                     flickArea.zoom += 0.05
                }

            }
            else if((event.key === Qt.Key_Minus) & (root.ctrlPressed)){
                if(flickArea.zoom > root.minScale){//if(vueEnsembleImg.scale > root.minScale){
                    flickArea.zoom -= 0.05
                }
            }
            else if ((event.key === Qt.Key_0) & (root.ctrlPressed)){
                flickArea.zoom = 0.95
            }

        }
        Keys.onReleased: (event)=>{
            if(event.key === Qt.Key_Control){
                text1.visible = false
                root.ctrlPressed = false
            }
            }
        }
        Rectangle{
            id:bunkerButton
            color:"transparent"
            width:110
            height:50
        }
    }
}

I also tried using a pdfPageImage but since it inherits from Image it behaves the same for this. I am using Qt 6.7.1. Unfortunately I really have to be able to do this as it is a request from my client. Is there a way to achieve this ? Perhaps with other object types ?

I have a vector graphics file (here a svg) that I would like to display in a window and be able to zoom in and out of at runtime (using CTRL +/-/0) without loosing quality. Unfortunately Qt seems to be loading the image with fixed size and therefore not vectorized. When zooming in, the image gets pixelized.

I am using an Image, a Flickable and resizeContent() + returnToBounds(). I could not find any work around online, the only solution I found was to set the sourceSize to zoom in/out but this does not work at runtime.

Here is my code :

import QtQuick
import QtQuick.Layouts
import QtQuick.Pdf


Window {
    id:root
    visibility:Window.Maximized
    visible: true
    title: qsTr("XXX")
    property bool ctrlPressed : false
    property real maxScale : 5
    property real minScale:0.95

    RowLayout{
        anchors.fill:parent
        anchors.leftMargin: 10
        anchors.rightMargin : 10
        spacing:0
        Rectangle{
            id:menucolumn
            color:"transparent"
            width:340
            height:50
        }
        Item{
            id:container
            Layout.alignment: Qt.AlignTop
            Layout.fillHeight: true
            Layout.preferredWidth: root.width - menucolumn.width - bunkerButton.width
            focus:true
            Flickable{
                id:flickArea
                anchors.fill:parent
                clip:true

                contentWidth: container.width
                contentHeight: container.height
                flickableDirection:Flickable.HorizontalAndVerticalFlick
                property real zoom : 0.95
                onZoomChanged: {
                    text1.text = "Zoom : " + Math.round(flickArea.zoom*100) + "%"
                    var zoomPoint = Qt.point(flickArea.width/2 + flickArea.contentX,
                                                 flickArea.height/2+flickArea.contentY);

                    flickArea.resizeContent((vueEnsembleImg.width * flickArea.zoom), (vueEnsembleImg.height * flickArea.zoom), zoomPoint);
                    //vueEnsembleImg.sourceSize.height =container.height*flickArea.zoom
                    //vueEnsembleImg.sourceSize.width=container.width*flickArea.zoom
                    flickArea.returnToBounds();
                }
                
            Image{
                id:vueEnsembleImg
                source: "image.svg"
                anchors.centerIn:parent
                fillMode: Image.PreserveAspectFit
                scale:flickArea.zoom
                smooth:false
                antialiasing:false
                transform: Scale {
                    id: scaleID ;
                    origin.x: flickArea.contentX + flickArea.width * flickArea.visibleArea.widthRatio / 2
                    origin.y: flickArea.contentY + flickArea.height * flickArea.visibleArea.heightRatio / 2
                }
               
            }

        }
        Text{
            id:text1
            visible:false
            anchors.top:parent.top
            anchors.left:parent.left
            text:"Zoom : " + Math.round(flickArea.zoom*100) + "%"
        }

        Keys.onPressed: (event)=> {
            if(event.key === Qt.Key_Control){
                root.ctrlPressed = true
                text1.visible = true
            }

            if((event.key === Qt.Key_Plus) & (root.ctrlPressed)){
                if(flickArea.zoom < root.maxScale){//if(vueEnsembleImg.scale < root.maxScale){
                     flickArea.zoom += 0.05
                }

            }
            else if((event.key === Qt.Key_Minus) & (root.ctrlPressed)){
                if(flickArea.zoom > root.minScale){//if(vueEnsembleImg.scale > root.minScale){
                    flickArea.zoom -= 0.05
                }
            }
            else if ((event.key === Qt.Key_0) & (root.ctrlPressed)){
                flickArea.zoom = 0.95
            }

        }
        Keys.onReleased: (event)=>{
            if(event.key === Qt.Key_Control){
                text1.visible = false
                root.ctrlPressed = false
            }
            }
        }
        Rectangle{
            id:bunkerButton
            color:"transparent"
            width:110
            height:50
        }
    }
}

I also tried using a pdfPageImage but since it inherits from Image it behaves the same for this. I am using Qt 6.7.1. Unfortunately I really have to be able to do this as it is a request from my client. Is there a way to achieve this ? Perhaps with other object types ?

Share Improve this question asked Nov 20, 2024 at 17:38 Bernard TourneurBernard Tourneur 133 bronze badges 5
  • What does it mean when you say setting sourceSize "does not work at runtime"? – JarMan Commented Nov 20, 2024 at 18:54
  • You should be able to change sourceSize at runtime, but I recommend setting it to a constant high value (such as your maximum zoom value) or changing it only in specific multiples of the zoom value. – smr Commented Nov 20, 2024 at 21:26
  • I tested several options : if I bind sourceSize to container size * scale, then when I zoom in I get a pixelized image AND also the flicking "bounds" change to the current content (in other words, I cannot move around the image anymore). If I bind sourceSize to flickArea size * scale, the result is the same. The only way I got the zoomed image to stay sharp is by creating a binding loop, by binding sourceSize to flickArea size * scale, and flickArea size to the image size which of course is very bad and also introduced errors, and failed when zooming out. – Bernard Tourneur Commented Nov 22, 2024 at 13:01
  • Setting sourceSize at constant highest value is a good idea, but unfortunately it degrades slightly the quality of the image when it is completely zoomed out (stairs effect on straight lines). If there is a solution to keep perfect quality across any zoom value that would be ideal. – Bernard Tourneur Commented Nov 22, 2024 at 13:04
  • Changing the sourceSize in real-time is a bad practice, and it slows down your application. It depends on the image you're loading; by loading a detailed image, your application might freeze during zooming or scaling. Additionally, I wasn't able to run your code, but it would be helpful if you printed your sourceSize as well, so you can know what value it has while you are scaling your image. – smr Commented Nov 22, 2024 at 16:56
Add a comment  | 

1 Answer 1

Reset to default 0

To implement Image.sourceSize it's best to property bind to width and height, so, as the SVG scales, the sourceSize will recalculate automatically:

Image {
    sourceSize: Qt.size(width, height)
}

However, I prefer setting icon.source, icon.width, icon.height and icon.color for scaling and recoloring my SVG. Various components have the icon property, e.g. Button:

import QtQuick
import QtQuick.Controls
Page {
    Slider {
        id: slider
        from: 100
        to: 500
        value: 256
    }

    IconButton {
        id: iconButton
        y: 100
        width: slider.value
        height: slider.value
        icon.width: slider.value
        icon.height: slider.value
        icon.source: "globe.svg"
        icon.color: pressed ? "red" : "blue"
        onClicked: PropertyAnimation {
            target: iconButton
            property: "rotation"
            from: 0
            to: 360
        }
    }
    
}

// IconButton.qml
import QtQuick
import QtQuick.Controls
Item {
    id: iconButton
    width: 32
    height: 32
    property alias icon: button.icon
    property alias pressed: button.pressed
    signal clicked
    Button {
        id: button
        anchors.centerIn: parent
        background: Item { }
        icon.width: iconButton.width
        icon.height: iconButton.height
        onClicked: iconButton.clicked()
    }
}

// globe.svg : https://raw.githubusercontent/Esri/calcite-ui-icons/master/icons/globe-32.svg

You can Try it Online!

发布评论

评论列表(0)

  1. 暂无评论