Frank's Random Wanderings

Fast RGB332 to RGB565 Colorspace Conversion

colorwheel

I’ve recently been working on a project with a small 320×240 TFT display connected to a microcontroller. In this case an ARM Cortex-M0+. Being able to display images is a really nice feature, however images can consume a lot of space in the limited flash memory of the microcontroller.

The controller chip built into the display accepts RGB565 data, which is to say, 16 bits per pixel. One idea to reduce the size of the images is to use RGB332, which is 8 bits per pixel. For displaying photographs this drop in color depth will be visually noticeable, however for icons and similar things RGB332 would be just fine.

Images can be converted to RGB332 using the free download program “Image2LCD” found here:

http://www.buydisplay.com/default/lcd-software

Now the microcontroller needs a fast way of converting the 8-bit RGB332 “pixel data” of the saved image file back into the 16-bit RGB565 “pixel data” that the display needs to receive. The obvious answer is a lookup table. I couldn’t quickly find such a table on the web so I spent a little time on the weekend writing a program to generate a table. Here’s my resulting RGB332 to RGB565 lookup table:

const Uint16 RGB332to565lookupTable[256] = {
    0x0000, 0x000a, 0x0015, 0x001f, 0x0120, 0x012a, 0x0135, 0x013f, 
    0x0240, 0x024a, 0x0255, 0x025f, 0x0360, 0x036a, 0x0375, 0x037f, 
    0x0480, 0x048a, 0x0495, 0x049f, 0x05a0, 0x05aa, 0x05b5, 0x05bf, 
    0x06c0, 0x06ca, 0x06d5, 0x06df, 0x07e0, 0x07ea, 0x07f5, 0x07ff, 
    0x2000, 0x200a, 0x2015, 0x201f, 0x2120, 0x212a, 0x2135, 0x213f, 
    0x2240, 0x224a, 0x2255, 0x225f, 0x2360, 0x236a, 0x2375, 0x237f, 
    0x2480, 0x248a, 0x2495, 0x249f, 0x25a0, 0x25aa, 0x25b5, 0x25bf, 
    0x26c0, 0x26ca, 0x26d5, 0x26df, 0x27e0, 0x27ea, 0x27f5, 0x27ff, 
    0x4800, 0x480a, 0x4815, 0x481f, 0x4920, 0x492a, 0x4935, 0x493f, 
    0x4a40, 0x4a4a, 0x4a55, 0x4a5f, 0x4b60, 0x4b6a, 0x4b75, 0x4b7f, 
    0x4c80, 0x4c8a, 0x4c95, 0x4c9f, 0x4da0, 0x4daa, 0x4db5, 0x4dbf, 
    0x4ec0, 0x4eca, 0x4ed5, 0x4edf, 0x4fe0, 0x4fea, 0x4ff5, 0x4fff, 
    0x6800, 0x680a, 0x6815, 0x681f, 0x6920, 0x692a, 0x6935, 0x693f, 
    0x6a40, 0x6a4a, 0x6a55, 0x6a5f, 0x6b60, 0x6b6a, 0x6b75, 0x6b7f, 
    0x6c80, 0x6c8a, 0x6c95, 0x6c9f, 0x6da0, 0x6daa, 0x6db5, 0x6dbf, 
    0x6ec0, 0x6eca, 0x6ed5, 0x6edf, 0x6fe0, 0x6fea, 0x6ff5, 0x6fff, 
    0x9000, 0x900a, 0x9015, 0x901f, 0x9120, 0x912a, 0x9135, 0x913f, 
    0x9240, 0x924a, 0x9255, 0x925f, 0x9360, 0x936a, 0x9375, 0x937f, 
    0x9480, 0x948a, 0x9495, 0x949f, 0x95a0, 0x95aa, 0x95b5, 0x95bf, 
    0x96c0, 0x96ca, 0x96d5, 0x96df, 0x97e0, 0x97ea, 0x97f5, 0x97ff, 
    0xb000, 0xb00a, 0xb015, 0xb01f, 0xb120, 0xb12a, 0xb135, 0xb13f, 
    0xb240, 0xb24a, 0xb255, 0xb25f, 0xb360, 0xb36a, 0xb375, 0xb37f, 
    0xb480, 0xb48a, 0xb495, 0xb49f, 0xb5a0, 0xb5aa, 0xb5b5, 0xb5bf, 
    0xb6c0, 0xb6ca, 0xb6d5, 0xb6df, 0xb7e0, 0xb7ea, 0xb7f5, 0xb7ff, 
    0xd800, 0xd80a, 0xd815, 0xd81f, 0xd920, 0xd92a, 0xd935, 0xd93f, 
    0xda40, 0xda4a, 0xda55, 0xda5f, 0xdb60, 0xdb6a, 0xdb75, 0xdb7f, 
    0xdc80, 0xdc8a, 0xdc95, 0xdc9f, 0xdda0, 0xddaa, 0xddb5, 0xddbf, 
    0xdec0, 0xdeca, 0xded5, 0xdedf, 0xdfe0, 0xdfea, 0xdff5, 0xdfff, 
    0xf800, 0xf80a, 0xf815, 0xf81f, 0xf920, 0xf92a, 0xf935, 0xf93f, 
    0xfa40, 0xfa4a, 0xfa55, 0xfa5f, 0xfb60, 0xfb6a, 0xfb75, 0xfb7f, 
    0xfc80, 0xfc8a, 0xfc95, 0xfc9f, 0xfda0, 0xfdaa, 0xfdb5, 0xfdbf, 
    0xfec0, 0xfeca, 0xfed5, 0xfedf, 0xffe0, 0xffea, 0xfff5, 0xffff 
};

The microcontroller code then simply has to:

  • Read a byte from the image
  • Use the byte as an index into the table to read a word from the table
  • Write the word to the display

And repeat for every pixel data byte in the image.

For those who want to generate the table themselves, here’s my little C program:

// Creates a file of 256 entries being a lookup table for RGB332 to RGB565 conversion.
// (c) Frank Van Hooft 2015
// Free software - do as you like with it.

#include 

typedef unsigned int Uint16;

Uint16 ConvertRGB332toRGB565(unsigned char rgb332);

const unsigned char b3to6lookup[8] = { 0, 9, 18, 27, 36, 45, 54, 63 };
const unsigned char b3to5lookup[8] = { 0, 4, 9, 13, 18, 22, 27, 31 };
const unsigned char b2to5lookup[4] = { 0, 10, 21, 31 };


int main()
{
	FILE *fp;
	int i;

	printf("\nGenerating file RGB332to565lookuptable.c... ");

	fp = fopen("RGB332to565lookuptable.c", "w"); 	

	fprintf(fp, "const Uint16 RGB332to565lookupTable[256] = {");

	for (i = 0; i<256; i++) {
		if (!(i % 8)) fprintf(fp, "\n    ");
		fprintf(fp, "0x%.4x, ", ConvertRGB332toRGB565(i));
	}

	fprintf(fp, "\n};\n");

	fclose(fp);

	printf("Finished.\n");
}


Uint16 ConvertRGB332toRGB565(unsigned char rgb332)
{
	Uint16 red, green, blue;

	red = (rgb332 & 0xe0) >> 5;		// rgb332 3 red bits now right justified
	red = (Uint16)b3to5lookup[red];		// 3 bits converted to 5 bits
	red = red << 11;			// red bits now 5 MSB bits

	green = (rgb332 & 0x1c) >> 2;		// rgb332 3 green bits now right justified
	green = (Uint16)b3to6lookup[green];	// 3 bits converted to 6 bits
	green = green << 5;			// green bits now 6 "middle" bits

	blue = rgb332 & 0x03;			// rgb332 2 blue bits are right justified
	blue = (Uint16)b2to5lookup[blue];	// 2 bits converted to 5 bits, right justified

	return (Uint16)(red | green | blue);
}

Notice this conversion table is not optimised for any particular purpose. The resulting lookup table is a "palette conversion" - it's mapping an 8-bit color palette to a 16-bit color palette. Obviously with an 8-bit starting point, only 256 colors from the 65536 possible 16-bit colors can be selected. For a general purpose conversion there is no perfect answer - something like the above is about as good as you can do. The above lookup table attempts to cover the 16-bit RGB565 color space as evenly as possible.

However, if you're doing this conversion for a specific image it can be possible to optimise the palette for it. For example, if your image was mostly shades of red, you could place a lot more "shades of red" 16-bit RGB565 entries into your lookup table (at the expense of green and blue entries). Some early computer games, like Doom, used 8-bit color, and optimised their palettes for the types of images they were displaying. Those who have played Doom know it's lots of shades of brown. There's no end to the tweaking that can be done for specific applications.

One thought on “Fast RGB332 to RGB565 Colorspace Conversion

  1. Todd Cromwell

    minor question:

    Using integer math, does (31r+3)/7=red, (63g+3)/7=green, (31b+1)/3=blue work for this rather than having to spell out the red, green, and blue arrays with literals? (The +3 and so forth are to get the denominator/2 in integer math.) If so, maybe you could remove the arrays and substitute those calcs (for your 3 lines with the cast to Uint16 on them)? Idea would be to scale a 2^n domain to a 2^m destination range.

Leave a Reply

Your email address will not be published. Required fields are marked *