Skip to main content

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.

Some RGB colors

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.

Some RGBA colors atop white and grey

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 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.

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.

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;

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, and a bright red in code is

uint16_t brightPurpleRGB565 = 31 << 11 | 0 << 5 | 0 << 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::getColorFrom24BitRGB(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 formatDescription
ARGB888832 bits, 8 bits per component
L8_ARGB88888 bits indexed format, ARGB8888 palette
RGB88824 bits, 8 bits per component.
L8_RGB8888 bits indexed format, RGB888 palette
RGB66624 bits, 6 bits per component
RGB56516 bits, 5 bits red, 6 bits green, 5 bits blue
L8_RGB5658 bits indexed format, RGB565 palette
ARGB22228 bits, 2 bits per component
ABGR22228 bits, 2 bits per component
RGBA22228 bits, 2 bits per component
BGRA22228 bits, 2 bits per component
GRAY44 bits grayscale
GRAY22 bits grayscale
BW1 bit grayscale
BW_RLE1 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-1 = 255. The L8 formats take up less space than their non-L8 counterparts, as an example a 100x100 image with 200 different colors, takes up 10010032 bits = 40000 bytes when stored in the ARGB8888 format, and only 1001008 bits + 200*32 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.

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 formatDescription
A88 bits, opacity only
A44 bits, opacity only
A22 bits, opacity only
A11 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.

Original RGB888 image

Converted RGB565 images with and without dithering

Converted xRGB2222 images with and without dithering

Converted GRAY4 images 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.

Original RGB888 and converted RGB565 images with and without dithering

And another red gradient ranging from (255,0,0) to black.

Original RGB888 and converted RGB565 images with and without dithering

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.

Further reading
Wikipedia article on color depth.