Monday, June 6, 2011

Pushing Pixels

At some point, many of us end up writing routines that will make changes to a picture. You might need to rotate the image, change the color scheme, etc. You can use the Graphics.Pixel property but Real Studio offers another, faster way: the RGBSurface class. This class has a Pixel method that works just like Graphics.Pixel, but RGBSurface is significantly faster than using Graphics.Pixel! In my test, RGBSurface averaged 14 times faster on the Mac and about 70 times faster on Windows. That means of course that Graphics.Pixel is pretty slow on Windows since RGBSurface on the Mac and on Windows are roughly the same.

You can change pixels using the RGBSurface class using almost identical code to what you'd write when using Graphics.Pixel. For example, my test code simply copies an entire picture pixel by pixel.

Here's the code using Graphics.Pixel:

dim copy as new Picture(beach.width, beach.height, 32)
dim beachGraphics as Graphics = beach.Graphics
dim copyGraphics as Graphics = copy.Graphics
dim lastX as Integer = g.Width - 1
dim lastY as Integer = g.Height - 1
for x as Integer = 0 to lastX
for y = 0 to lastY
copyGraphics.Pixel(x, y) = beachGraphics.pixel(x, y)

And here's the same code using the RGBSurface class:

dim copy as new Picture(beach.width, beach.height, 32)
dim sr as RGBSurface = beach.RGBSurface
dim cr as RGBSurface = copy.RGBSurface
dim lastX as Integer = beach.Width - 1
dim lastY as Integer = beach.Height - 1
for x as Integer = 0 to lastX
for y as Integer = 0 to lastY
cr.Pixel(x, y) = sr.pixel(x, y)

The only big difference is that you just need to get an RGBSurface object for the source and the destination and then copy pixels between them.

So when you need to do pixel-level changes, make sure you are using RGBSurface!

EDIT: Charles Yeomans found an optimization for both examples. It didn't speed up the first example but it made a big difference for the RGBSurface example making it even faster.


Mathias said...

wut maybe a stupid question: Why does graphics.pixel even exists?
Why don't you guys make graphics.pixel an alias for RGBSurface?

Geoff Perlman said...

@ Mathias - RGBSurface was added many years after Graphics.Pixel. An alias wouldn't do it because you need to get the RGBSurface object and then access it over and over. If you made it an alias you'd have the overhead of creating an RGBSurface document each time you accessed a pixel.

Ricounet said...

Just out of curiosity, what was Charles' optimization?

Anthony said...
This comment has been removed by the author.
Anthony said...

What about with some Pragmas tossed in there? Noticeable difference?

Michael said...

What about for drawing lines?

Is it still quicker to use the graphics methods or should we do it directly with a RGB surface?

Geoff Perlman said...

@ Ricounet - He copied the graphics object from the source object into a variable rather than accessing the original every time. This in fact did not speed up the Graphics.Pixel version but it greatly improved the RGBSurface version. For example, in my Mac tests, the original graphics.pixel version took 46 ticks. With the update, it took 45 ticks. But the RGBSurface version went from 3 ticks to 2 or even 1 in some cases.

Geoff Perlman said...

@ Anthony - What pragmas do you suggest we use in this example? Turning off background tasks, that sort of thing?

Geoff Perlman said...

@ Michael - I haven't tested drawing lines with graphics versus using RGBSurface. Give it a try and let us know.

sam said...

It's even faster if you change the For commands to read like so:

Dim x, y as integer
for x = 0 to lastX
for y = 0 to lastY

sam said...

If you also add pragmas to the previous code I posted, it runs in half the time in my test with a 9 megapixel image.

99 ticks for the previous version.
67 ticks with my For modification.
47 ticks with my For mod and pragmas.

I added the following pragmas to top of the function.
#pragma DisableBackgroundTasks
#pragma StackOverflowChecking false
#pragma NilObjectChecking false
#pragma BoundsChecking false

Michael said...

I presume its faster because you are not re-initialising the y variable inside the for x loop?