C
Qt Quick Ultralite perspective_transforms Example
Shows how to implement perspective (2.5D) transformations in a Qt Quick Ultralite application.
Overview
The perspective_transforms
example implements perspective transformations and quasi-3D effects using Matrix4x4
QML transform type. It demonstrates this using a music album cover flow component.
The example's UI has a group of radio buttons on the top, enabling to switch between the following cover flow styles:
- Carousel
- Circle
- Circle 2D (using orthogonal transformations)
- Perspective
The cover flow component is rendered in the center. It animates the album cover movement according to the selected style.
At the very bottom, a group of slider controls let the user adjust the camera parameters, such as elevation or tilt angle.
Additionally, the example has a "demo" mode, which is enabled if there is user interaction for more than 5 seconds. This mode animates switching between the available covers and view types. The animations stops when the user starts interacting with the application.
Target platforms
- MCIMX93-EVK
- MIMXRT1050
- MIMXRT1060
- MIMXRT1064
- MIMXRT1170
- STM32F469I
- STM32F769i
- STM32H750b
- RH850 D1M1A
Project structure
Application project structure has no special features related to perspective transformations, however it's a bit more complex than minimal. It contains the following subdirectories:
- controls - contains implementation for custom UI controls used in the example, such as CheckBox, Slider or RadioButton
- imports - content of a QML modules used in the project - mostly for project wide constants (using singletons)
- resources - graphical assets - mostly album covers
CMake project file
The CMakeLists.txt for this example defines perspective_transforms
as the main executable target.
... qul_add_target(perspective_transforms QML_PROJECT mcu_perspective_transforms.qmlproject SELECTORS no_controls GENERATE_ENTRYPOINT ) elseif(NOT QUL_PLATFORM MATCHES "^stm32f769i" AND NOT QUL_PLATFORM MATCHES "^stm32f469i" AND NOT QUL_PLATFORM MATCHES "^mimxrt1170-evkb") qul_add_target(perspective_transforms QML_PROJECT mcu_perspective_transforms.qmlproject SELECTORS small_controls GENERATE_ENTRYPOINT ) ...
QmlProject file
All relevant qml files are specified in the QmlProject file.
... QmlFiles { files: [ "CoverFlow.qml", "CoverFlowState.qml", "Cover.qml", "IdleTimer.qml", "perspective_transforms.qml" ] } QmlFiles { files: [ "controls/CheckBox.qml", "controls/RadioButton.qml", "controls/Slider.qml" ] } ...
All the image assets are added with ImageFiles.MCU.resourceOptimizeForRotation property enabled. This will improve transformations performance on supported platforms.
ImageFiles { files: [ "resources/cover0.jpg", "resources/cover1.jpg", "resources/cover2.jpg", "resources/cover3.jpg", "resources/cover4.jpg", "resources/cover5.jpg", "resources/cover6.jpg", "resources/cover7.jpg", "resources/cover8.jpg", "resources/cover9.jpg", ] MCU.base: "resources" // Optimize all assets for transformations MCU.resourceOptimizeForRotation: true
In addition, a module holding project-wide constants is defined using constants.qmlproject and added to the main project file.
ModuleFiles { files: [ "imports/constants/constants.qmlproject", "configuration/configuration.qmlproject" ] MCU.qulModules: ["Controls"] }
Application UI
The perspective_transforms.qml file defines the application's user interface.
It lays out the main UI components, such as RadioButtons and Sliders. These are the custom controls that are defined in the controls
subdirectory.
import "controls"
IdleTimer
takes care of animating UI when user inactivity is detected. This is intended for platforms without a touch screen. It exposes two signals, which are used to independently switch between the cover flow types and current album selection.
IdleTimer { id: idleTimer property int coverDir: 1 onSwitchCover: { ... } onSwitchFlowType: { ... } }
Most relevant part is the instantiation of Cover Flow components. This starts with creating an object that holds current state of the CoverFlow
component.
CoverFlowState { id: currentState screenWidth: root.width screenHeight: root.height } ...
Then, the CoverFlow
component takes care of actual rendering.
CoverFlow { anchors.fill: parent currentState: currentState }
CoverFlowState
The CoverFlowState.qml file holds all the parameters that affects how CoverFlow
is rendered. It configures the following parameters:
- Screen/Canvas size
CoverFlow
size and position- Number of covers to render and currently selected cover index
- Camera settings like FOV or clipping distance, elevation, tilt, etc.
- Settings of particular cover flow types
- Information about current view type and parameters related to type transition
A morph ratio is a property that is used for animated transitions between different cover flow types.
property real morphRatio: 1 property int currentViewType: CoverFlowType.Carousel property int previousViewType: CoverFlowType.Carousel NumberAnimation on morphRatio { id: morphAnimation from: 0.0 to: 1.0 duration: 200 }
Switching between cover flow types is done using switchViewType
.
function switchViewType(newType : int){ previousViewType = currentViewType currentViewType = newType morphAnimation.start() }
CoverFlow
The CoverFlow.qml file implements the cover flow component. It is responsible for creating multiple instances of Cover
components, where actual rendering logic is implemented.
This component has a single property holding its current state.
Item { id: root property CoverFlowState currentState ...
A Repeater takes care of dynamically creating a number of Covers
based on the value defined in the cover flow state.
Repeater { model: root.currentState.numberOfCovers delegate: Cover { required property int index texture: "cover" + index + ".jpg" coverIndex: index state: root.currentState postMatrix: root.globalPostMatrix preMatrix: root.globalPreMatrix reflectionTransform: root.reflectionTransform } }
Cover
The Cover.qml implements all matrix calculations needed to render a cover (and its reflections) using right transformations. The Cover
contorl is a Rectangle with Image
items (with proper transforms applied) as its children. One image represents the actual cover image, whereas the second one is used to render a reflection of the first. Both Image items are using the Matrix4x4 transformation, where the matrix
property is bound to appropriate functions returning matrix4x4 QML basic type (please distinct Matrix4x4 transform object from matrix4x4 basic type).
Rectangle { id: cover property string texture property int coverIndex property CoverFlowState state property matrix4x4 coverTransform: calcCoverTransform() property matrix4x4 postMatrix property matrix4x4 preMatrix property matrix4x4 reflectionTransform z: calcFinalZ() Image { id: coverImageBase source: cover.texture transform: Matrix4x4 { matrix: coverTransform } opacity: 1.0 } Image { id: mirrorImageBase visible: cover.state.showReflection source: cover.texture transform: [ Matrix4x4 { matrix: reflectionTransform }, Matrix4x4 { matrix: coverTransform } ] opacity: 0.1 } ...
A new matrix4x4 can be created using the Qt::matrix4x4() factory method, by giving all the matrix components. Let's take a translation matrix for example:
function mtxTranslate(x : real, y : real, z : real) : matrix4x4 { return Qt.matrix4x4(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1) }
The follwoing arithmetic operations are available for the matrix4x4 type:
- matrix by matrix multiplication matrix4x4::times
- matrix by scalar multiplication matrix4x4::times
- matrix addition matrix4x4::plus
All of them are used in the calcCoverTransform()
function:
function calcCoverTransform() : matrix4x4 { var previousViewType = state.previousViewType var currentViewType = state.currentViewType if (state.morphRatio == 0) { currentViewType = previousViewType } else if (state.morphRatio == 1) {
A matrix4x4
type has sixteen values, each accessible via the properties m11
through m44
(in row/column order). This is used when calculating the z
position of the cover as a whole.
function calcFinalZ() : real { var coverTransform = cover.coverTransform var x = state.coverSize/2 var y = state.coverSize/2 var inv_d = 1.0 / (coverTransform.m41 * x + coverTransform.m42 * y + coverTransform.m44) var fX = (coverTransform.m11 * x + coverTransform.m12 * y + coverTransform.m14) * inv_d var fZ = (coverTransform.m31 * x + coverTransform.m32 * y + coverTransform.m34) * inv_d var littleX = Math.abs(fX - state.coverFlowX - state.coverFlowW * 0.5) return -fZ * 100000 - littleX }
The Cover.qml implements multiple functions operating on the matrices, which can be used as a reference. These functions can be grouped into the following categories:
- Elementary matrix generations functions (translation, scale, rotation, perspective)
- Calculating pre and post transformation matrices (camera matrix and initial cover transformation)
- Calculating final transforms for individual cover flow types
All these basic building blocks are sufficient to implement a complex 2.5D effects.
Files:
- perspective_transforms/CMakeLists.txt
- perspective_transforms/Cover.qml
- perspective_transforms/CoverFlow.qml
- perspective_transforms/CoverFlowState.qml
- perspective_transforms/IdleTimer.qml
- perspective_transforms/configuration/+no_controls/Configuration.qml
- perspective_transforms/configuration/Configuration.qml
- perspective_transforms/configuration/configuration.qmlproject
- perspective_transforms/controls/CheckBox.qml
- perspective_transforms/controls/RadioButton.qml
- perspective_transforms/controls/Slider.qml
- perspective_transforms/imports/constants/+small_controls/Constants.qml
- perspective_transforms/imports/constants/Constants.qml
- perspective_transforms/imports/constants/CoverFlowType.qml
- perspective_transforms/imports/constants/constants.qmlproject
- perspective_transforms/imports/constants/qmldir
- perspective_transforms/mcu_perspective_transforms.qmlproject
- perspective_transforms/perspective_transforms.qml
Images:
- perspective_transforms/resources/cover0.jpg
- perspective_transforms/resources/cover1.jpg
- perspective_transforms/resources/cover2.jpg
- perspective_transforms/resources/cover3.jpg
- perspective_transforms/resources/cover4.jpg
- perspective_transforms/resources/cover5.jpg
- perspective_transforms/resources/cover6.jpg
- perspective_transforms/resources/cover7.jpg
- perspective_transforms/resources/cover8.jpg
- perspective_transforms/resources/cover9.jpg
See also Matrix4x4, matrix4x4, and Qt::matrix4x4().
Available under certain Qt licenses.
Find out more.