Converting 16 bit color to 32 bit color

Started by
7 comments, last by Zahlman 16 years, 8 months ago
Hi, I'm not 100% sure about the theory behind representing colors in 16 and 32 bits. I'm trying to write a function to convert a 16-bit representation of a color into a 32-bit representation (assuming 16-bit is 565). Is this correct?


int Convert_16_To_32(short icolor)
{
    // icolor is a 16 bit color

    int b = (icolor & 0x1F) >> 11;
    int g = (icolor & 0x7E0) >> 5;
    int r = icolor & F800;

    r *= 256;
    r /= 32;

    g *= 256;
    g /= 32;

    b *= 256;
    b /= 32;

    int color = ((r << 16) | (g << 8) | b);
    color |= 0xFF000000;    // Possibly optional command for completely opaque alpha?

    return(color);
}

Advertisement
Please note that I have not done it in some time, but it does look
correct to me.

I would probably try to add support for both 555 and 565 formats though[smile]
with the 565 format g /= 32 should be g /= 64, since there are 64 shades of green in 565 (2^6).
Gauvir,

That doesnt look correct to me for a few reasons...

First:
int b = (icolor & 0x1F) >> 11;int g = (icolor & 0x7E0) >> 5;int r = icolor & F800;


Appears to be in the wrong order, and the masks are wrong.
You're taking the right most 5 bits, and shifting it 11 bits to the right, this should give you int b = 0;

Also, you're taking the top 5 bits, which should be the 'r' value, and not shifting it at all.

Unless I'm mistaken, you want:

// Notice I'm storing it in an unsigned integer, so as to prevent the
// most significant bit as being interpreted as sign, etc...just cleaner
unsigned r = (icolor & 0xF800) >> 11;
unsigned g = (icolor & 0x07E0) >> 5;
unsigned b = (icolor & 0x001F);

Next, when you're scaling your values from 0-255, you're using 256 instead of 255. Also, you're dividing each by 32. However, none of them (save for green) could have a value of 32, as they were only 5 bits (0-31).

I believe you want...

r = r * 255 / 31;
g = g * 255 / 63;
b = b * 255 / 31;

Give those numbers a try and see if you get what you're expecting.

Cheers!

Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
I'm actually pretty sure that its wrong. The logical layout of a 565 color value is rrrrrggggggbbbbb. So to extract blue, you just mask by 0x001f, green you mask by 0x07e0 and shift right by 5, red you mask by 0xf800 and shift by 11.

with the components extracted, you then need to multiply the value by 255, and then divide by the maximum value that the component can hold;

in the end:

r' = (r * 255) / 31
g' = (g * 255) / 63
b' = (b * 255) / 31


EDIT -- Beaten to the punch. Serves me right for wandering off mid reply [grin]

throw table_exception("(? ???)? ? ???");

Quote:Original post by JWalsh
Next, when you're scaling your values from 0-255, you're using 256 instead of 255. Also, you're dividing each by 32. However, none of them (save for green) could have a value of 32, as they were only 5 bits (0-31).


Actually, this part is ok (it's sort of a design choice), but it can be simplified. The net effect is to multiply the values by 8 (i.e. 256 / 32). GBA graphics actually are like this: the "white" regions of images are actually (248, 248, 248) rather than (255, 255, 255).

Anyway, all that's really happening if you take this approach is to shift right by 3 ;)

To clean up the code, we can make a helper function that handles shifting one "field":

// We'll use template parameters to specify the widths of fields instead of// function parameters. This forces the compiler to do some of the inlining// work, at the expense of some flexibility. That is, with this approach we// have to know ahead of time - i.e., with compile-time constants - what field// widths/positions to use.// In our case, it also makes the calling code a little prettier: the template// parameters get separated visually from the function parameter that holds the// input data to be field-shifted.// Shift bits [input_low, input_high) of input to [output_low, output_high),// left-aligning the value within the output field. Return the resulting value// for just that field (with the shift applied).template <int input_low, int input_high, int output_low, int output_high>int shiftedField(int input) {  // Mask out the field.  input &= (1 << input_high) - (1 << input_low);  // Have to branch here because shifting by a negative amount isn't kosher.  if (output_high > input_high) {    input <<= (output_high - input_high);  } else if (output_high < input_high) {    input >>= (input_high - output_high);  } // otherwise, do nothing ;)  // Clear any bits below output_low, by creating the mask for bits 0 to  // output_low and negating it.  input &= ~((1 << output_low) - 1);}


And then we can express the algorithm in terms of that helper function:

// It's easy to confirm visually that we have the right parameters in the// templates: adjacent "fields" share begin/end points, and the difference// between pairs gives the field widths.int 565toFullColor(short input) {  return 0xff000000 | // alpha         shiftedField<11,16,16,24>(input) | // r         shiftedField< 5,11, 8,16>(input) | // g         shiftedField< 0, 5, 0, 8>(input); // b}int 555toFullColor(short input) {  return 0xff000000 | // alpha         shiftedField<10,15,16,24>(input) | // r         shiftedField< 5,10, 8,16>(input) | // g         shiftedField< 0, 5, 0, 8>(input); // b}// To go the other way, just reverse the field width specifications:short FullColorto565(int input) {  return shiftedField<16,24,11,16>(input) | // r         shiftedField< 8,16, 5,11>(input) | // g         shiftedField< 0, 8, 0, 5>(input); // b}short FullColorto555(int input) {  return shiftedField<16,24,10,15>(input) | // r         shiftedField< 8,16, 5,10>(input) | // g         shiftedField< 0, 8, 0, 5>(input); // b}


(As an exercise, write the equivalent helper function taking function parameters for field widths instead of template parameters, and show how to use it instead. For extra credit, compile and disassemble the results each way, in release mode, and diagnose what your compiler is capable of :)



Some people would argue that I'm writing stuff here that's too advanced for the For Beginners forum. To them, I say that people who are *really* beginners shouldn't be mucking around with anything that requires them to convert between 16 and 32 bit colour values themselves *anyway*. :)

EDIT: Fixed several ||'s that should have been |'s; I must be half-asleep :)
Quote:Original post by Zahlman
Some people would argue that I'm writing stuff here that's too advanced for the For Beginners forum. To them, I say that people who are *really* beginners shouldn't be mucking around with anything that requires them to convert between 16 and 32 bit colour values themselves *anyway*. :)

You're facetious again! [smile]

What about:
Quote:struct pixel16
{
unsigned short red: 5;
unsigned short green: 6;
unsigned short blue:5;
};

Sure, that's not really portable (as the standard doesn't define the size of a short), but most compilers on most 32 bit systems will implement it as a 16 bits value, so that should work.
Conversion is then:
// assume packing of 1struct pixel32{  unsigned char red;  unsigned char green;  unsigned char blue;  unsigned char alpha;};// conversion functorstruct convert_16_to_32{  pixel32 operator()(const pixel16& p16)  {    pixel32 p32;    p32.red = p16.red << 3;    p32.green = p16.green << 2;    p32.blue = p16.blue << 3;    p32.alpha = 255; // opaque    return p32;  }}// and a sample examplestd::vector<pixel16> p16_buffer;// fill the buffer...std::vector<pixel32> p32_buffer(p16_buffer.size());std::transform(p16_buffer.begin(),                p16_buffer.end(),                p32_buffer.begin(),                convert_16_to_32());

Not tested, but should work.
Quote:Original post by Zahlman
EDIT: Fixed several ||'s that should have been |'s; I must be half-asleep :)

Hum.

565toFullColor...

I'd say you're more than half asleep [grin]
Quote:Original post by Emmanuel Deloget
You're facetious again! [smile]


Not reeeeeally. The generalization was intended to be useful... doing it with bitfields is a lot neater, but you need to check all the packing issues and make the shifts line up properly (i.e. do the math yourself for how far to shift red/green/blue, and maintain those values). With my approach I *think* all you need to do is make sure the types have the sizes you expect, and for rigor, use unsigned values (the standard says a lot more about the binary representation of unsigned values than it does about signed ones). And it doesn't require a temporary ;P [1]

As for the function names... I of course wanted to distinguish the two types of 'short' pixel, but of course you can't start the name with a digit, what was I thinking :(

But I'll concede I was quite tired. :)

[1] In the language I'm designing, you wouldn't anyway, because it takes the VB/Pascal approach of implicitly having a temporary for a function's return value with the same name as the function (or at least, behaving as-if).

This topic is closed to new replies.

Advertisement