跳轉到主要內容

Delta Compressed Animation

The DeltaCompressedAnimation widget displays frames from a delta-compressed animation resource. The animation resource is generated by the image converter from a sequence of PNG images.
The DeltaCompressedAnimation widget is designed to reduce the storage space required for multi-frame animations by encoding the parts that change between frames.

Delta-compressed butterfly animation

The table below compares the DeltaCompressedAnimation widget with the AnimatedImage widget:

AspectDeltaCompressedAnimationAnimatedImage
CompressionLossless.Lossless.
StorageStores changed image parts between frames instead of a complete bitmap for every frame, so the generated animation is often smaller than the source images.Stores each input image as an individual bitmap, with a complete image for every frame.
EncodingChooses the best supported encoding for each part. Different parts can use different encodings.Uses the source image bitmaps.
Source imagesConverts the PNGs to animation data; it does not include them as application bitmaps. This saves space, but the images cannot also be reused individually as bitmaps.Keeps the frames as normal image bitmaps, so they can also be displayed individually elsewhere in the application.
Input namesFrame paths are listed explicitly in application.config and played in that order; filenames need not be sequential.Input image names must be sequential, for example image_01, image_02, image_03. The widget is given the first and last bitmap.
Redraw areaKnows which rectangular parts changed and invalidates and redraws those parts when advancing a frame.Does not know which pixels changed, so it redraws the entire widget for each frame.
Pixel transferTransfers only changed parts, avoiding writes of unchanged pixels. This can improve rendering speed when framebuffer transfers are slow, such as on SPI displays.Transfers the full widget image on each frame update, including unchanged pixels. That wastes bandwidth on slow displays.

Another option for multi-frame content is MJPEG video. MJPEG uses lossy compression, which is often suitable for photography and similar footage, but not always for graphics. Decoding is computationally expensive without hardware support. See the MJPEG Video article for details.

Non-memory-mapped flash​

The DeltaCompressedAnimation widget supports storing animations in non-memory-mapped flash in applications that use the LCD16bppSerialFlash.
Typical platforms are STM32C0, STM32G0, STM32U0, STM32C5, and STM32H5 microcontrollers without QSPI or OSPI support.

Animations stored in memory-mapped flash is supported on all platforms.

Hardware acceleration is used when applicable (GPU2D / DMA2D / GFXPAND).

Availability​

DeltaCompressedAnimation is not available in TouchGFX Designer. Create and configure the widget in application code using the methods described below.

Defining an Animation​

Define animations in a top-level animations array in application.config. Each animation must have a unique name and at least one frames entry. Frame paths are relative to the animation assets directory and must be listed in playback order.

{
"image_configuration": {
...
},
"text_configuration": {
...
},
"animations": [
{
"name": "animation1",
"section": "ExtFlashSection",
"encodings": ["COMP_RGB"],
"key_frame_interval": 5,
"frames": [
"animation1/ani_01.png",
"animation1/ani_02.png",
"animation1/ani_03.png"
]
}
]
}

Generate the animation by generating code in TouchGFX Designer (F4).

After the animation assets are converted, generated/images/include/images/Animations.hpp declares a resource named animation_<name>. For the configuration above, the resource is animation_animation1.

The generated animation data is stored in a standard .cpp file in the generated/images/src folder, one file per animation. It is automatically linked into the application like normal images.

Section placement​

Animations can be placed in different memory sections. The section property in the animation configuration specifies the memory section where the animation should be placed. For example, setting "section": "ExtFlashSection" places the animation in the external flash memory section. The section string can be set to any valid memory section supported by the linker script.

Compression methods​

It is possible to control the compression methods used in an animation. The Image Converter will select the best compression for each part of the animation. In some cases it is relevant to reduce the compression methods to improve the rendering performance or to avoid inclusion of the decompresser code in the application.

The allowed compression methods are specified as strings in an encodings array. These optional encodings are available in addition to RGB565:

EncodingDescriptionSupported by DMA2DSupported by Supported by GFXPAND
L8_RGB5658-bit palette indices with an RGB565 color table.No (*)No (**)No
L8_RGB8888-bit palette indices with an RGB888 color table.YesNo (**)No
L8_ARGB88888-bit palette indices with an ARGB8888 color table.YesNo (**)No
L8RLE_RGB565Run-length encoded 8-bit palette indices with an RGB565 color table.NoNoYes
L8RLE_RGB888Run-length encoded 8-bit palette indices with an RGB888 color table.NoNoYes
L8RLE_ARGB8888Run-length encoded 8-bit palette indices with an ARGB8888 color table.NoNoYes
COMP_RGBCompressed RGB pixel data.NoNoYes
COLORA solid-color fill.YesYesNo

(*) L8 with RGB565 color table is supported by DMA2D version 3 and is not recommended for DMA2D version 2 platforms.
(**) L8 rendering is delegated to DMA2D.

Using compressed encodings in animations requires the corresponding image compression feature to be enabled in the framework or having GFXPAND enabled. See Enabling the Image Compression Features.

The Image Converter will try the listed encodings where applicable. It will also use uncompressed RGB formats.
If no encodings are listed, the Image Converter will use all the above encodings.
The example above only allows compressed RGB (not L8).

Key Frame Interval​

The key_frame_interval setting controls how often the converter generates key frames. If omitted, it defaults to 20; set it to 0 to disable key-frame generation. A key frame contains a full frame, so when an area needs redrawing but is not covered by the current delta, the widget can restore it from the most recent key frame and apply the following deltas. The first frame of an animation is always a key frame.

Key frames can improve rendering performance when other graphics overlap or appear behind the animation and cause areas outside the current delta to be redrawn. A shorter interval limits how many deltas need to be replayed for those areas, at the cost of increasing the animation's storage size.

Double Buffering​

Set "double_buffering": true in the animation's application.config entry when using a double frame buffer strategy.
Each encoded frame then includes a combined delta from both that frame and the previous frame. This can improve rendering performance at the cost of a larger animation resource.
The option defaults to false.

Animation Resource size​

The animation shown in the introduction contains 50 frames at 480 x 272 pixels, with solid-color areas encoded as RGB565. The file sizes are:

Format and settingsSize (MB)Notes
Uncompressed RGB565 image sequence13.3 MBPixel data only; 480 x 272 x 2 bytes x 50 frames.
MJPEG, generated with FFmpeg2.1 MBLossy compression.
DeltaCompressedAnimation, all encodings allowed, no key frames0.5 MBSmallest result in this comparison.
DeltaCompressedAnimation, only L8 and L8-RLE encodings1.1 MBUses encodings selected for better rendering performance.

The animation is an example with large savings as the background pixels never change, only the butterfly. This background pixel information is repeated in a MJPEG video, where all frames are the full image.

The size of the generated animation and the compression rate can be found in the header of the generated file:

TouchGFX/generated/images/src/animation_butterfly_animation.cpp
#include <touchgfx/hal/Config.hpp>

// Header RAM buffer required: 129 words
// Uncompressed data size: 13317120 bytes.
// Compressed data size: 1101012 bytes = 8.26764%
// Key frame interval: 1000 frames
// Formats used: RGB565, L8_RGB565, L8_RLE_RGB565

Animation Resource​

Pass the animation resource to DeltaCompressedAnimation::setAnimation(). The widget reads the resource header to set its width and height. It does not copy the resource, so it must remain valid (readable) for as long as the widget uses it.

If the animation uses the L8 or the L8_RLE format it needs a small buffer in RAM to be able to draw the animation (see below).

Adding the Widget to a Screen​

DeltaCompressedAnimation is a normal C++ widget, so declare it as a member of the screen view that owns it. Include the widget header and add the widget as a member:

#include <touchgfx/widgets/DeltaCompressedAnimation.hpp>

class Screen1View : public Screen1ViewBase
{
public:
Screen1View();
void setupScreen() override;
void tearDownScreen() override;

private:
DeltaCompressedAnimation animation1;
};

Include the generated animation resources header in the .cpp file. In setupScreen(), assign the generated resource, position the widget, set its playback interval, add it to the view, and start playback:

#include <images/Animations.hpp>

void Screen1View::setupScreen()
{
Screen1ViewBase::setupScreen();

animation1.setXY(161, 40);
animation1.setAnimation(animation_anim1);
animation1.setUpdateTicksInterval(2);
add(animation1);
animation1.startAnimation();
}

With the default startAnimation() argument, playback stops at the final frame. Pass true to loop playback.
Stop timer-driven playback when the screen is torn down:

void Screen1View::tearDownScreen()
{
animation1.stopAnimation();
Screen1ViewBase::tearDownScreen();
}

Handling Animation Completion​

A completion callback is optional. To use one, add a callback member and handler to the view:

class Screen1View : public Screen1ViewBase
{
...
private:
touchgfx::Callback<Screen1View, const DeltaCompressedAnimation&> animationEndedCallback;

void animationEndedCallbackHandler(const DeltaCompressedAnimation& source);
}

Bind the callback in the view constructor, then register it with setDoneAction():

Screen1View::Screen1View()
: animationEndedCallback(this, &Screen1View::animationEndedCallbackHandler)
{
}

void Screen1View::setupScreen()
{
Screen1ViewBase::setupScreen();

animation1.setAnimation(animation_anim1);
animation1.setDoneAction(animationEndedCallback);
add(animation1);
animation1.startAnimation();
}

void Screen1View::animationEndedCallbackHandler(const DeltaCompressedAnimation& source)
{
if (&source == &animation1)
{
// Perform the screen-specific completion action here.
}
}

With the default startAnimation() argument, the callback runs once when playback reaches the final frame. Passing true to startAnimation() loops playback and invokes the callback at the end of every loop.

Keep the generated animation resource and any buffer passed to setHeaderBuffer() valid while the widget may draw from them.

Header Buffer​

Animations that use L8 or L8-RLE compression require a caller-provided buffer in RAM to draw those parts. The buffer must be writable, 32-bit aligned, and remain valid while the widget may draw from it. A buffer can be shared by widgets and screens.

The requirement is often below 100 32-bit words. Find the exact sizes of animations using the L8 or L8-RLE formats in the generated Animations.hpp header file:

TouchGFX/generated/images/include/images/Animations.hpp
#ifndef TOUCHGFX_ANIMATIONS_HPP
#define TOUCHGFX_ANIMATIONS_HPP

extern const unsigned char animation_anim1[];
extern const unsigned char animation_anim2[];

enum AnimationHeaderBufferSizes
{
ANIMATION_ANIM1_BUFFER_WORDS = 50,
ANIMATION_ANIM2_BUFFER_WORDS = 36,
ANIMATION_MAX_BUFFER_WORDS = 50
};

#endif // TOUCHGFX_ANIMATIONS_HPP

The ANIMATION_MAX_BUFFER_WORDS enum item defines the maximum number of 32-bit words required by any animation's headerbuffer.
Declare an array with the reported number of words elements. Pass its capacity in bytes to setHeaderBuffer() using sizeof:

class Screen1View : public Screen1ViewBase
{
private:
DeltaCompressedAnimation animation1;
uint32_t anim1_buffer[ANIMATION_ANIM1_BUFFER_WORDS];
};

void Screen1View::setupScreen()
{
animation1.setAnimation(animation_anim1);
animation1.setHeaderBuffer(anim1_buffer, sizeof(anim1_buffer));
add(animation1);

Configuration and Playback​

The widget is configured through its public methods rather than Designer properties.

MethodDescription
setAnimation()Assigns the resource and loads its current frame and dimensions.
setHeaderBuffer()Supplies the caller-owned buffer needed for L8 and L8-RLE parts.
setUpdateTicksInterval()Sets the timer ticks between frame changes.
setLoopAnimation()Sets whether playback restarts after the final frame.
startAnimation()Starts playback; pass true to loop after the final frame.
stopAnimation()Stops playback and unregisters the widget from timer ticks.
setDoneAction()Registers a callback for the final frame, or each loop's end.

Call setDoneAction() to observe playback completion. The callback receives a reference to the widget that completed the animation. Its parameter type is touchgfx::GenericCallback<const DeltaCompressedAnimation&>&.

Frame Control​

nextFrame() advances to the next frame and invalidates its rectangular parts. It wraps to the first frame after the final frame and returns false when the newly selected frame is the final frame. getFrameNumber() returns the zero-based index of the current frame.

API Reference​