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