Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Darking pixels...

Credits go to CGamesPlay, Jonny Cook, Thomas Harte, Tobias Dammers, and vpenquerch for helping out!
This thread is locked; no one can reply to it. rss feed Print
Darking pixels...
ngiacomelli
Member #5,114
October 2004

I'm using a Mode7 technique and have decided that I'd like to darken pixels that appear far away from the camera. I was just wondering how to do this? I first use getpixel to grab the pixel colour. So I need some code to go between getpixel and putpixel that will darken the pixel dependant upon distance!

CGamesPlay
Member #2,559
July 2002
avatar

To darken the pixel, you might try:

int c = original_value;
float h, s, v;
// Convert to HSV color coordinates
rgb_to_hsv(getr(c), getg(c), getb(c), &h, &s, &v);
// Distance is 0 at the bottom of the screen, 1 at the horizon
float distance = (float) (SCREEN_H - my_y) / (SCREEN_H - horizon_y);
// Scale the value (distance from black) down proportional to the distance
v *= 1 - distance;
// Convert back to RGB coordinates
int r, g, b;
hsv_to_rgb(h, s, v, &r, &g, &b);
int new_color = makcol(r, g, b);

Now, this method will product pretty results where horizon pixels are black and close pixels are full color. You can make it so horizon pixels are brighter by scaling distance. Try distance *= .7 before we scale the value v.

Of course, this method is slow. If it's too slow, you can just multiply r, g, and b by distance to accomplish a (I believe) similar effect.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Thomas Harte
Member #33
April 2000
avatar

Obviously I need to first suggest that for speed reasons you don't use getpixel or putpixel anywhere that you are doing significant graphics work but instead check out the "Direct access to video memory" section of the manual and apply various methods there. Don't worry - they all work on memory bitmaps too.

However to directly answer your question - Allegro will help you through its support for conversions between HSV and RGB colour spaces.

Get the r, g and b components for the pixel you want to darken. Use Allegro function rgb_to_hsv to get h, s and v components for your colour. The v component is "brightness" and in the Allegro world is a floating point number ranging from 0 to 1. Multiply it by an amount chosen to reflect the darkening you want to perform on the line. Then put the h, s and v into hsv_to_rgb and use the result to plot your new pixel.

As for deciding how far to darken based on depth, many people use "linear fog". That means that the brightness at z = n is twice that at z = 2n. So, you could divide your v by z.

EDIT: just beaten on the HSV front, I see! Incidentally, if you have a fixed camera height and rotation towards the ground you can just create a single bitmap that contains darkened scanlines and blit that transparently after normal floor drawing. In a roundabout fashion this is what SNES games do.

vpenquerch
Member #233
April 2000

The HSV stuff can be precalculated, and stored in a table.
After working out the distance, you transform it into an integer range
(say, 0-255) and you lookup each of R, G and B into it, then you
reform the triplet.
You ought to have one version of this per color depth, to avoid the
constant tests against the color depth in the inner loop.

ngiacomelli
Member #5,114
October 2004

Some fantastic reponses! Thank you all.

Quote:

Obviously I need to first suggest that for speed reasons you don't use getpixel or putpixel anywhere that you are doing significant graphics work but instead check out the "Direct access to video memory" section of the manual and apply various methods there. Don't worry - they all work on memory bitmaps too.

Well, I've never tried anything like that before. I found some nice code for a faster putpixel in the manual. I wrote a new getpixel using the putpixel example. It seems to work correctly. But if I've done something terribly wrong... please tell me :)

void fast_putpixel(BITMAP *bmp, int x, int y, int color)
{
      ((long *)bmp->line[y])[x] = color;
}

int fast_getpixel(BITMAP *bmp, int x, int y)
{
      return ((long *)bmp->line[y])[x];
}

I wont give cookies until I've tried the distance shading out! :)

Jonny Cook
Member #4,055
November 2003

If you try to acces a pixel outside of the bitmap with those functions the world might explode. Please be careful (for all our sakes).

The face of a child can say it all, especially the mouth part of the face.

Tobias Dammers
Member #2,604
August 2002
avatar

And if you need RIDICULOUSLY FAST code, you can use subtraction instead of multiplication (it's not correct, but looks OK and it's a lot faster), and pre-calculate the values into a 1-dimensional table based on scanline.
Or if you have a few cpu cycles to spare, you can interpolate between the texel color and a fog color instead of black (again, you can use a scanline-based precalc table). This way, you can easily create different moods for different scenes (IIRC, F-Zero did this).

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

ngiacomelli
Member #5,114
October 2004

Everything worked fine :) Except... I just noticed that the title for this thread should be: Darkening pixels... ::)

Neil Walker
Member #210
April 2000
avatar

You could always make a hint to the compiler by making those two functions inline. Or if you prefer the ugly horrible way, make them macros.

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

Kitty Cat
Member #2,815
October 2002
avatar

For 32-bit images:

int c1 = _getpixel32(image, x, y);
int c2 = (c1&0xFF00FF) * a / 255;
c1 = (c1&0xFF00FF00) * a / 255;
_putpixel32(image, x, y, (c1&0xFF00FF00) | (c2&0xFF00FF));

Where 'a' is the amount to darken between 0 and 255.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Tobias Dammers
Member #2,604
August 2002
avatar

Nice trick KC!
Here's one for 16 bpp (565), untested though:

// a should be in the range 0...64
int c = _getpixel16(image, x, y);
register int cr = c & 31;
register int cg = (c >> 5) & 63;
register int cb = (c >> 11) & 31;
c = ((cr | (cb << 10) | (cg << 20)) * a) >> 6;
cr = c & 31;
cb = (c >> 10) & 31;
cg = (c >> 20) & 63;
putpixel16(image, x, y, cr | (cg << 5) | (cb << 11));

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Kitty Cat
Member #2,815
October 2002
avatar

Actually..
c1 = (c1&0xFF00FF00) * a / 255;just realized that'll break if the top byte isn't alpha (the multiply will overflow the value). However, you should be able to do this:
c1 = (c1&0xFF00FF00) / 255 * a;

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Neil Walker
Member #210
April 2000
avatar

Hello,
Could you explain for simpletons what exactly is going on with the colours in those binary operations. I think you're getting separately the highest red/blue and green values then I lose it after that :)

Neil.
MAME Cabinet Blog / AXL LIBRARY (a games framework) / AXL Documentation and Tutorial

wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie

nonnus29
Member #2,606
August 2002
avatar

KittyCat, that's awesome! You just saved me 4 lines of code in my 2.7kb software renderer!

;D

edit; Check out this link for the rgb color theory. Kitty cat's code adds with saturation (ie multiply by x, if greater than 255, then = 255 or the opposite for 0). Very cool.

Tobias Dammers
Member #2,604
August 2002
avatar

The trick is that you create 32-bit integer values that contain color components with enough zeroes between them. When you multiply, the zeroes will be filled with the overflow from the multiplication. Then we divide (or R-shift) back and filter the result. Observe:

a = 0x12
b = 0x34
c = 0x1234 // this is a packed version with the components a and b.
// my code rearranges it to be something similar to this:
c1 = 0x120034
// Then we can multiply it by 256 for example:
cx256 = 0x12003400
/* Note that the zeroes in c1 are now filled, but that the result from b*256 does not interfere with that from a*256. In fact, we have now executed 2 multiplications with only 1 mul statement.*/
c3 = (cx256 & 0xFF00FF00) / 256
c3 = 0x120034 // nothing has changed, since we multiplied and then divided by 256.

By doing several multiplications in one statement, we can get away with one mul operator. Since multiplication and division are very expensive operations compared to shifts and binary operators, this can save a lot of cpu time.

My code rearranges the components and adds zeroes between them, so I get away with a single mul. KC's code decomposes the color int into two values, using 2 muls instead of 3. The downside of my code is that it works only for 16-bit colors, while KC's version is for 32-bit.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

GullRaDriel
Member #3,861
September 2003
avatar

Nice tricks you gave.
How is it possible that nobody have already make a collection of them for each color depth and make an add for allegro ?

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

CGamesPlay
Member #2,559
July 2002
avatar

An add-on for Allegro would defeat the purpose of these low-level optimizations :P And we have a forum for exchanging cool tricks!

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Thomas Harte
Member #33
April 2000
avatar

Quote:

Since multiplication and division are very expensive operations compared to shifts and binary operators

They're not that expensive. I don't think any modern CPU charges more than twice as much for a multiply as a shift, and many charge the same.

vpenquerch
Member #233
April 2000

> They're not that expensive. I don't think any modern CPU charges more than twice
> as much for a multiply as a shift, and many charge the same.

When I was doing asm stuff, divides were above 100 cycles.... :)
Was fun stuff to write though.

ngiacomelli
Member #5,114
October 2004

Quote:

You could always make a hint to the compiler by making those two functions inline. Or if you prefer the ugly horrible way, make them macros.

Done!

I do have a question about making look-up tables for getr, getg, and getb. What's the easiest method?

Go to: