C

Qt Quick Ultralite sprite_animations Example

Demonstrates how to create a sprite animation.

Overview

The sprite_animations example shows how to create a sprite animation from frame sequences in an image file.

The image source for this example consist of 16 frames, where each frame size is 180x160.

Target platforms

Image formats

Qt Quick Ultralite lets you use a hardware image decoder instead of the default. Both the AnimatedSprite and AnimatedSpriteDirectory QML types support the registered image format for the sprite animation.

The example shows the use case of the image decoder on the STM32F769i, STM32H750b, RH850/D1M1A Evaluation Board and Infineon TRAVEO T2G CYT4DN boards. For these boards, the project configures the JPEG decoder and uses the raw image data of JPEG files in the ImageFiles node. The image decoder enables to decode and render the image data at runtime. See Qt Quick Ultralite imagedecoder Example and Using JPEG decoder for more details.

Note: Due to a limitation of the Graphics Driver, currently the only supported subsampling mode for source JPEG images is YUV420. See Sample application user guide for JPEG decode driver TRAVEO T2G cluster series User Manual for more information. To convert source images to the YUV420 subsampling format, you can use ImageMagick Convert tool: convert input.jpg -sampling-factor 4:2:0 output.jpg.

Note: For Infineon TRAVEO T2G CYT4DN, set the mandatory CMake variable TVII_JPEG_DRIVER_DIR. It should point to the root folder of the JPEG decode driver SDK.

On the RH850 platform, add the following compile definitions to configure the JPEG driver:

  • USE_OUTPUT_SPLIT_MODE

    Configures the Renesas JCUA hardware in division mode where the decoding is performed in chunks. This definition can be skipped if normal mode is to be used where complete image is decoded at once.

  • DECODE_BUFFER_PIXEL_LINES

    If USE_OUTPUT_SPLIT_MODE is enabled, DECODE_BUFFER_PIXEL_LINES sets the number of output buffer pixel lines which will be processed in one iteration. This value must be multiple of 16.

    The default value of DECODE_BUFFER_PIXEL_LINES is 16.

  • CHROMA_SUBSAMPLING

    Sets the chroma-subsampling format of the images in use. The possible values are as follows:

    1. R_JCUA_JPEG_FORMAT_YCBCR420
    2. R_JCUA_JPEG_FORMAT_YCBCR411
    3. R_JCUA_JPEG_FORMAT_YCBCR422
    4. R_JCUA_JPEG_FORMAT_YCBCR444

    The default value of CHROMA_SUBSAMPLING is R_JCUA_JPEG_FORMAT_YCBCR420.

    Note: Currently, the chroma-subsampling cannot be determined at runtime and has to be specified at compile time. All jpeg images used in the application must be saved with same chroma-subsampling.

For the other boards and platforms, the project uses PNG image file and the resource compiler to decode the image at build time.

Project structure

CMake project file

The following CMake example shows how to decode images on platforms that does not use the JPEG decoder:

qul_add_target(sprite_animations
    QML_PROJECT mcu_sprite_animations.qmlproject
    GENERATE_ENTRYPOINT
)

The following QmlProject example show how you can configure a JPEG decoder with the necessary image resource properties and platform configuration:

qul_add_target(sprite_animations
    QML_PROJECT mcu_sprite_animations_jpeg.qmlproject
    os/${QUL_OS_LOWERCASE}/main.cpp
)
QmlProject file

The Qmlproject file includes the required Qml and Image files.

import QmlProject 1.3

Project {
    mainFile: "sprite_animations.qml"
    QmlFiles {
        files: [
            "ToggleButton.qml"
        ]
    }
    ImageFiles {
        files: [
            "qt-image-sequence.png"
        ]
        MCU.resourceAnimatedSpriteFrameWidth: 180
        MCU.resourceAnimatedSpriteFrameHeight: 160
    }
}

QmlProject offers ImageFiles.MCU.resourceAnimatedSpriteFrameWidth, ImageFiles.MCU.resourceAnimatedSpriteFrameHeight, and ImageFiles.MCU.resourceAnimatedSpriteOptimizations to let you optimize the image sources.

The resource compiler splits the image source into 16 frames internally, and applies the cropping technique to optimize them for better performance and lower memory footprint. This enables rendering only the changed parts between a series of the frames.

Application UI

The sprite_animations.qml file defines AnimatedSprite type. It defines the size of a single frame, number of frames in an image, and source of the image. The animation starts by default. When a user clicks the image, it runs or stops the animations based on the current running value.

    AnimatedSprite {
        id: sprite
        anchors.centerIn: parent
        source: "qt-image-sequence.png"

        frameDuration: 80
        frameCount: 16
        frameWidth: 180
        frameHeight: 160

        loops: AnimatedSprite.Infinite

        onFinished: {
            txtMsg.text = "Finished"
        }

        onRunningChanged: {
            if (sprite.running) {
                txtMsg.text = ""
            }
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                if (sprite.running) {
                    sprite.stop()
                } else {
                    sprite.start()
                }
            }
        }
    }

On the top-left corner of the UI, the number and the duration of the current frame (in milliseconds) of each frame of the animation.

    Column {
        id: spriteInfo
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 16
        spacing: 4

        Text {
            id: txtFrameNo
            text: "frame: " + (sprite.currentFrame + 1) + " / " + sprite.frameCount
            font.pixelSize: 14
            color: "white"
        }
        Text {
            id: txtDuration
            text: "duration: " + sprite.frameDuration
            font.pixelSize: 14
            color: "white"
        }
        Text {
            id: txtMsg
            font.pixelSize: 14
            text: ""
            color: "white"
        }
    }

The application has a button on the top-right corner to toggle the loops property value between AnimatedSprite.Infinite and 1.

    ToggleButton {
        id: toggleLoops
        width: parent.width / 4
        height: parent.height / 9
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.margins: 8
        checkedText: "Infinite"
        uncheckedText: "Once"
        onCheckedChanged: {
            if (toggleLoops.checked) {
                sprite.loops = AnimatedSprite.Infinite
            } else {
                sprite.loops = 1
            }
        }
    }

The ToggleButton is a custom component using the simple visual QML types such as Rectangle, MouseArea, Text, and Row.

import QtQuick 2.15

Rectangle {
    id: control
    color: "white"

    property bool checked: true
    readonly property color foregroundColor: "black"
    readonly property int borderWidth: 1
    readonly property alias checkedText: txtChecked.text
    readonly property alias uncheckedText: txtUnchecked.text

    Row {
        x: control.borderWidth
        y: control.borderWidth
        spacing: control.borderWidth

        Rectangle {
            id: leftPart
            width: (control.width - control.borderWidth * 3) / 2
            height: (control.height - control.borderWidth * 2)
            color: control.checked? control.color : control.foregroundColor

            Text {
                id: txtChecked
                anchors.centerIn: parent
                color: control.checked? control.foregroundColor : control.color
                font.pixelSize: 14
            }
        }
        Rectangle {
            id: rightPart
            width: leftPart.width
            height: leftPart.height
            color: control.checked? control.foregroundColor : control.color

            Text {
                id: txtUnchecked
                anchors.centerIn: parent
                color: control.checked? control.color : control.foregroundColor
                font.pixelSize: 14
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: control.checked = !control.checked
    }
}
Using JPEG decoder

On platforms that use a JPEG decoder, you can use the MCU.resourceKeepRawData QmlProject property to bundle the JPEG files as the raw image data. You can also define MCU.Config.maxResourceCacheSize to make sure there is enough space to store the decoded image data at runtime.

import QmlProject 1.3

Project {
    MCU.Config {
        // enough size to contain one decoded image
        maxResourceCacheSize: 153600
    }
    QmlFiles {
        files: [
            "ToggleButton.qml",
            "sprite_animations_jpeg.qml"
        ]
    }
    ImageFiles {
        files: [
            "qt-image-sequence/00.jpg",
            "qt-image-sequence/01.jpg",
            "qt-image-sequence/02.jpg",
            "qt-image-sequence/03.jpg",
            "qt-image-sequence/04.jpg",
            "qt-image-sequence/05.jpg",
            "qt-image-sequence/06.jpg",
            "qt-image-sequence/07.jpg",
            "qt-image-sequence/08.jpg",
            "qt-image-sequence/09.jpg",
            "qt-image-sequence/10.jpg",
            "qt-image-sequence/11.jpg",
            "qt-image-sequence/12.jpg",
            "qt-image-sequence/13.jpg",
            "qt-image-sequence/14.jpg",
            "qt-image-sequence/15.jpg"
        ]
        MCU.resourceAnimatedSprite: true
        MCU.resourceKeepRawData: true
    }
}

The example uses a series of frame images with AnimatedSpriteDirectory to emphasize the benefit of the hardware decoding. This reduces memory usage and CPU load for the sprite animation.

    AnimatedSpriteDirectory {
        id: sprite
        anchors.centerIn: parent
        sourcePath: "qt-image-sequence"

        frameDuration: 80
        loops: AnimatedSprite.Infinite

        readonly property int frameCount: 16
        ...

Files:

Images:

See also Managing Resources.

Available under certain Qt licenses.
Find out more.