Never get a single pixel when you can get a whole line

MAXScript FAQ > How To Make It Faster > Never get a single pixel when you can get a whole line

The getPixels and setPixels functions for reading and writing pixels from / to bitmaps are rather slow.

When changing a large number or even all pixels of a bitmap, it is a good idea to perform the getPixels() and setPixels() just once for each horizontal line and do the rest of the work with the resulting array elements.

In the following examples, a 1000x1000 pixels bitmap should be altered by changing every single pixel. This means reading and writing back one million pixels.

In the first example, every single pixel will be read separately, multiplied by a number and written back. We will stop the time in order to compare to the optimized version.

EXAMPLE 1 - UNOPTIMIZED SCRIPT:

b = bitmap 1000 1000 color:red--create new bitmap with red background
st = timestamp()--get start time in milliseconds
for y = 1 to 1000 do--go through all lines in the bitmap
(
  for x = 1 to 1000 do--go through all pixels in a line
  (
    pixels = getPixels b [x-1,y-1] 1--read one pixel from X,Y
    pixels[1] *= (x+y)/2000.0--alter the pixel
    setPixels b [x-1,y-1] pixels--write modified pixel back
  )--end x loop
)--end y loop
et = timestamp()--get end time in milliseconds
print (et-st)--print time to finish
display b--show a nice black-red gradient

Running this script on a 2.1GHz system resulted in a time stamp of 16094 milliseconds.

In the second example, instead of reading every single pixel, will be read every line and process all its pixels inside the array returned by getPixels() before writing the whole line back to the bitmap. This means we will make only 1000 read and 1000 write calls instead of one million reads and one million writes.

EXAMPLE 2 - OPTIMIZED SCRIPT:

b = bitmap 1000 1000 color:red--create new bitmap with red background
st = timestamp()--get start time in milliseconds
for y = 1 to 1000 do--go through all lines in the bitmap
(
  pixels = getPixels b [0,y-1] 1000--read all 1000 pixels of a single line
  for x = 1 to 1000 do--go through all pixels in the line
  (
    pixels[x] *= (x+y)/2000.0--alter the pixel
  ) 
  setPixels b [0,y-1] pixels--write back the complete modified line
)
et = timestamp()--get end time in milliseconds
print (et-st)--print time to finish
display b--show the same nice black-red gradient

Running the optimized script on the same 2.1GHz system resulted in a time stamp of 4937 milliseconds - more than three times faster!

Since 3ds Max 2010, the pasteBitmap() function can be used in a custom function: mode to perform per-pixel operations using a MAXScript function. This method does not involve the getPixels() and setPixels() calls but does the same and is also great for bothcompositing two bitmaps together and for manipulating pixels ina single bitmap as in our test example.We simply pass the same bitmap twice. The result is normally stored in the second bitmap, in this case it is identical to the first bitmap so we effectively read and write using one bitmap value:

EXAMPLE 3 - ALTERNATIVE SCRIPT:

fn compfn c1 p1 c2 p2 =
(
  c1 * ((p1.x+p1.y)/2000.0)
)
b = bitmap 1000 1000 color:red--create new bitmap with red background
st = timestamp()--get start time in milliseconds
pasteBitmap b b [0,0] [0,0] type:#function function:compfn--call the function per pixel
et = timestamp()--get end time in milliseconds
print (et-st)--print time to finish
display b--show the same nice black-red gradient

Running the alternative script on the same 2.1GHz system resulted in a time stamp of 3828 milliseconds - a second faster than the optimized script above!

The following table shows the times in milliseconds for the three examples at different image resolutions:

 

2000x2000

1000x1000

500x500

250x250

125x125

EXAMPLE 1

64657

16094

3578

906

203

EXAMPLE 2

20000

4937

1266

344

62

EXAMPLE 3

15187

3828

906

172

47

Previous Tip

Use the 'flagForeground' node viewport state method

Next Tip

Only calculate once if possible