Image Compression
TouchGFX supports image compression starting from version 4.22. The support in 4.22 is limited to compression of L8 images.
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 present in many STM32 micro controllers cannot draw a compressed image directly. Compressed images are always drawn using software rendering.
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.
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 compressing 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 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 images very easy.
There are a few limitations. Compressed images cannot be used with Widgets that scales or rotates the image or with the Canvas widgets. See the list below.
Caution
3 algorithms
TouchGFX uses 3 different compression algorithm. 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.
Working with compressed images
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
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 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 level. 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% (not counting the color table).
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. Therefore, compressed images are always drawn by software 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));
bool r = Bitmap::decompress(BITMAP_GAUGE_BACKGROUND_ID, lzwBuffer);
image1.setXY(148, 148);
image1.setBitmap(touchgfx::Bitmap(BITMAP_GAUGE_BACKGROUND_ID));
}
The bitmap cache must big enough the hold uncompressed image. Our image is 184 x 184 pixels plus the color table holding 207 ARGB8888 colors. The total size is therefore 34.688 bytes.
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 RLE and L4 compressed images.
When the decompressed image is not used anymore, it can be removed from the bitmap cache using the Bitmap::cacheRemoveBitmap
method.
Limitations
A compressed image can not be used with the Widgets that scales or rotates the image, or the Widgets that fills an area. 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 L8 image with 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:
- 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