diff options
| author | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io> | 2024-08-29 15:20:35 +0200 |
|---|---|---|
| committer | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io> | 2024-09-11 15:11:31 +0200 |
| commit | 252048b9561a7b27e84bf14c84659d6fec96a58b (patch) | |
| tree | f6d161c1bd3fe13acd83534ac7cbd2cac5d2083a | |
| parent | c9c7ce461a9251c76a668dac268343d0084bbfc3 (diff) | |
Example for VectorImage
This introduces a small example of VectorImage and other ways
to display SVG content in a Qt Quick application. Its purpose is
to document the available APIs for this and explain what their
differences are.
The heart.svg is a public domain, no-attribution image which is
also used in other examples and manual tests in Qt.
Pick-to: 6.8.0 6.8
Change-Id: I457ffc01d7ea286a157a4a5bff0571098b852cef
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
| -rw-r--r-- | examples/quick/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | examples/quick/vectorimage/CMakeLists.txt | 46 | ||||
| -rw-r--r-- | examples/quick/vectorimage/Heart.qml | 40 | ||||
| -rw-r--r-- | examples/quick/vectorimage/Main.qml | 123 | ||||
| -rw-r--r-- | examples/quick/vectorimage/doc/images/qml-vectorimage-example.png | bin | 0 -> 82385 bytes | |||
| -rw-r--r-- | examples/quick/vectorimage/doc/src/vectorimage.qdoc | 105 | ||||
| -rw-r--r-- | examples/quick/vectorimage/generate.bat | 1 | ||||
| -rw-r--r-- | examples/quick/vectorimage/heart.svg | 55 | ||||
| -rw-r--r-- | examples/quick/vectorimage/main.cpp | 16 |
9 files changed, 387 insertions, 0 deletions
diff --git a/examples/quick/CMakeLists.txt b/examples/quick/CMakeLists.txt index 4765d0b425..4b8d59eebb 100644 --- a/examples/quick/CMakeLists.txt +++ b/examples/quick/CMakeLists.txt @@ -37,6 +37,7 @@ if(TARGET Qt6::QuickWidgets AND TARGET Qt6::Widgets AND (QT_FEATURE_opengl OR QT endif() add_subdirectory(quickshapes) add_subdirectory(advancedtext) +qt_internal_add_example(vectorimage) # qt_examples_build_end() misses at least some of these due to some # source subdirectories being added multiple times. See QTBUG-96159. diff --git a/examples/quick/vectorimage/CMakeLists.txt b/examples/quick/vectorimage/CMakeLists.txt new file mode 100644 index 0000000000..67345df1d9 --- /dev/null +++ b/examples/quick/vectorimage/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(vectorimage LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick) + +qt_standard_project_setup(REQUIRES 6.8) + +qt_add_executable(vectorimageexample + WIN32 + MACOSX_BUNDLE + main.cpp +) + +target_link_libraries(vectorimageexample PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Qml + Qt6::Quick +) + +qt_add_qml_module(vectorimageexample + URI vectorimage + QML_FILES + "Main.qml" + "Heart.qml" + RESOURCES + "heart.svg" +) + +install(TARGETS vectorimageexample + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_qml_app_script( + TARGET vectorimageexample + OUTPUT_SCRIPT deploy_script + MACOS_BUNDLE_POST_BUILD + NO_UNSUPPORTED_PLATFORM_ERROR + DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM +) +install(SCRIPT ${deploy_script}) diff --git a/examples/quick/vectorimage/Heart.qml b/examples/quick/vectorimage/Heart.qml new file mode 100644 index 0000000000..ecd91d1e44 --- /dev/null +++ b/examples/quick/vectorimage/Heart.qml @@ -0,0 +1,40 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// Generated from SVG file heart.svg +import QtQuick +import QtQuick.Shapes + +Item { + implicitWidth: 595 + implicitHeight: 841 + transform: [ + Translate { x: -100; y: -200 }, + Scale { xScale: width / 550; yScale: height / 550 } + ] + objectName: "svg1" + Shape { + preferredRendererType: Shape.CurveRenderer + objectName: "layer1" + transform: Translate { x: 0; y: 30 } + ShapePath { + objectName: "svg_path:path7" + strokeColor: "#ff000000" + strokeWidth: 18.7 + capStyle: ShapePath.FlatCap + joinStyle: ShapePath.MiterJoin + miterLimit: 4 + fillColor: "#ffe60000" + fillRule: ShapePath.WindingFill + pathHints: ShapePath.PathQuadratic | ShapePath.PathNonIntersecting | ShapePath.PathNonOverlappingControlPointTriangles + PathSvg { path: "M 263.416 235.146 Q 213.736 235.146 178.576 270.306 Q 143.416 305.466 143.416 355.146 Q 143.416 410.953 176.932 457.224 Q 197.525 485.653 256.465 537.292 Q 336.471 607.388 371.978 658.454 Q 406.412 606.394 488.235 533.861 Q 546.507 482.205 567.035 454.113 Q 600.541 408.26 600.541 355.146 Q 600.541 305.466 565.381 270.306 Q 530.221 235.146 480.541 235.146 Q 445.065 235.146 415.427 254.433 Q 386.595 273.195 371.978 304.333 Q 357.361 273.195 328.53 254.433 Q 298.891 235.146 263.416 235.146 " } + } + ShapePath { + objectName: "svg_path:path220" + strokeColor: "transparent" + fillColor: "#a5e6e6e6" + fillRule: ShapePath.WindingFill + pathHints: ShapePath.PathQuadratic | ShapePath.PathNonIntersecting | ShapePath.PathNonOverlappingControlPointTriangles + PathSvg { path: "M 265 253.594 Q 221.53 253.594 190.765 284.359 Q 160 315.124 160 358.594 Q 160 407.427 189.325 447.919 Q 207.343 472.798 258.912 517.988 Q 293.908 548.655 319.172 575.158 Q 331.804 588.409 342.003 600.62 Q 352.202 612.83 359.969 624 Q 360.944 622.526 354.226 613.042 Q 347.508 603.558 333.097 586.064 Q 276.548 517.421 248.274 465.553 Q 220 413.686 220 378.594 Q 220 335.124 250.765 304.359 Q 281.53 273.594 325 273.594 Q 325.27 273.594 325.802 273.625 Q 326.266 273.653 326.5 273.656 Q 298.956 253.594 265 253.594 " } + } + } +} diff --git a/examples/quick/vectorimage/Main.qml b/examples/quick/vectorimage/Main.qml new file mode 100644 index 0000000000..44faf9d370 --- /dev/null +++ b/examples/quick/vectorimage/Main.qml @@ -0,0 +1,123 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.VectorImage + +ApplicationWindow { + id: topLevel + width: 800 + height: 800 + visible: true + title: qsTr("Vector Image Example") + color: "lightpink" + + property real sourceSize: Math.min(topLevel.width / 12, topLevel.height / 12) + + Component { + id: imageComponent +//! [image] + Image { + sourceSize: Qt.size(topLevel.sourceSize, topLevel.sourceSize) + source: "heart.svg" + } +//! [image] + } + + Component { + id: vectorImageComponent +//! [vectorimage] + VectorImage { + width: topLevel.sourceSize + height: topLevel.sourceSize + preferredRendererType: VectorImage.CurveRenderer + source: "heart.svg" + } +//! [vectorimage] + } + + Component { + id: svgtoqmlComponent +//! [svgtoqml] + Heart { + width: topLevel.sourceSize + height: topLevel.sourceSize + } +//! [svgtoqml] + } + + GridLayout { + id: grid + anchors.fill: parent + columns: 3 + uniformCellWidths: true + rowSpacing: 0 + + Label { + id: label + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + text: "Image" + color: "black" + font.pixelSize: 20 + font.bold: true + } + + Label { + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + text: "VectorImage" + color: "black" + font.pixelSize: 20 + font.bold: true + } + + Label { + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + text: "svgtoqml" + color: "black" + font.pixelSize: 20 + font.bold: true + } + + Repeater { + id: repeater + property int count: grid.columns + model: grid.columns * count + Item { + property int margin: 10 + Layout.preferredHeight: ((grid.height - label.height - margin) / repeater.count) + Layout.preferredWidth: ((grid.height - label.height - margin) / repeater.count) + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + clip: true + + Rectangle { + anchors.fill: parent + border.width: 1 + color: "lightcoral" + radius: 10 + } + + Item { + property int row: index / grid.columns + transformOrigin: Item.Center + anchors.fill: parent + Loader { + id: loader + property int column: index % grid.columns + + anchors.centerIn: parent + sourceComponent: { + switch (column) { + case 0: return imageComponent + case 1: return vectorImageComponent + case 2: return svgtoqmlComponent + } + } + } + scale: 1 + row * 1.5 + } + } + } + } +} diff --git a/examples/quick/vectorimage/doc/images/qml-vectorimage-example.png b/examples/quick/vectorimage/doc/images/qml-vectorimage-example.png Binary files differnew file mode 100644 index 0000000000..b4b2fafee1 --- /dev/null +++ b/examples/quick/vectorimage/doc/images/qml-vectorimage-example.png diff --git a/examples/quick/vectorimage/doc/src/vectorimage.qdoc b/examples/quick/vectorimage/doc/src/vectorimage.qdoc new file mode 100644 index 0000000000..791160d58e --- /dev/null +++ b/examples/quick/vectorimage/doc/src/vectorimage.qdoc @@ -0,0 +1,105 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! + \title Vector Image Example + \example vectorimage + \image qml-vectorimage-example.png + \brief A Qt Quick example demonstrating the differences of \l{Qt SVG}, VectorImage and \l{svgtoqml}. + \ingroup qtquickexamples + \examplecategory {Graphics} + + There are several different ways you can include two-dimensional vector graphics in your + Qt application. This example focuses on the \c{SVG} format and how this can be used with Qt. + As a baseline, Qt supports the static features of the \c{SVG Tiny 1.2} profile. In addition, + \l{Qt SVG} optionally supports some features from the full profile, but those are not used in + this example. + + The example shows a grid of 3x3 cells, each containing the same vector image of a heart. The + heart image is provided as an \c{SVG} file with the example. + + In the grid, each row shows the image at a different scale factor (1x, 2.5x and 4x + respectively). + + Each column represents a different way to render the vector graphics in Qt. The left-most column + is an \l{Image} component, the center column is a \l{VectorImage} component and the right-most + column is a pregenerated \c{QML} representation created using the \l{svgtoqml} tool. + + Each method of rendering the vector graphics has its own benefits and drawbacks, and caters to + different use cases. To make an informed decision about which one to use, it can be useful to + understand the details of how they differ. + + \section1 The \l{Image} component and \l{Qt SVG} + + When you use the \l{Image} element and set an \c{SVG} file as source, this will invoke the + image format plugin in \l{Qt SVG}. The plugin will parse the \c{SVG} file, rasterize it using + the software rasterizer in \l{QPainter} and then provide it to Qt Quick as a pixmap image. This + is equivalent to using the \l{QSvgRenderer} class to draw the image. + + \snippet vectorimage/Main.qml image + + Since the vector image is rasterized at a specific size, any transformation we apply to the + \l{Image} will be applied to the \e{rasterized image}. This can result in pixelation artifacts + and uneven curves. + + Loading a vector image through \l{Image} works best if you request the image at the exact size + that you intend to display. When the \l{Image} is displayed at a 1x scale in the top-most row, + it looks identical to the others, but at higher scale factors it starts to look fuzzy. + + If the image will ever only be displayed with a single size, then this will typically be the + most performant option. There is a start-up cost for rasterizing the image at the specified + size, but after this, the cost of copying the data onto the screen is very low. + + But as the image is requested at multiple different sizes, the start-up cost will grow, as will + the accumulated memory consumption. Animated zooms of the image can often get too expensive to + run at full frame rate on lower end devices. These are the use cases for which \l{VectorImage} + and \l{svgtoqml} should be considered. + + \section1 The \l{VectorImage} component + + As an alternative to \l{Image}, Qt provides the \l{VectorImage} component. This converts the + \c{SVG} image to a vector graphics representation in Qt Quick, and rasterization happens on + demand, on the graphics HW, as it is rendered to the screen. + + \snippet vectorimage/Main.qml vectorimage + + Since the image is not pre-rasterized, we can apply transformations to it without losing + fidelity to the original shapes. Note that the example uses the \c{VectorImage.CurveRenderer} + renderer type. This is recommended for use cases where the image will be transformed and + antialiasing is needed. + + This means that we can display the image at any size and even animate the scale of the image, + and all the work will be done by the graphics hardware. However, when the \l{VectorImage} is + rendered onto the screen, it will come at a slightly higher cost than rendering an \l{Image}. + This is because the rasterization of curves happens every time the component is rendered and + not ahead of time. + + Therefore, \l{VectorImage} is most suitable for vector graphics where the size will change + frequently. It can also be suitable when the destination size of the image is very large and + memory consumption is a concern. When using \l{Image}, the full rasterized image has to be + stored in graphics memory. Thus, the memory consumption will scale with the size of the image. + The memory consumed by \l{VectorImage} will be the same, regardless of the destination size it + is rendered at. + + \section1 The \l{svgtoqml} tool + + The \l{VectorImage} component parses the \c{SVG} file and builds up an equivalent scene of + Qt Quick items at run-time. + + If the \c{SVG} is part of the application assets, then some of the work can be done ahead of + time instead, by using the \l{svgtoqml} tool. This tool produces the same scene as + \l{VectorImage}, but instead of building it at run-time, it creates a \c{QML} file which can + be included in the application project. + + In this example, the \c{heart.svg} file has been pre-converted to a file called \c{Heart.qml}. + This can be instantiated in the scene as any other Qt Quick item. + + \snippet vectorimage/Main.qml svgtoqml + + By using this approach, we do not need to parse the \c{SVG} file every time the application + is started. In addition, the \c{svgtoqml} tool can optimize and analyze the shapes, in order to + provide the renderer with hints that will further speed up its run-time processing. + + The \l{svgtoqml} tool should be considered for the same use cases as \l{VectorImage} and should + be preferred whenever the \c{SVG} file is available as an asset when the application is built + and is not provided by the end-user of the application. +*/ diff --git a/examples/quick/vectorimage/generate.bat b/examples/quick/vectorimage/generate.bat new file mode 100644 index 0000000000..20fe425e5c --- /dev/null +++ b/examples/quick/vectorimage/generate.bat @@ -0,0 +1 @@ +@svgtoqml -p --copyright-statement "Copyright (C) 2024 The Qt Company Ltd.\nSPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause" -c heart.svg Heart.qml diff --git a/examples/quick/vectorimage/heart.svg b/examples/quick/vectorimage/heart.svg new file mode 100644 index 0000000000..1d31434cb8 --- /dev/null +++ b/examples/quick/vectorimage/heart.svg @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --><svg viewBox="100 200 550 550" height="841.88976pt" id="svg1" inkscape:version="0.40+cvs" sodipodi:docbase="C:\Documents and Settings\Jon Phillips\My Documents\projects\clipart-project\submissions" sodipodi:docname="heart-left-highlight.svg" sodipodi:version="0.32" width="595.27559pt" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"> +<metadata> +<rdf:RDF xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<cc:Work rdf:about=""> +<dc:title>Heart Left-Highlight</dc:title> +<dc:description>This is a normal valentines day heart.</dc:description> +<dc:subject> +<rdf:Bag> +<rdf:li>holiday</rdf:li> +<rdf:li>valentines</rdf:li> +<rdf:li></rdf:li> +<rdf:li>valentine</rdf:li> +<rdf:li>hash(0x8a091c0)</rdf:li> +<rdf:li>hash(0x8a0916c)</rdf:li> +<rdf:li>signs_and_symbols</rdf:li> +<rdf:li>hash(0x8a091f0)</rdf:li> +<rdf:li>day</rdf:li> +</rdf:Bag> +</dc:subject> +<dc:publisher> +<cc:Agent rdf:about="http://www.openclipart.org"> +<dc:title>Jon Phillips</dc:title> +</cc:Agent> +</dc:publisher> +<dc:creator> +<cc:Agent> +<dc:title>Jon Phillips</dc:title> +</cc:Agent> +</dc:creator> +<dc:rights> +<cc:Agent> +<dc:title>Jon Phillips</dc:title> +</cc:Agent> +</dc:rights> +<dc:date></dc:date> +<dc:format>image/svg+xml</dc:format> +<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> +<cc:license rdf:resource="http://web.resource.org/cc/PublicDomain"/> +<dc:language>en</dc:language> +</cc:Work> +<cc:License rdf:about="http://web.resource.org/cc/PublicDomain"> +<cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/> +<cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/> +<cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/> +</cc:License> +</rdf:RDF> +</metadata> +<defs id="defs3"/> +<sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="549.40674" inkscape:cy="596.00159" inkscape:document-units="px" inkscape:guide-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="615" inkscape:window-width="866" inkscape:window-x="88" inkscape:window-y="116" inkscape:zoom="0.35000000" pagecolor="#ffffff" showguides="true"/> +<g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0 30)"> +<path d="M 263.41570,235.14588 C 197.17570,235.14588 143.41575,288.90587 143.41575,355.14588 C 143.41575,489.90139 279.34890,525.23318 371.97820,658.45392 C 459.55244,526.05056 600.54070,485.59932 600.54070,355.14588 C 600.54070,288.90588 546.78080,235.14587 480.54070,235.14588 C 432.49280,235.14588 391.13910,263.51631 371.97820,304.33338 C 352.81740,263.51630 311.46370,235.14587 263.41570,235.14588 z " id="path7" sodipodi:nodetypes="ccccccc" style="fill:#e60000;fill-opacity:1.0000000;stroke:#000000;stroke-width:18.700001;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"/> +<path d="M 265.00000,253.59375 C 207.04033,253.59375 160.00000,300.63407 160.00000,358.59375 C 160.00000,476.50415 278.91857,507.43251 359.96875,624.00000 C 366.52868,614.08205 220.00000,478.47309 220.00000,378.59375 C 220.00000,320.63407 267.04033,273.59375 325.00000,273.59375 C 325.50453,273.59375 325.99718,273.64912 326.50000,273.65625 C 309.22436,261.07286 288.00557,253.59374 265.00000,253.59375 z " id="path220" sodipodi:nodetypes="ccccccc" style="fill:#e6e6e6;fill-opacity:0.64556962;stroke:none;stroke-width:18.700001;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"/> +</g> +</svg> diff --git a/examples/quick/vectorimage/main.cpp b/examples/quick/vectorimage/main.cpp new file mode 100644 index 0000000000..dc77b0aabd --- /dev/null +++ b/examples/quick/vectorimage/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + app.setOrganizationName("QtProject"); + + QQmlApplicationEngine engine; + engine.loadFromModule("vectorimage", "Main"); + + return app.exec(); +} |
