• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GameMaker How to get the color of a specific pixel from a sprite?

  • Thread starter SayBaconOneMoreTime
  • Start date
S

SayBaconOneMoreTime

Guest
Trying to make a better implementation of a Z axis in my top down game than just slapping down a million collision regions that set the object's Z height whenever they land on them. I decided a pretty efficient way of doing it would be to make a sprite the size of the room with pixels that can be interpreted by code as a number (i.e., every bit of red in the RGB value would add one to the number, every green would add 10, so forth and so on). The problem with this idea is, I can't find any function that lets me get the color of a pixel at (x,y) on a sprite. Is there something I'm missing here?
 

Binsk

Member
You can't do it directly. There are get color commands for the screen and surfaces (both very slow) or you could pop the image into a buffer and read it from there (very fast).

I would suggest this:
  1. Beginning of the room, create a surface the size of the sprite and draw the sprite to it.
  2. Push the surface into a buffer
  3. Delete the surface
Whenever you need a value you can read it from the buffer.
 
S

SayBaconOneMoreTime

Guest
You can't do it directly. There are get color commands for the screen and surfaces (both very slow) or you could pop the image into a buffer and read it from there (very fast).

I would suggest this:
  1. Beginning of the room, create a surface the size of the sprite and draw the sprite to it.
  2. Push the surface into a buffer
  3. Delete the surface
Whenever you need a value you can read it from the buffer.
Alright, I'll look into buffers, I figured I would have to do something along those lines. 20-30 instances tops reading from that every step shouldn't be big deal performance wise right?
 

Binsk

Member
No, that should be fine. Buffers are extremely fast.

The slow part is getting things into the buffer but you only have to do that once and it still should be in the blink of an eye.
 
S

SayBaconOneMoreTime

Guest
No, that should be fine. Buffers are extremely fast.

The slow part is getting things into the buffer but you only have to do that once and it still should be in the blink of an eye.
I will only need one of these per room, and the sprite will already be defined, so my thought is, I can just make all these buffers the first time someone runs the game, save them, and call on the one I need for the specific room whenever I need it. Now I gotta figure out how to read from whatever I get from buffer_get_surface().
 

Binsk

Member
That sounds like a good plan. As for reading the data, it's not too bad. Takes a little math.

The colors are stored in BGRA format in the buffer, all as u8 values (0..255). If you have a 128x128 image then to get each value the offset would be something like:
B = (x + 128 * y) * 4
G = (x + 128 * y) * 4 + 1
R = (x + 128 * y) * 4 + 2
A = (x + 128 * y) * 4 + 3
 
S

SayBaconOneMoreTime

Guest
That sounds like a good plan. As for reading the data, it's not too bad. Takes a little math.

The colors are stored in BGRA format in the buffer, all as u8 values (0..255). If you have a 128x128 image then to get each value the offset would be something like:
B = (x + 128 * y) * 4
G = (x + 128 * y) * 4 + 1
R = (x + 128 * y) * 4 + 2
A = (x + 128 * y) * 4 + 3
Thanks pal! I know it's a lot to ask, but could you give me an explanation of how you came to that math? I hate to put things in my game and not know how they work lest they break.

EDIT: I had to swap the maths for the R and B channels but apart from that this works perfectly! One question in addition to how you came to that math: how would you handle non-square images, i.e. 64x128 or something like that? After I get that figured out I should be able to start actually implementing this. Thanks again!
 
Last edited by a moderator:

Binsk

Member
Really? I could have sworn that copying to the buffer formats in BGRA instead of RGBA. Hum...
Rectangular images should work fine. The 128 * y should just have the 128 changed to whatever the width of the image is.

As for the math, it's just a matter of translating a 3D grid of values [x, y, chanel] into a 1D array of values. The function essentially "reads" the color data from your surface like you would a book. It starts at the top left pixel and reads each value left to right. Once it gets to the end it jumps down a row and repeats. This is how it stores it in the buffer. So a 2x2 image,
P1 P2
P3 P4

Would be formatted like this:
P1b, P1g, P1r, P1a, P2b, P2g, P2r, P2a, P3b, P3g, ..., P4a

If you have a 128xN image then your first 128 values will be the top row of colors (so all those have a y value of 0 while the x value changes). The next row down would be values [129..256] and so on (where the y value is 1 and, again, the x value changes). This means for every change in y you need to jump 128 values (the image width). However, for every change in x you only need to change 1 value. That is why we get the (x + 128 * y) portion.

Do the math, if you wanted y = 1 and x = 2 then we would skip the entire first row of pixels (because those are all y = 0) so we have 128 * 1 = 128. Then we want to read two values down that row so (2 + 128 * 1) = 130.

Lastly, we multiply everything by 4 because there are actually 4 values per pixel instead of 1 (since you have 4 color channels). It's the same concept as with y. Doing the above math finds the position of the first chanel. You just add 1 to get the 2nd chanel, 2 for the 3rd, etc.

I'm kinda bad at explaining this, but I hope that helped.
 
S

SayBaconOneMoreTime

Guest
Really? I could have sworn that copying to the buffer formats in BGRA instead of RGBA. Hum...
Rectangular images should work fine. The 128 * y should just have the 128 changed to whatever the width of the image is.

As for the math, it's just a matter of translating a 3D grid of values [x, y, chanel] into a 1D array of values. The function essentially "reads" the color data from your surface like you would a book. It starts at the top left pixel and reads each value left to right. Once it gets to the end it jumps down a row and repeats. This is how it stores it in the buffer. So a 2x2 image,
P1 P2
P3 P4

Would be formatted like this:
P1b, P1g, P1r, P1a, P2b, P2g, P2r, P2a, P3b, P3g, ..., P4a

If you have a 128xN image then your first 128 values will be the top row of colors (so all those have a y value of 0 while the x value changes). The next row down would be values [129..256] and so on (where the y value is 1 and, again, the x value changes). This means for every change in y you need to jump 128 values (the image width). However, for every change in x you only need to change 1 value. That is why we get the (x + 128 * y) portion.

Do the math, if you wanted y = 1 and x = 2 then we would skip the entire first row of pixels (because those are all y = 0) so we have 128 * 1 = 128. Then we want to read two values down that row so (2 + 128 * 1) = 130.

Lastly, we multiply everything by 4 because there are actually 4 values per pixel instead of 1 (since you have 4 color channels). It's the same concept as with y. Doing the above math finds the position of the first chanel. You just add 1 to get the 2nd chanel, 2 for the 3rd, etc.

I'm kinda bad at explaining this, but I hope that helped.
Ahhh ok, it makes sense now. Thanks a lot for the help man!
 
Top