Image Compression
TouchGFX supports image compression starting from version 4.22. The support from version 4.22 to 4.23 inclusive is limited to compression of L8 images. From version 4.24 compression of RGB565, RGB888, and ARGB8888 image formats is supported.
Image compression is the process of reducing the storage requirements for an image. The reduced size of the images in a project can result in cost reductions if a smaller flash is used. The reduction can also mean that more images can be used in the project resulting in a richer UI.
Image compression generally comes in two flavors: Lossless or lossy. Lossy image compression works by removing minor details of the image. This often gives the biggest reduction but the original image cannot be reproduced exactly. Lossless compression always reproduces the original image without any differences. Lossless compression gives in general a lower size reduction.
For graphics it is often required that UI elements are drawn exactly as they were designed. For this reason TouchGFX only supports lossless compression.
The advantage of image compression is the size reduction, but there is also a disadvantage, as the image must be decompressed when it is drawn to the frame buffer. This decompression requires in many situations more work from the CPU compared to drawing an uncompressed image. A performance reduction is possibly the outcome.
This means that the advantage gained from the flash reduction must be compared to the disadvantage from the increased CPU usage.
Be aware that the graphics accelerators DMA2D and GPU2D (ChromART and NeoChrom GPU) present in many STM32 micro controllers cannot draw a compressed image directly. Compressed images are drawn using a mix of software and hardware rendering, i.e. the compressed data is decompressed in chunks by software and these chunks are then delegated to the DMA2D where applicable.
In many applications is it not recommended to compress all images, but only those where the performance is not hurt and the flash reduction is relevant. See also the section below about decompression to the Bitmap cache as a mean to get both lower storage requirement and good performance.
L8 Compression
As mentioned above TouchGFX 4.22 supports compression of L8-images. Recall that the L8 bitmap formats are suitable only for images with up to 256 colors. Each pixel is just an 8-bit number that refers to a color in a color table stored with the image. The compression of an L8 is only compression of the pixel numbers. The color table is left untouched.
As an example consider the image below. It is used as a background in a metering application.
The image is 184 x 184 pixels. The size of the pixel data is thus 184 x 184 = 33,856 bytes.
If we compress the image the pixel data is reduced to 5,735 bytes. The total size of the compressed image data, including the color table, is less than 20% of the original image. Compression thus allows us to have 5 different backgrounds in the same flash space, or to reduce the flash requirements by 28,121 bytes.
A compressed L8 image is used like an ordinary uncompressed bitmap. You can for example show the image using the Image widget without any modification to the project in the TouchGFX Designer or in code. This makes use of compressed L8 images very easy.
3 algorithms #{three-algorithms}
TouchGFX uses 3 different compression algorithms for the L8 format. The image converter selects the algorithm that gives the best compression, unless the user has mandated a specific algorithm in the configuration. The algorithms are:
- L4, encode each pixel in 4 bits. Only works for images with up to 16 colors.
- RLE, run-length encoding of the pixels. Only works for images with up to 64 colors.
- LZW9, dictionary based encoding. Works for all L8 images.
The RLE algorithm decompresses much faster than LZW9, so the image converter will select RLE if LZW9 is only compressing the image marginally better.
RGB Compression
As mentioned earlier, TouchGFX 4.24 introduced image compression support for the RGB565, RGB888, and ARGB8888 image formats. Images which contains more than 256 unique colors cannot be stored in the compact L8 image format and must be stored in one of the aforementioned formats. The compression of an RGB565, RGB888, or ARGB8888 image is directly compressing the 16-, 24-, or 32-bit pixels.
As an example consider the image below. It is a more complex and rich background than the above example and has more than 256 unique colors. Therefore, it cannot be stored in L8, and it must be stored in the ARGB8888 format because it has transparent pixels (in the corners).
The image is 240 x 240 pixels. The size of the pixel data is thus 240 x 240 x 4 = 230.400 bytes because we use 4 bytes to store each pixel in the ARGB8888 image format.
Compressing the image reduces the size to 32.347 bytes. The size of the compressed image is considerably smaller and only 14% of the original image size. Compression allows us to have multiple complex backgrounds in the same flash space, or reduce the flash requirements by a substantial amount. It also enables flash-limited devices to adopt more complex and rich graphics than otherwise possible with the L8 formats.
A compressed RGB image can be used like an ordinary uncompressed bitmap. You can for example show the image using the Image widget without any modification to the project in the TouchGFX Designer or in code. This makes use of compressed RGB images very easy.
Caution
2 algorithms
TouchGFX uses 2 slightly different compression algorithms which are automatically selected based on the image format to be compressed. The algorithms are:
- QOI, encodes RGB888 and ARGB8888 pixels using a variation of the Quite OK Image format compression algorithm.
- QOI565, encodes RGB565 pixels using a variation of QOI that is tailored for 16-bit pixel values.
Both variants of the RGB Compression algorithms are optimized for fast decompression speeds to limit the run-time performance penalty when rendering compressed RGB images.
Working with Compressed Images
Enabling the Image Compression Features
Image compression requires extra code in the target application. To avoid this higher space requirements, the compression code is optional on some platforms. You may have to enable the feature for your project.
Click "Config" on the left of the Designer, then click "Framework Features"
The image compression features are shown in the bottom. You can enable the specific feature set you need.
The targets has different options for the framework features, and in some cases the target has no optional features as shown below:
In this case all the features are always enabled.
Caution
L8 Compression
Compressed images are used just like the ordinary images. You either configure widgets to use the bitmaps in the TouchGFX Designer, or you assign the Bitmaps in code.
The only configuration required is to set the Compression value to "Auto". The Image Converter will then automatically select the most suitable compression or none if the image is not compressible.
Now we can select the image for a Widget as normal. There is no difference here compared to an uncompressed image.
There is also no difference when working with the images in code. The compressed bitmaps are referenced using their BitmapID as usual:
image1.setXY(148, 148);
image1.setBitmap(touchgfx::Bitmap(BITMAP_GAUGE_BACKGROUND_ID));
Read more about compressed L8-images here
RGB Compression
Compressing RGB565, RGB888, or ARGB8888 images in the TouchGFX Designer follows the same process as L8 compression.
If selecting "Yes" the Image Converter will compress the image with the algorithm that matches the image format.
As with the L8 Compression, we can select the image for a widget as normal and reference the bitmap in code as usual.
Compression level
The compression algorithm selected by the Image Converter is written in the generated files. Here we can also find the compression level.
The image we used above in the L8 Compression example is generated
into the file generated/images/src/image_gauge_background.cpp
. The
header of this file reads:
image_gauge_background.cpp (extract)
// 4.22 D0 AN R0 FL8_ARGB8888 U888 N0 SExtFlashSection EExtFlashSection CL8_LZW9
LOCATION_PRAGMA("ExtFlashSection")
KEEP extern const unsigned char image_gauge_background[] LOCATION_ATTRIBUTE("ExtFlashSection") = {
// 184x184 L8_ARGB8888 pixels. Compression [output/input x 100]: 5735/33856 x 100 = 16.9%
0x00, 0x26, 0x50, 0xa8, 0x60, 0xe1, 0x02, 0x86, 0x0c, 0x1a, 0x36, 0x70,
....
The end of the comment in the first line shows the compression algorithm. Here we see that the Image Converter has selected the LZW9 algorithm. The end of line 5 shows that we have now 5735 bytes of pixel data whereas the original was 33856 bytes. Resulting in a compression to 16.9% (the smaller compression level the better). Note! The compression percentage does not count the color table.
Compression Failure
In some cases the Image Converter gives a warning or error when compressing images. This can happen if the image is not compatible with the selected algorithm, or if the size of the compressed image is not below 90% of the original image. This will only happen in very rare coincidences. For example if a small image of 5 x 5 uses 25 different and distinct colors. Since there is no repetition or redundancy in the image, there is no possibilities for the compression to reduce the data.
L8 Images
For L8 images this can happen if a specific algorithm was selected that does not match the given image. For example if an image contains 17 colors or more, and and L4 compression is selected, the image can not be compressed using the selected algorithm. The Image Converter will print an error message when you generate code, and the Designer will show an error message:
The solution to the problem is to use another algorithm. The preferred way is to select "Auto" for L8 images. Then the image converter will try all algorithms, and select the best algorithm of those who are applicable.
In some rare cases the image does not compress below 90% of the original size. The Image Converter does not compress the image in that case and issues a warning text as shown below:
The reason for not compressing is that the saving in flash size does not outweigh the lower rendering performance. It is possible to force the compression by not using "Auto" but selecting one of the algorithms ("L4", "RLE", or "LZW").
The warning message is only informational. The code generation continues and the project will work as expected.
RGB Images
For RGB image formats you can not select the "Auto", as there is only one compression algorithm. If you select "Yes" and the image is not compressible below 90% of the original size, the Image Converter will generate an error:
The solution here is to select "None" for Compression for the image.
Decompressing Images to the Bitmap Cache
Drawing compressed images performs in most cases worse than drawing uncompressed images. Further more, as mentioned in the introduction, the graphics accelerators in STM32 micro-controllers (DMA2D and GPU2D) cannot draw the compressed images directly. Therefore, compressed images are drawn by a mix of software and hardware, resulting in lower performance and higher cpu-load.
For these reasons TouchGFX also contains functionality to decompress a compressed image into the bitmap cache in RAM at runtime.
When the image is decompressed to RAM the drawing performance is similar to using uncompressed images and the accelerators can draw the image.
To be able to decompress we need first to setup the bitmap cache. Read more about using the bitmap cache here.
After setting up the bitmap cache, we can decompress the image using
the function Bitmap::decompress
. The full code is shown below:
// Define an array for the bitmap cache
uint16_t cache[20*1024]; //40 KB cache
// Define an array for the decompression temporary buffer
uint16_t lzwBuffer[1024];
void TemplateView::setupScreen()
{
...
Bitmap::setCache(cache, sizeof(cache)); // Register the bitmap cache
bool r = Bitmap::decompress(BITMAP_GAUGE_BACKGROUND_ID, lzwBuffer); // Decompress the bitmap
image1.setBitmap(touchgfx::Bitmap(BITMAP_GAUGE_BACKGROUND_ID)); // Use the bitmap as normal
image1.setXY(148, 148); // Position Image widget
}
In this example we want to decompress the 184 x 184 image from the L8 Compression example above into the bitmap cache. The bitmap cache must be big enough to the hold uncompressed image. The 184 x 184 pixels plus the color table holding 207 ARGB8888 colors. The total size is therefore 34,688 bytes.
In this example we use the LZW9 algorithm. A 2048 bytes buffer is used by the decompresser during the decompression of an LZW9 compressed image (for building a dictionary). The buffer is not required after the decompression and can be reused for other purposes. The buffer is not required for decompression of L4, RLE, QOI, or QOI565 compressed images.
When the decompressed image is not used anymore, it can be removed
from the bitmap cache using the Bitmap::cacheRemoveBitmap
method.
Limit program size
If you use decompression into the bitmap cache you have a few options
to limit the size of your program. As mentioned above there are two
types of image compression; L8 and RGB. When using
Bitmap::decompress
your program will contain the code for both
decompressing L8 and RGB images. If you only use the RGB image
compression, you can use the dedicated method for decompressing RGB
images into the bitmap cache, which is Bitmap::decompressRGB
, that
way your program will only contain the required code for decompressing
RGB images. The same applies if you only use L8 compression, here the
method is called Bitmap::decompressL8
. See examples below.
void TemplateView::setupScreen()
{
...
// Decompress the bitmap (RGB using QOI)
bool r = Bitmap::decompressRGB(BITMAP_GAUGE_BACKGROUND_ID);
...
}
void TemplateView::setupScreen()
{
...
// Decompress the bitmap (L8 using RLE, no buffer required)
bool r = Bitmap::decompressL8(BITMAP_GAUGE_BACKGROUND_ID);
...
}
Limitations
There are a few limitations when working with compressed images. Compressed images cannot be used with Widgets that scales or rotates the image, or with Widgets that fills an area, or with the Canvas widgets.
Caution
These limitations are made for performance reasons. The TouchGFX Designer will not allow you to select a compressed image.
If you want to use a specific image with any of these Widgets we suggest to not enable compression for the image. Alternatively, you can decompress the image at runtime.
The Widgets that does not support compressed images are:
- TextureMapper
- ScalableImage
- Gauge for the needle and arc
- Static- and DynamicGraph for filling the area below the graph
- AnalogClock for the hands
- Circle, Line and Shape
- Circle- and LineProgress