svgqmlvector-graphicsqt6

Dynamically zoomable vector image in QML


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 ?


Solution

  • 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.com/Esri/calcite-ui-icons/master/icons/globe-32.svg
    

    You can Try it Online!