Color Formats
Colors are what is seen on the pixels of the display. These colors come from values stored in a framebuffer. Traditionally in graphics systems, there is a limited amount of possible colors that can be represented, used and displayed. This applies also to TouchGFX and TouchGFX applications.
The number of possible pixel colors of an application has an impact on many parts of an application. From the visual appearance of what is seen on the display to the memory consumption imposed by the framebuffer and the overall performance. This section will explain colors in TouchGFX in more detail and describe the color formats available in TouchGFX and highlight pros and cons.
Color
A color in TouchGFX is a triplet of red, green and blue components, known as an RGB color. Each component of the color ranges from 0 to 255. 0 means that the component is off, and 255 means that the component is at its maximum.
A completely black color is represented by the RGB color (0,0,0) and a completely white is (255,255,255). Bright green is (0,255,0), a semi bright red (128,0,0), a darkish purple (64,0,64) and so on.
Grayscale
For grayscale applications all colors are gray, ranging from black to white and therefore the representation is the gray intensity instead of the RGB value. One can think of grayscale colors as RGB colors where R = G = B.
Opacity
In some circumstances we might consider colors to have a component describing the opacity of the color. The opacity ranges from 0 to 255, as the other components of the color. Colors with opacity are referred to as RGBA colors. The A stands for alpha, which is the classic name used for the opacity level.
A completely opaque black color here is (0,0,0,255), a somewhat transparent red is (255,0,0,128), and so on.
When a color is not completely opaque it needs to be mixed together with the color already present. This mixing of colors is known as alpha blending.
Color depth
Color depth is the number of bits used to describe each color as stored in the framebuffer. We denote this number the bits per pixel, or in short bpp.
The more bits we use, the more colors we can describe.
A much used color depth is 24 bpp. As each bit can be either on or off, this implies that 224 = 16777216 different colors can be represented.
Another, a little less used, color depth is 1 bpp. This color depth applies to black and white applications, and implies that only 21 = 2 different colors can be represented.
TouchGFX has built-in support for the following color depths:
- 32 bpp - 16777216 colors and corresponding opacity values
- 24 bpp - 16777216 colors
- 16 bpp - 65536 colors
- 6/8 bpp - 64 colors
- 4 bpp - 16 grayscale colors
- 2 bpp - 4 grayscale colors
- 1 bpp - 2 grayscale colors
A note on color component ranges. When working with less than 24 bpp color depth, each of the red, green and blue components does not directly range from 0 to 255. Instead such a component, say red in 16 bpp, ranges 0 to 31. We will think of the value 31 as representing the most red we can represent in 16 bpp, i.e. 255 in 24 bpp. One way of thinking of this is that colors of 16 bpp depth can only represent a subset of the colors possible in 24 bpp.
In 6/8 bpp color depts, each pixels uses 6 bits for the color information (2 bits each for red, green, and blue). To simplify the framebuffer access each pixel is increased from 6 bits to 8 bits (1 byte). The extra two bits in the framebuffer are unused.
Formats
Having determined the amount of bits needed to represent colors, we investigate the contents of the bits in more detail. A color will have some bits describing the red component, some the green and some the blue, but the color depth alone does not specify the order (format) of the bits in a pixel. E.g. blue first, then green, then red, or maybe in the reverse order.
Pixel color formats
Dependent on the color depth of the application, some particular color formats will be available.
RGB888
In TouchGFX, a color of 24 bpp color depth will have the color format RGB888. This means that 8 bits are used for each of the color components red, green and blue.
Representing such a color, say bright purple rgb(255,0,255), in code is done by assembling the components into a color value
uint32_t brightPurpleRGB888 = 255 << 16 | 0 << 8 | 255 << 0;
In this format, red is in the highest 8 bits, then green, and blue in the low 8 bit.
RGB565
For 16 bpp colors, TouchGFX uses the color format RGB565. That is 5 bits for red, 6 bits for green, 5 bits for blue. As we have 5 bits for red, fully lit is 31, so the bright purple in code is
uint16_t brightPurpleRGB565 = 31 << 11 | 0 << 5 | 31 << 0;
RGBx2222, xRGB2222, BGRx2222, xBGR2222
For 6 bpp colors, TouchGFX supports 4 different formats, RGBx2222, xRGB2222, BGRx2222, xBGR222. 6 bit colors are stored as bytes and this is the reason for having the x in the forementioned formats. The color is padded with 2 bits, to fit into a byte. The reason for having both RGB and BGR is that some displays need this and we do not want to convert pixels before sending them to the display. Representing a bright yellow in RGBx2222 in code is
uint8_t brightYellowRGBx2222 = 3 << 6 | 3 << 4 | 0 << 2;
GRAY4, GRAY2, BW
For each of the grayscale color depths TouchGFX supports one corresponding color format. For 4 bpp the color format is denoted GRAY4, for 2 bpp it is GRAY2 and for 1 bpp it is denoted BW for black and white. In 4 bpp a completely white color is
uint8_t whiteGRAY4 = 15;
TouchGFX includes a helper function that will return the correct representation of a color in the correct color format.
#include <touchgfx/Color.hpp>
...
aColor = Color::getColorFromRGB(255,0,128);
Image formats
Images are an important part of most UI applications and images are filled with colors. In TouchGFX images are stored in memory and are filled with colors of a specific format. In many cases images are using one of the supported pixel color formats, but other image formats are also available. A pixel in an image of a particular image color format will be converted into the appropriate pixel format before being drawn
Image Color format | Description |
---|---|
ARGB8888 | 32 bits, 8 bits per component |
L8_ARGB8888 | 8 bits indexed format, ARGB8888 palette |
RGB888 | 24 bits, 8 bits per component. |
L8_RGB888 | 8 bits indexed format, RGB888 palette |
RGB666 | 24 bits, 6 bits per component |
RGB565 | 16 bits, 5 bits red, 6 bits green, 5 bits blue |
L8_RGB565 | 8 bits indexed format, RGB565 palette |
ARGB2222 | 8 bits, 2 bits per component |
ABGR2222 | 8 bits, 2 bits per component |
RGBA2222 | 8 bits, 2 bits per component |
BGRA2222 | 8 bits, 2 bits per component |
GRAY4 | 4 bits grayscale |
GRAY2 | 2 bits grayscale |
BW | 1 bit grayscale |
BW_RLE | 1 bit grayscale run-length encoded |
Some of these image formats, the L8 ones, are representing the image in question by a lookup table of colors (known as a CLUT) and indices into this table. The maximum number of possible colors in such an L8 image is 28 = 256. The L8 formats take up less space than their non-L8 counterparts, as an example a 100x100 image with 200 different colors, takes up 100x100x32 bits = 40000 bytes when stored in the ARGB8888 format, and only 100x100x8 bits + 200x32 bits = 10800 bytes when stored in the L8_ARGB8888 format.
The image format BW_RLE stores the colors as consecutive runs of black and white instead of storing single pixel colors. This can in many cases also be more space efficient.
The rest of the formats are the same as the pixel color formats above.
Framebuffer formats
Not all image formats are available as framebuffer formats. The L8 formats cannot be used as framebuffer formats in TouchGFX. This is because it is not practical blend to two images in the framebuffer.
Byte and pixel order
The 24 bits format RGB888, and 32 bits format ARGB888, is often accessed using a byte pointer. When doing that it is necessary to understand that the pixels are stored in little endian order.
Take as an example the 32 bits color 0xFFFF7700 (alpha = 0xFF, red = 0xFF, green = 0x77, blue = 0x00). When the color is in a 32 bit variable or register, the value is 0xFFFF7700. When the color is stored in memory the bytes stored are { 0x00, 0x77, 0xFF, 0xFF }. This corresponds to the order BGRA.
Similarly, the 16 bits format, RGB565, is always accessed through a 16 bit pointer, so the byte order is not interesting, but it is swapped in memory.
For the 8 bits formats, e.g. ARGB2222, the color fits into a byte (alpha in the two highest bits), which is stored without change.
The smaller formats, GRAY4, GRAY2, and BW, can be stored in two orders. The low bits can be the leftmost pixel or the rightmost pixel. If the low bits are the leftmost we call this LSB-mode, otherwise it is MSB-mode.
Framebuffer format | Order | Description |
---|---|---|
ARGB8888 | BGRA | 32 bits, 8 bits per component |
XRGB8888 | BGRX | 32 bits, 8 bits per component, alpha byte ignored |
RGB888 | BGR | 24 bits, 8 bits per component. |
RGB565 | 16 bits, 5 bits red, 6 bits green, 5 bits blue | |
ARGB2222 | 8 bits, 2 bits per component | |
ABGR2222 | 8 bits, 2 bits per component | |
RGBA2222 | 8 bits, 2 bits per component | |
BGRA2222 | 8 bits, 2 bits per component | |
GRAY4 | LSB | 4 bits grayscale |
GRAY2 | LSB | 2 bits grayscale |
BW | MSB | 1 bit grayscale |
Text formats
Texts, or more accurately glyphs, are also stored in memory in a specific color format. The available text color formats in TouchGFX are
Text Color format | Description |
---|---|
A8 | 8 bits, opacity only |
A4 | 4 bits, opacity only |
A2 | 2 bits, opacity only |
A1 | 1 bits, opacity only |
Glyph formats are comparable to small images, where each color entry holds the level of opacity of each pixel. This enables applying the actual color, the red, green and blue components, at a later time, and enables drawing for instance the stored glyph 'A' in both a blue version and a red version.
The more bits used for each glyph the smoother and nicer it will typically appear.
Visual quality
When doing embedded graphics we want the highest visual quality, but at the same time we need to look at the amount of memory consumed.
Therefore it is many times desirable to go for an RGB565 color format instead of the richer RGB888 and in general we should go for the color format that provides us the most visual quality, while respecting our memory requirements.
Dithering
TouchGFX employs what is known as dithering to improve the visual quality of images when representing these in different color formats.
Dithering is a well known technique for making images appear to have more colors than what is actually present. This is done by adding a bit of noise to the colors of the image.
One example - when converting an RGB888 image to an RGB565 image, instead of just chopping of the lower bits of each color component, the conversion process adds some noise to each of the resulting colors, the result being that the converted image looks richer and similar to the RGB888 original.
Explaining by images instead of words, we have an original RGB888 image, and a number of converted images. The converted images have the formats RGB565 with and without dithering, xRGB2222 with and without dithering, GRAY4 with and without dithering.
As can be seen dithering improves the perceived quality of images quite a bit. When looking closely at the RGB565 with and without dithering, it can be seen that the dithered version looks almost exactly like the original whereas the undithered one has some areas where color banding is apparent. This examplifies that in many cases 16 bit colors is more than enough to do great looking graphics.
When your graphics assets has big gradients, you might experience some color banding even in dithered images. Here are two examples. A bluish gradient ranging from RGB888 (64,190,222) to black and the converted RGB565 image with and without dithering.
And another red gradient ranging from (255,0,0) to black.
Looking closely, it can be seen that color banding is present in both the dithered and the undithered RGB565 versions. The red image has the most noticeable bands.
Always pay close attention to your resulting images and color formats and if needed alter your original images or choose another color format.
Performance
All the image formats discussed are optimized for "easiness" of drawing. This means that the images can more or less be copied to the framebuffer without much conversion taking place.
This is intentional and is one of the reasons TouchGFX can achieve fluent graphics on microcontrollers.
When designing a UI with TouchGFX one uses .png images and at compile time each of these images are converted into one of the efficient image formats detailed above.
Alpha blending
At runtime the copying of image data is done either by a regular CPU copy operation or by using features of the MCU. If the image includes pixels that are not completely transparent or opaque, the pixels need to be alpha blended onto the background. In some STM32 MCUs this blending is supported by the hardware.
Other image formats
If you need to support other image formats at runtime, for example compressed image formats, such as .jpg or .png you can utilize the support of TouchGFX for dynamic bitmaps.