From Design Tool to 16-Bit Display

Description

In this training, you will learn why an image may look slightly different on a 16-bit color display than it does in the design tool, and how to prepare it for the best possible result.

The effect is most visible on gradients, which turn into a series of flat bands.

Intended Audience

This training is designed for UI designers and application developers who produce the image assets of an application, and for embedded system developers who choose a display format for their product.

Prerequisites

To get the most out of this training, participants should have:

  • A basic understanding of color representation and depth.

  • A basic understanding of the Image Generator.

Training

Where the Color Is Lost

A design tool works in 24-bit color: 8 bits of red, 8 of green and 8 of blue. An RGB565 pixel stores 5 bits of red, 6 of green and 5 of blue, so 3 low bits of red, 2 of green and 3 of blue have to go.

The bits are truncated, not rounded, so a color is always converted downward. What is left is 32 levels of red, 64 levels of green and 32 levels of blue. Two consecutive representable colors are 8 apart out of 255 on red and blue and 4 apart on green, and pure white #FFFFFF becomes #F8FCF8.

On a photograph this is invisible, because two neighboring pixels already differ by more than one step. On a gradient it is very visible: the gradient crosses each step at a precise position and holds the same color on both sides of it, which the eye reads as a band.

That truncation happens at two independent places:

  • The Image Generator truncates when the output format requested for an image is narrower than the image file.

  • The display truncates when its own pixel format is narrower than what is drawn into it.

The table below crosses the two. Every cell is a screen capture of the same 320x240 gradient, drawn by an application running on a simulator: the rows are the output format requested from the Image Generator, the columns are the pixel format of the display.

RGB565 display

RGB888 display

Image in RGB888

RGB888 image on an RGB565 display, banded RGB888 image on an RGB888 display, smooth

Image in RGB565

RGB565 image on an RGB565 display, banded RGB565 image on an RGB888 display, banded

Image in RGB565, dithered

Dithered RGB565 image on an RGB565 display, smooth Dithered RGB565 image on an RGB888 display, smooth

Only three of those six captures differ, and the repeated ones are not an editing shortcut: they are identical byte for byte.

  • On an RGB565 display, the RGB888 image and the RGB565 image give exactly the same pixels. Requesting a wider output format from the Image Generator buys nothing there, because the display truncates what the generator did not.

  • The only capture that keeps the colors of the source file is the one that never meets a 16-bit format at all, which is not the situation of a product with a 16-bit display.

The last row of the table is the one worth reaching, and the rest of this page is about how to get there.

The number of bands depends on how much color the gradient crosses, not on its size. A gradient between two close colors crosses few steps and produces few, wide bands, which are the most visible. A gradient between two distant colors produces many narrow bands, which are much harder to see.

Fixing a Banded Gradient

The color loss itself cannot be avoided: the display has the pixel format it has. What can be avoided is the regularity of the bands, and the way to do it is dithering.

Dithering recovers no lost bit: the dithered image of the table holds the same levels per channel as the banded one, 8 of red and 13 of blue over the narrow range that gradient crosses. What it changes is that neighboring pixels alternate between two of those levels instead of holding one flat value across a band, so the eye averages them back into a gradient. The image trades a visible band for an invisible grain. Counted on those captures, the banded rendering uses 23 distinct colors and the dithered one 45, against 170 for the rendering that meets no 16-bit format at all.

Dithering also makes the rendering independent of the display. The dithered image gives exactly the same pixels on an RGB565 display and on an RGB888 one, because its colors already sit on the levels an RGB565 display can show, leaving that display nothing to truncate.

Proceed as follows:

  1. Take the source image, the file declared in a *.images.list file, as it is before conversion. Dithering an image that the Image Generator has already converted has nothing left to work with.

  2. Produce a dithered copy of it, either with Adobe Photoshop or on the command line. If the image has soft transparency, use the command line: Photoshop’s PNG-8 export stores transparency on one bit only, as explained in Transparency: PNG-8 or PNG-24.

  3. Replace the source image with the dithered copy, under the same file name.

  4. Build the application again, so that the Image Generator converts the new file.

  5. Look at the result on the board. If bands remain, raise the number of colors and start again at step 2.

Dithering with Adobe Photoshop

Open the image, then select File > Export > Save for Web (Legacy), and set the following values:

  • Format: PNG-8,

  • Color reduction algorithm: Adaptive,

  • Dither mode: Diffusion,

  • Dither: 100%,

  • Colors: the lowest value that leaves no visible band, starting at 16 for a gradient made of a single hue.

Then click Save and overwrite the source image.

Save for Web dialog with the PNG-8 preset

Save for Web dialog with the PNG-8 preset

Warning

A photograph, or an image with several hues, needs a much higher Colors value than a single-hue gradient. Raise it until the artifacts disappear, then stop, because a higher value also makes the file larger.

The two sections below show what the dither mode and the color reduction algorithm change, and why the values above are the ones to use.

Choosing the Dither Mode

No Dither keeps the bands:

No Dither

No Dither

Diffusion spreads the error over the neighboring pixels and gives the most natural result:

Diffusion dither

Diffusion dither

Noise gives a comparable result with a more random grain:

Noise dither

Noise dither

Pattern applies a fixed matrix, which stays visible as a regular texture:

Pattern dither

Pattern dither

Diffusion and Noise are the two usable modes. The Dither percentage next to them controls how much grain is applied; 100% is the usual choice.

Choosing the Color Reduction Algorithm

Adaptive builds the palette from the colors actually present in the image:

Adaptive palette

Adaptive palette

Restrictive uses a fixed palette, which gives a coarser result on a narrow gradient:

Restrictive palette

Restrictive palette

Adaptive is the right default for an image that is going to be converted to a display format.

Transparency: PNG-8 or PNG-24

PNG-8 stores transparency on one bit: a pixel is either fully opaque or fully transparent. That is enough for an image with a hard edge, and not enough for an image with a soft edge or a drop shadow.

An image that needs real alpha levels must be exported as PNG-24, which offers no dithering at all.

Save for Web dialog with the PNG-24 preset

Save for Web dialog with the PNG-24 preset

No Save for Web setting gives both. An image that needs a dithered gradient and soft transparency at the same time has to be dithered on the command line, where the operation is restricted to the color channels and leaves the transparency untouched.

Dithering on the Command Line

The same result is reachable without a design tool, which is useful to process a whole set of assets or to run the conversion from a build script.

With ImageMagick:

magick input.png -channel RGB -ordered-dither o8x8,32,32,64 output.png

The command reduces the image to 32 levels of red, 64 of green and 32 of blue, which are exactly the levels an RGB565 display can show, and spreads the resulting error over an 8x8 matrix. -channel RGB restricts the operation to the color channels, so an image with an alpha channel keeps its transparency levels untouched. Without it, the transparency is dithered too.

Note

The three numbers are not given in red, green, blue order. The values above are the ones that produce 32, 64 and 32 levels. Check the result of the command on a full-range test image:

magick -size 512x64 gradient:"#000000-#FFFFFF" PNG24:ramp.png
magick ramp.png -channel RGB -ordered-dither o8x8,32,32,64 PNG24:check.png
magick check.png -channel R -separate -format "%k " info:
magick check.png -channel G -separate -format "%k " info:
magick check.png -channel B -separate -format "%k\n" info:

The three commands print 32, 64 and 32. These values were obtained with ImageMagick 7.1.2.

Impact on Compression

Dithering makes neighboring pixels differ on purpose, which is the opposite of what a run-length compression looks for. An image that compresses well in a RLE format before dithering may grow substantially after it, and may end up larger than the same image in an uncompressed format.

Check the size of the generated resource after dithering an image that uses a RLE format.