aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2024-08-29 15:20:35 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2024-09-12 04:32:20 +0000
commit6d5519dc93d2c0acf77236697bfe15de379fbce1 (patch)
treef6461f6e51262ad47797559472666633afe1cc33
parentcba9df6fe701316ad5052fc491d042db2985b5df (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. Change-Id: I457ffc01d7ea286a157a4a5bff0571098b852cef Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io> (cherry picked from commit 252048b9561a7b27e84bf14c84659d6fec96a58b) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> (cherry picked from commit 3df94fbce1dd451b2f75846ad55ffdef5e5734a8)
-rw-r--r--examples/quick/CMakeLists.txt1
-rw-r--r--examples/quick/vectorimage/CMakeLists.txt46
-rw-r--r--examples/quick/vectorimage/Heart.qml40
-rw-r--r--examples/quick/vectorimage/Main.qml123
-rw-r--r--examples/quick/vectorimage/doc/images/qml-vectorimage-example.pngbin0 -> 82385 bytes
-rw-r--r--examples/quick/vectorimage/doc/src/vectorimage.qdoc105
-rw-r--r--examples/quick/vectorimage/generate.bat1
-rw-r--r--examples/quick/vectorimage/heart.svg55
-rw-r--r--examples/quick/vectorimage/main.cpp16
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
new file mode 100644
index 0000000000..b4b2fafee1
--- /dev/null
+++ b/examples/quick/vectorimage/doc/images/qml-vectorimage-example.png
Binary files differ
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();
+}