Manually converting between different bpp depths?

Hi there.

I see that SDL does a very good job converting and using different
depths but I wanted to learn to do that myself and the way SDL does
isn’t very clear to me.
Actually, I understand how, for instance, I have a 24bpp RGB data and
want to make a 16bpp (565) but what about the other way around? How can
I calculate by hand, if I have a 16bpp data and want a 24 bpp which is
visually equivallent?
I tought about something like:
RGB32 = ((Red16 & 0xf800) << 8) | ((Green16 & 0x07e0) << 5) | ((Blue16 &
0x001f) << 3)
This kinda works but some colors are not quite correct.
Any ideas?

Thanks.

Adilson.

Adilson Oliveira wrote:

How can I calculate by hand, if I have a 16bpp data and want a 24 bpp
which is visually equivallent?
I tought about something like:
RGB32 = ((Red16 & 0xf800) << 8) | ((Green16 & 0x07e0) << 5) | ((Blue16 &
0x001f) << 3)
This kinda works but some colors are not quite correct.

E.g. for the blue component, you have to convert from the range 0…31 to
the range 0…255. That’s done by multiplying by 255/31 = 8.226, not by
multiplying by 8 (i.e. shifting by 3 bits). The same for the other
components, except that it’s 0…63 for green.

Just try inserting white (0b1111111111111111) into your formula: you
will get 0b111110001111110011111000, while what you should get is
0b111111111111111111111111.

-Christian

Christian Walther wrote:

Adilson Oliveira wrote:

How can I calculate by hand, if I have a 16bpp data and want a 24 bpp
which is visually equivallent?
I tought about something like:
RGB32 = ((Red16 & 0xf800) << 8) | ((Green16 & 0x07e0) << 5) | ((Blue16
& 0x001f) << 3)
This kinda works but some colors are not quite correct.

E.g. for the blue component, you have to convert from the range 0…31 to
the range 0…255. That’s done by multiplying by 255/31 = 8.226, not by
multiplying by 8 (i.e. shifting by 3 bits). The same for the other
components, except that it’s 0…63 for green.

Just try inserting white (0b1111111111111111) into your formula: you
will get 0b111110001111110011111000, while what you should get is
0b111111111111111111111111.

You’re absolutely right. Now you see what causes lot’s of problems and
lack of sleep :slight_smile:
Now I’ll think in a more efficient way to make this without having to
rely on real number calculations. Perhaps, I should have some sleep first :wink:

Thanks.

Adilson.

Hi,

Selon Christian Walther :

Adilson Oliveira wrote:

How can I calculate by hand, if I have a 16bpp data and want a 24 bpp
which is visually equivallent?
I tought about something like:
RGB32 = ((Red16 & 0xf800) << 8) | ((Green16 & 0x07e0) << 5) | ((Blue16 &
0x001f) << 3)
This kinda works but some colors are not quite correct.
E.g. for the blue component, you have to convert from the range 0…31 to
the range 0…255. That’s done by multiplying by 255/31 = 8.226, not by
multiplying by 8 (i.e. shifting by 3 bits). The same for the other
components, except that it’s 0…63 for green.

Just try inserting white (0b1111111111111111) into your formula: you
will get 0b111110001111110011111000, while what you should get is
0b111111111111111111111111.

You can achieve this using only integers :

R5 = RGB16 & 0xF800;
G6 = RGB16 & 0x07E0;
B5 = RGB16 & 0x001F;
R8 = (R5 >> 8) | (R5 >> 13);
G8 = (G6 >> 3) | (G6 >> 9);
B8 = (B5 << 3) | (B5 >> 2);
RGB24 = (R8 << 16) | (G8 << 8) | B8;

This way you get for each possible value of B5 :
0b00000 : 0b00000000
0b00001 : 0b00001000
0b00010 : 0b00010000
0b00011 : 0b00011000
0b00100 : 0b00100001
0b00101 : 0b00101001
0b00110 : 0b00110001
0b00111 : 0b00111001
0b01000 : 0b01000010
0b01001 : 0b01001010
0b01010 : 0b01010010
0b01011 : 0b01011010
0b01100 : 0b01100011
0b01101 : 0b01101011
0b01110 : 0b01110011
0b01111 : 0b01111011
0b10000 : 0b10000100
0b10001 : 0b10001100
0b10010 : 0b10010100
0b10011 : 0b10011100
0b10100 : 0b10100101
0b10101 : 0b10101101
0b10110 : 0b10110101
0b10111 : 0b10111101
0b11000 : 0b11000110
0b11001 : 0b11001110
0b11010 : 0b11010110
0b11011 : 0b11011110
0b11100 : 0b11100111
0b11101 : 0b11101111
0b11110 : 0b11110111
0b11111 : 0b11111111

As you can see, it’s just copying the 3 most significant bits of B5 in the 3
least significant bits of B8.

Regards,

Xavier

Xavier Joubert wrote:

E.g. for the blue component, you have to convert from the range 0…31 to
the range 0…255. That’s done by multiplying by 255/31 = 8.226, not by
multiplying by 8 (i.e. shifting by 3 bits). The same for the other
components, except that it’s 0…63 for green.

Just try inserting white (0b1111111111111111) into your formula: you
will get 0b111110001111110011111000, while what you should get is
0b111111111111111111111111.

You can achieve this using only integers :

R5 = RGB16 & 0xF800;
G6 = RGB16 & 0x07E0;
B5 = RGB16 & 0x001F;
R8 = (R5 >> 8) | (R5 >> 13);
G8 = (G6 >> 3) | (G6 >> 9);
B8 = (B5 << 3) | (B5 >> 2);
RGB24 = (R8 << 16) | (G8 << 8) | B8;

This way you get for each possible value of B5 :
0b00000 : 0b00000000
0b00001 : 0b00001000

0b11110 : 0b11110111
0b11111 : 0b11111111

As you can see, it’s just copying the 3 most significant bits of B5 in the 3
least significant bits of B8.

Clever idea. It doesn’t always round to the nearest 8-bit value though -
it’s off by 1 in 4 of the 32 cases. But the more efficient
implementation may well be worth it.

I’d have done it using integer multiplication and division: B8 = (B5*255

  • 15)/31. (The +15 is there to turn the integer division’s "round down"
    behavior into a “round to nearest”.)

    -Christian

Xavier Joubert wrote:

Hi,

Selon Christian Walther :

Adilson Oliveira wrote:

How can I calculate by hand, if I have a 16bpp data and want a 24 bpp
which is visually equivallent?
I tought about something like:
RGB32 = ((Red16 & 0xf800) << 8) | ((Green16 & 0x07e0) << 5) | ((Blue16 &
0x001f) << 3)
This kinda works but some colors are not quite correct.

E.g. for the blue component, you have to convert from the range 0…31 to
the range 0…255. That’s done by multiplying by 255/31 = 8.226, not by
multiplying by 8 (i.e. shifting by 3 bits). The same for the other
components, except that it’s 0…63 for green.

Just try inserting white (0b1111111111111111) into your formula: you
will get 0b111110001111110011111000, while what you should get is
0b111111111111111111111111.

You can achieve this using only integers :

R5 = RGB16 & 0xF800;
G6 = RGB16 & 0x07E0;
B5 = RGB16 & 0x001F;
R8 = (R5 >> 8) | (R5 >> 13);
G8 = (G6 >> 3) | (G6 >> 9);
B8 = (B5 << 3) | (B5 >> 2);
RGB24 = (R8 << 16) | (G8 << 8) | B8;

Thanks for the tip.

Actually I already cooked up something quite similar and packed into one
line of code only, like this:

*out = ((((*data16 >> 8) & 0xf8) | ((*data16 >> 13) & 0x07)) << 16) |
((((*data16 >> 3) & 0xfc) | ((*data16 >> 9) & 0x03)) << 8) |
(((*data16 << 3) & 0xf8) | ((*data16 >> 2) & 0x07));

[]s

Adilson.

Hi,

Selon Christian Walther :

Clever idea. It doesn’t always round to the nearest 8-bit value though -
it’s off by 1 in 4 of the 32 cases. But the more efficient
implementation may well be worth it.

Not on a modern CPU, where dividing has almost the same cost as shifting. The
bottleneck would be the memory on such system.

But this may make a difference where dividing is very expensive (old CPU,
embedded system…).

Regards,

Xavier