Archived

This topic is now archived and is closed to further replies.

Why does this work?

This topic is 5794 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Isn''t ''%'' horribly slow, since it needs an idiv ? better do something like b&31. Though it will wrap around instead.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
either the author was high when he wrote the book or made some serious typos which the editor missed. NEVER use plus in anything that is combining bits like this. it is messy that way. also never use the AND operator to clamp values when you are supposed to scale them. finnally NEVER use floating point in anything that is realtime and involves combining color componnents into pixels.

#define _RGB16BIT555(r,g,b) ( (r&0xF8) << 8)) | ((g<<5)&0x3E0) | (b>>3) )

more efficent then previous macros, though you should be able to come up with this from the info given. make sure all the inputs are longs or at least shorts or things wont work. ints are fine as well (for those not in the know, (ints can be 16bit or 32bit depending on the compiler, most nowadays have ints as 32bit).

Share this post


Link to post
Share on other sites
Thanks for the suggestiongs, folks. I knew something was fishy when I saw that. I''ve actually been please with LaMothe''s code so far. I''ve had to correct a few things that wouldn''t compile, but I figured that had more to do with my compiler than anything else (I use Dev-C++. I think the compiler begins with ''M''. hehe).

I''ll fix the macro so that is scales the colors properly. I don''t know what Lamothe was thinking.

Oh, and good point, buzzy_b. 0 and 1 would really mess things up.

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

Share this post


Link to post
Share on other sites
''a person'', you must be smoking the same thing as LaMothe, because your macro is actually more broken than the original.

The mask values and bit shifts are all wrong, and you managed to break the rule that YOU mentioned about using AND to scale values.

As for doing floating point to scale the values, there''s really no other way to do it. You just have to bite the bullet and do the scaling, hopefully caching the result to be reused for several frames. Most applications don''t require explicit format conversion on a frame by frame basis anyway (most only need it at load time if at all).


--
Eric

Share this post


Link to post
Share on other sites
I think I''m just going to stick with 0-31, actually. Writing a bunch of code to scale the numbers when I could just get used to 0-31 is plain lazy, and I''d rather have the speed that comes with not having to use the FPU and doing the multiplies and pushing more variables on the stack.

I mean, scaling would be hella useful if I were writing a graphics library for someone else, but I''ll just be using this myself, so...

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

Share this post


Link to post
Share on other sites