Why does this work?

Started by
13 comments, last by utwo007 22 years, 2 months ago
Here''s a macro from a book that I''m reading that is supposed to convert three int s (one for red, one for green, and one for blue) into a 16-bit word used to represent a color. I''m sure you''ve all seen this before. #define _RGB16BIT555(r,g,b) ((b%32) + ((g%32) << 5) + ((r%32) << 10)) I don''t understand why this should work. I mean, consider the following: _RGB16BIT555(0,0,33); and... _RGB16BIT555(0,0,65) Both 33%32 and 65%32 = 1. Wouldn''t this yield the same shade of blue, even though the user obviously wanted two different shades? I''m sure the answer to this is obvious and I''m just overloading something. ---signature--- People get ready. I''m ready to play.
---signature---People get ready.I'm ready to play.
Advertisement
Each color component gets 5 bits. You''re example uses numbers that break that 5 bit boundry. You only get 32 shades of each component. (2^5=32)

Thats from Tricks of the Windows game programming gurus!

and yes the maximum value you get on 5 bits is 11111b = 0x1F = 31
on the _RGB16BIT565 macro green gets one more bit so its maximum is 111111b = 0x3f = 63
Yep! I knew someone had to have seen that before.

Anyway, I understand why each number is %32-ed. But I was under the understanding that this sort of macro should be used to convert the standard 256-levels into 32 levels in a logical manner, meaning a value of 32 would be less than 64, which would be less than 96. Using this macro, these would all yield the same exact shade.

Let me put it another way. Using graphics programs like Photoshop, we are all used to representing colors as such:

white = 255,255,255
green = 000,255,000

dark green = 000,064,000
darker green = 000,032,00

You see, the last two examples should yield 2 different shades of green. Using that macro, it wouldn't. If I did the following:

_RGB16BIT555(0,0,32);

I would expect a darker shade of blue than this:

_RGB16BIT(0,0,64);

And this would be the lightest shade of blue possible:

_RGB16BIT555(0,0,255);

And I'd be right about one thing -- the last one would equal the lightest shade possible because 255%32=31, which is the highest you can go on a 32-element scale of 0-31. However, this doesn't work the way it was intended when other values are entered. Why bother with the (%)? Why not just force the user to enter in a value between 0-31? It'd be easier to understand.

I mean, it's be easiest to enter a value between 0-255 because that's what we're all used to, but if the macro doesn't work for this purpose then what's the point? Shouldn't it be written like this?

#define _RGB16BIT555(r,g,b) ((((b+1)/8)-1) + ((((g+1)/8)-1) << 5) + ((((r+1)/8)-1) << 10))

Each color would be given a value between 0 and 31 (255+1=256; 256/8 = 32; 32-1=31; ). Most of this math can be done before the macro is called, and I can even write some simple comparative logic that decided if the value entered is closer to one multiple of 32 or the other.

ARRGH! I don't get it.

---signature---
People get ready.
I'm ready to play.

Edited by - utwo007 on January 29, 2002 9:33:11 PM
---signature---People get ready.I'm ready to play.
Isn''t ''%'' horribly slow, since it needs an idiv ? better do something like b&31. Though it will wrap around instead.
maybe you can change it to:

#define _RGB16BIT555(r,g,b) (((b&0x1f)) + ((g&0x1f) << 5) + ((r&0x1f) << 10))

that way you just feed 0-31 values, and if you feed higher values the bits over 5 wont be taken into account

Edit: got green (g) twice, and no blue (b)

Edited by - kwizatz on January 29, 2002 10:33:31 PM
Well, I can do it this way:

the color is 255,128,0 (orange).

// Macro for creating color USHORT

#define _RGB16555(r,g,b) ((b)+(g << 5)+(r << 10))

// This function assumes the user has already locked the
// surface, obtained a pointer to the video buffer, calculated
// the memory pitch, and passed all this information to it.

int red = 255;
int green = 128;
int blue = 255;

int newred = ((red+1) >> 3) - 1;
int newgreen = ((green+1) >> 3) - 1;
int newblue = ((blue+1) >> 3) - 1;

256/8 = 32. Perfect! Because we''re dealing with 0-255 and not 1-256, we must first add 1 to the color element''s value. Then, we can devide by 8 easily by shifting the number three digits. ( >> 3 will effectively devide by 2^3, or 8). Then, we subtract the 1 back from the result.

Let''s see how this''ll work for our example (orange). We need a red value of 255, the highest allowable. Let''s transulate that to 16-bit color, shall we?

255+1=256
256/8=32
32-1=31

Woohoo! Now lets try 128 for the green element:

128+1=129
129/8=16 //since it''s an int, the remainder is truncated
16-1=15

Damned good!

And blue''s a no-brainer, so we''ll just go ahead and run the macro. Now newred is 31, newgreen is 15, and newblue is 0:

SomePixel = _RGB16555(newred,newgreen,newblue);


Pretty cool, huh? Now I can use the standard 255,255,255 design and convert it to 16bit color information using NO devides, NO modulus, and NO multiplies! I guess I could just get used to a 31,31,31 way of thinking, but I''m too stubborn!

What do you think? It isn''t perfect. Since the result of int >> 3 is truncated, it''s always rounded down. That''s no good, because in reality, the number 128, when converted from 8-bit format to 5-bit format, is actually closer 16 than 15. I could use some simple comparative logic to decide whether a number is closer to one multiple of 8 or another. I dunno.

---signature---
People get ready.
I''m ready to play.
---signature---People get ready.I'm ready to play.
Whoever wrote that macro is horribly misguided; they're trying to implement either a clamp or a scale function using modulo arithmetic, either of which just doesn't work. If you need to clamp the color values to the range 0-32 with no wraparound, you can just use the bitwise and operator, as pointed out earlier.

To really convert between color depths you need to rescale the RGB values to the range 0-32 rather than simply clamping them. Let's say their range is 0-255 (8 bits per color component). Assuming unsigned values for r, g, b, a working implementation of the macro might look like this (it isn't optimal, but it's good enough unless you're doing some serious format conversions every single frame):

#define SCALE( val, maxNew ) (int)((float)(val) / 256.0f * (maxNew))
#define _RGB16BIT555(r,g,b) (SCALE( b, 32 ) | (SCALE( g, 32 ) << 5) | (SCALE( r, 32 ) << 10))

Note this also fixes another problem with the original: the parameters within a macro definition must be parenthesized in order to ensure you get intended results in all cases.

If this macro actually was from a Tricks of the [etc..] Gurus book, I'm not surprised. I was very disappointed with the quality of code in both those books.


--
Eric

Edited by - ekenslow on January 30, 2002 1:40:40 AM
-- Eric
utwo007, why don't you just leave the number alone before you divide by 8? Why add one and then subtract one? If you left it, then

255 >> 3

should yield 31 (since its below 256, it'd be rounded down), and

128 >> 3

would yield the more accurate 16.

Just a thought.

[EDIT]
I just thought of another problem with what you proposed. What happens when you choose the color 0, or 1?

1+1=2
2/8=0 (an int gets truncated)
0-1=-1

-1 is not a valid color I'm afraid. My slightly revised way should take care of this.
[/EDIT]

--Buzzy

Edited by - buzzy_b on January 30, 2002 2:04:41 AM
I''m reading the same book. The macro works...but I have no idea what''s happening. Im not to good with the whole macro and bit operation thing. Does anyone know of a good tutorial to help me out?

This topic is closed to new replies.

Advertisement