RGB565
RGB888
color conversion
16-bit to 24-bit
image processing

How does one convert 16-bit RGB565 to 24-bit RGB888?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

RGB565 stores red, green, and blue in 16 bits total, while RGB888 stores each channel in a full 8 bits. Converting from RGB565 to RGB888 means extracting the 5-bit or 6-bit channels, then expanding them to 8-bit values so they can be used in a 24-bit pixel format.

Understand the Bit Layout

RGB565 packs the channels like this:

  • red: 5 bits
  • green: 6 bits
  • blue: 5 bits

The bit layout inside one 16-bit pixel is:

rrrrrggggggbbbbb

RGB888, by contrast, stores one byte for each channel:

rrrrrrrr gggggggg bbbbbbbb

So the job is not just copying bits. You must separate the source channels and scale them into the wider target range.

Extract the RGB565 Channels

Start by masking and shifting:

c
1#include <stdint.h>
2
3uint8_t extract_r5(uint16_t pixel) {
4    return (pixel >> 11) & 0x1F;
5}
6
7uint8_t extract_g6(uint16_t pixel) {
8    return (pixel >> 5) & 0x3F;
9}
10
11uint8_t extract_b5(uint16_t pixel) {
12    return pixel & 0x1F;
13}

After extraction, the values are still in the smaller 0..31 or 0..63 ranges. They are not yet valid 8-bit channels.

Expand to 8 Bits

The common fast approximation is to shift left and copy the top bits into the low end:

c
1uint8_t expand_5_to_8(uint8_t value) {
2    return (value << 3) | (value >> 2);
3}
4
5uint8_t expand_6_to_8(uint8_t value) {
6    return (value << 2) | (value >> 4);
7}

Why this works:

  • a 5-bit value becomes approximately value * 255 / 31
  • a 6-bit value becomes approximately value * 255 / 63

The bit-replication trick is fast and visually close to exact scaling, which is why it is common in graphics code and embedded systems.

Full Conversion Function

c
1#include <stdint.h>
2
3void rgb565_to_rgb888(uint16_t pixel, uint8_t *r, uint8_t *g, uint8_t *b) {
4    uint8_t r5 = (pixel >> 11) & 0x1F;
5    uint8_t g6 = (pixel >> 5) & 0x3F;
6    uint8_t b5 = pixel & 0x1F;
7
8    *r = (r5 << 3) | (r5 >> 2);
9    *g = (g6 << 2) | (g6 >> 4);
10    *b = (b5 << 3) | (b5 >> 2);
11}

If you need one packed 24-bit integer instead of three separate bytes, combine the expanded channels:

c
1uint32_t rgb565_to_rgb888_packed(uint16_t pixel) {
2    uint8_t r, g, b;
3    rgb565_to_rgb888(pixel, &r, &g, &b);
4    return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
5}

Exact Scaling Versus Fast Approximation

If accuracy matters more than speed, you can scale mathematically:

c
1uint8_t exact_5_to_8(uint8_t value) {
2    return (uint8_t)((value * 255) / 31);
3}
4
5uint8_t exact_6_to_8(uint8_t value) {
6    return (uint8_t)((value * 255) / 63);
7}

The exact formula is a little slower, but it makes the mapping explicit. In many systems, the replicated-bit method is preferred because it is cheap and the visual difference is usually negligible.

Byte Order Is a Separate Issue

A common source of bugs is confusing color format with byte order. RGB565 describes how bits are assigned to channels. Endianness describes how the two bytes of the 16-bit value are stored in memory or received on a wire.

If the source bytes arrive in the wrong order, fix that first:

c
uint16_t pixel = ((uint16_t)high_byte << 8) | low_byte;

Only after reconstructing the correct 16-bit value should you extract the channel fields.

Common Pitfalls

  • Treating RGB565 as if each color already had 8 bits leads to washed-out or distorted colors.
  • Forgetting that green uses 6 bits, not 5, produces visibly wrong green values.
  • Confusing endianness with channel layout can make a correct conversion formula still produce the wrong color.
  • Using only left shifts without filling lower bits creates a darker, less accurate 8-bit result.
  • Packing into a 24-bit integer without casting to uint32_t first can cause accidental overflow in C.

Summary

  • RGB565 stores 5/6/5 bits for red, green, and blue.
  • Convert by masking and shifting out each channel, then expanding to 8 bits.
  • The common fast expansion uses bit replication such as (value << 3) | (value >> 2).
  • Check byte order separately, because endianness problems can look like color-conversion bugs.

Course illustration
Course illustration

All Rights Reserved.