VB Graphics Programming: Part 4 (Optimizations Checklist)

This last section of my graphics programming tutorials takes a different approach from the previous three sections.  Instead of discussing specific graphics routines, I’m going to give you my “Top 10 List of Graphics Code Optimizations.”  This checklist of optimization techniques will provide a simple, straightforward mechanism for speeding up your graphics application.  We’ll start with the easiest ways to speed up your code and end with the most dramatic (but effective) ways.  My hope is that you can take slow graphics-related code, adjust it according to this checklist, and end up with a much faster version with minimal impact to code readability or complexity.

Tanner’s Top 10 List of Graphics Optimizations

10. Compile to native code, enabling every advanced optimization. While it may seem obvious, this is the easiest – but oft forgotten – way to speed up your graphics code.  Enabling one optimization in particular: ‘Remove Array Bound Checks,’ will significantly speed up DIB section code, as it is heavily dependent on arrays.  When you check that box, VB stops checking array locations for validity (i.e. if you try to access location (x,y) in an array, VB won’t check to see if x or y falls within the declared range of the array).  This optimization comes at a trade-off, of course; any code involving arrays could now be as much as 10-15x faster.  Unfortunately, if you try to access an invalid array location, you won’t get an error – your code will cause a critical fault and the program will crash.  (If you write decent code, however, errors like this will never occur. Check your bounds in advance!)

9. Check your ScaleMode: use pixels, never twips. VB.Net has completely done away with twips, but ol’ VB6 decided to make this silly measurement the default ScaleMode setting for forms and picture boxes.

If your code a) doesn’t work, or b) does work but is strangely slow, make sure you’ve set the ScaleMode of all relevant picture boxes and/or forms to pixels.  As a general rule of thumb, set every object’s ScaleMode to pixels even if you don’t plan to use it for graphics.  This helps ensure that you’ll never fall victim to twips-related errors.

8. Don’t make your code refreshing. For most graphics-related code, AutoRedraw is your friend.  If you have AutoRedraw set to false and you use API-based graphics methods on a picture box, VB6 will attempt to refresh the image every time a pixel changes.  For any image larger than 1×1 pixels, this is a problem.  Refreshing an image takes a lot of time, so refresh an image only after you’re completely done messing with it.  (There are several exceptions to this rule, but they only apply to experienced users who are doing unconventional tricks with picture boxes; generally speaking, unless using a picture box as the receiver of a buffer, keep AutoRedraw on.)

Another thing worth mentioning here is progress bars – if you’re using a progress bar to track image processing code, don’t refresh it for every pixel or even every line.  Refreshing a progress bar is almost as slow as refreshing an image, so if you must use a progress bar then refresh it every 15-20 lines to minimize its impact on your code.

7. Optimize your variables. Because graphics programming can involve many variables, here are some tips for avoiding bad variable usage:

  • Variants are very slow.  Never – EVER – use Variant types in your code.  They’re slow, they take up a lot of memory, and you can always find a way to write code that doesn’t require them.  (Another thing worth mentioning is to never declare your variables like this: Dim x, y as Long. In this example, VB6 will declare x as a Variant and y as a Long. The proper programming technique is: Dim x as Long, y as Long.)
  • Don’t use decimal variables.  Generally speaking, Singles and Doubles are slower, Longs and Integers are faster.  In every Visual Basic case I’ve researched (there are exceptions in other languages, particularly ASM, and under different hardware configurations), floating-point/decimal math is slower than integer/whole number math.  Therefore, avoid decimals if possible.  For example, Red = Red * 1.5 tends to be slower than Red = (Red * 15) \ 10.
  • Longs are fastest.  Because VB6 was designed for 32-bit systems, it heavily favors Long-type variables.  This means that in most cases, Long variables will be executed faster than Integer or Byte variables – and significantly faster than Single, Double, or Variant ones (typically ~3-5x faster than Single ones, and moreso for the other data types).  Use Longs unless you absolutely need to conserve memory by using Integers or Bytes.  (This is especially important for looping variables; because they get accessed so many times, always use Long-type variables for your loops.  VBFibre – the famous VB optimization site shows the significant difference between using a Single and a Long as For...Next variables.)
  • Avoid type conversions.  Regardless of what type of variable you use, keep it consistent.  When you intermix variable types, any programming language slows down – a line like Integer = Long requires VB to temporarily change Long into an Integer, and that takes time.  (Note: this doesn’t apply to DIB section arrays – they must always be of type Byte.)

6. In-line optimization: fix your math. In-line optimization refers to optimizing your code one line at a time. This often involves using faster math functions and better programming techniques. There are many tips I could list under this category, but here are some of the most critical math optimizations you can do for VB6 code:

  • Watch for dividing code.  Two points here: 1) always use \ instead of /. You may not know this, but these functions are different in VB6. Backslash represents integer divide – it ignores any decimals that arise from the division, while frontslash allows for decimal math.  If you’re only interested in the integer results of division, use backslash. It will be faster.  2) Don’t divide if you don’t have to.  Dividing is the slowest of the four basic math functions so avoid it if you can.
  • Avoid calling functions and subs within For/Next or Do/While loops.  Though convenient, functions and subs in VB6 are generally slower than manually inserting that code into the loop.  The slow-down generally isn’t noticeable unless you’re calling functions from within a loop, because then the speed loss is accentuated thousands of times over.
  • If you must use functions and subs, use ByRef instead of ByValByRef variables simply send a function the address of a variable.  ByVal variables require VB to make a copy of the variable before it sends it – and creating a new variable takes time.
  • Lengthy math equations take a lengthy time to process.  Short lines of math allow the compiler to better optimize their order and purpose.  One giant line of code might impress your friends, but VB6 can’t optimize it nearly as well as a bunch of smaller lines.

For a much more in-depth discussion of VB optimization techniques, visit VBFibre at http://www.persistentrealities.com/vbfibre/index.php.  Every VB6 programmer should spend some time there – it’s the most accurate and well-explained information ANYWHERE about VB6 optimizations.  Phenomenal site, donate if you can.

5. Use look-up tables. Few things can speed up code as dramatically as a look-up table.  For those who don’t know, consider the following code example (used to invert a pixel’s color):

For x = 0 to 99
For y = 0 to 99

   'Color = GetPixel()… goes here

   'RGB extraction goes here

   R = 255 - R
   G = 255 - G
   B = 255 - B

   'SetPixel()… goes here

Next y
Next x

For every pixel, VB has to do three ‘Subtract’ functions. This is bad coding – for this simple 100×100 picture example that’s over 30,000 ‘Subtracts.’  Try the following code instead:

Dim LookUpTable(0 to 255) as Byte

 'Fill the look-up table with every possible color value and it's corresponding inverted value
For x = 0 to 255
   LookUpTable(x) = 255 - x
Next x

For x = 0 to 99
For y = 0 to 99

    'Color = GetPixel… goes here

    'RGB extraction goes here

   R = LookUpTable(R)
   G = LookUpTable(G)
   B = LookUpTable(B)

    'SetPixel… goes here

Next y
Next x

With this code, VB only does 256 ‘Subtracts’ and then uses the table of values to change R, G, and B for each pixel.  This makes a big difference in execution time – so use look-up tables every chance you get.

(There is one disclaimer associated with look-up tables in VB6: unless you follow step 10 of this tutorial (particularly the “Remove Array Bound Checks” option), look-up tables can accidentally slow down code that works on small images.  Note that the steps of this tutorial are in order for a reason!)

An excellent example of look-up tables can be found in the Real-time Brightness program.

4. Forget about PSet and Point – use the API. If you haven’t read the previous three sections of these tutorials, now is the time to do it.  PSet and Point are terrible choices for per-pixel image processing.  Use GetPixel and SetPixel/V for a huge speed increase.

3. Forget about GetPixel and SetPixel/V – use DIB sections. While GetPixel and SetPixel/V are nice, they’re still slow.  DIB sections (or BitmapBits) will give you significantly better results.

2. DIB Sections do better in streams. If you read these tutorials in order, you’ll remember that tutorial three discussed how to declare an ImageData() array.  Specifically, a method like Redim ImageData(0 to 2, 0 to Width, 0 to Height) is used.  While this creates an easy-to-use array, its possible to speed it up by declaring it differently.  Redim ImageData (0 to (Width * Height * 3)) gives us an array the exact same size as the first statement, but we use only one dimension instead of three – making our array more than 3x faster for VB to access (and for large arrays, the gain can surpass 10x).  The only problem with this is that we can no longer access direct pixels or colors, but for many graphics functions (like the ‘Invert’ example on (5), or brightness, or contrast, etc.) we do the same thing to every color within a pixel so it doesn’t matter.  For example:

For x = 0 to (Width * Height * 3)
   ImageData(x) = 255 - ImageData(x)
Next x

would invert an image just the same as the example in optimization (5).  The reason I call this method a “stream” is that we treat the image as a continuous stream of values, not as separate pixels or colors. For a more in-depth example of this, see the Real-Time Brightness program.

(One additional disclaimer is in order: make sure to read Section VI: the infamous 4-byte alignment issue in the previous tutorial. Image streams must be declared in a way that adheres to this rule, which limits them to working on images whose width is a multiple of 4.)

1. If you do all this and your program is still too slow, try something extreme. I hate to say it, but if you’ve done everything above and your program is still too slow, you may be out of luck with traditional methods. Here are some advanced techniques you might consider:

  • Consider switching to SafeArrays. This archive from the old Students of Game Design site is an excellent tutorial detailing the structure and use of “SafeArrays”, the internal VB format for arrays.  SafeArrays won’t give you a speed increase in editing your array information, but they will get and set pixel information faster than GetDIBits and Set/StretchDIBits.  If your code requires a large amount of getting and setting pixels, SafeArrays may give you the performance boost you need without a lot of extra coding (as they are structured almost identically to the arrays returned by GetDIBits).
  • Look into assembly language extensions. Planet-Source-Code has several excellent programs by the aforementioned Robert Rayment demonstrating how to use ASM (assembly language) within VB to create graphics routines slightly faster than traditional VB methods. Assembly language is often the fastest programming language available to Windows programmers (next to machine language, but you don’t want to write that :) ), and with some clever work you can use it within your VB6 program.  Be forewarned, however, that this is most definitely not an easy thing to do – but it may give you a little extra speed.
  • Search the net for 3rd party SDKs or ActiveX controls dealing with graphics (DirectX, OpenGL, ActiveX controls, etc.).  In almost every case, DirectX, OpenGL, or similar SDKs (software development kits) won’t help your per-pixel graphics code to be any faster.  They may, however, provide you with hardware support for your effect.  For example, DirectX provides hardware acceleration for gamma correction, which will be much faster than trying to perform gamma correction in code.  Also, you could buy a VB-compatible OCX or DLL written in another language that does your effect for you.  This can, however, be an expensive option.
  • Learn another programming language. The sad but true fact is that some other programming languages provide faster graphics programming techniques via pointers and other tricks.  Even java contains many powerful image editing routines as part of the language, so if you’re really desperate than you can try learning (or rewriting your project) in another programming language.  Also, if you rewrite your routine in C/C++ you could compile it into a DLL and use that from VB, should you want to.
  • Go to the library and check out some graphics programming books. Some of my favorite programming techniques were learned from non-VB graphics programming books.  Even if you don’t understand the code associated with a book, you can still learn a lot about different filters, optimization routines, etc.  If your library doesn’t have anything good, search Amazon.com – you’ll be amazed at how many graphics programming books are out there.
  • Use your imagination. Sometimes the best optimizations are written by programmers who aren’t planning on writing a good optimization.  Your ingenuity is your best programming tool – try to think of new, clever ways to speed up your code. If you think up something great, be sure to let me know about it!

As a demonstration of many of these techniques, feel free to check out the Real-time Brightness example below:

And that, my friends, this is the end of these tutorials.  It’s taken me many, many hours to write these up, so I hope you’ve gained something from reading them.  If you have any comments, questions, suggestions, ideas for future tutorials, or want to send me a generous donation, visit my contact page.

Happy programming!

Similar Posts:

 

This site - and its many free downloads - are 100% funded by donations. Please consider a small contribution to fund server costs and to help me support my family. Even $1.00 helps. Thank you!

9 thoughts on “VB Graphics Programming: Part 4 (Optimizations Checklist)”

  1. Great article.

    I’m interested in computer vision where the same principles can be applied to obtain a cheap working platform.

    Thank you very much for your work.
    Miguel

  2. I am absolutely loving these articles! I did discover a few more problems though when you converted your website:

    “Therefore, avoid decimals if possible. For example, Red = Red * 1.5 tends to be slower than Red = (Red * 15) 10. ”

    Should be:

    Therefore, avoid decimals if possible. For example, Red = Red * 1.5 tends to be slower than Red = (Red * 15) \ 10.

    “Watch for dividing code. Two points here: 1) always use instead of /. ”

    Should Be:
    Watch for dividing code. Two points here: 1) always use \ instead of /.

  3. Ah, nice catch Mike. I’ve corrected both mistakes and will double-check for any other escape character problems.

    Many thanks for pointing those out, and I’m glad the articles have been helpful to you.

  4. i’ve read the articles and the tips&triks are great but i’ve found a little problem applying the code. I use GetImageData and SetImageData (from the brightness demo) to store and retrieve an image (into a picturebox) acquired from a webcam. If the sourcepicbox has the same dimensions of the destpicbox all works fine. but if the destpicbox is different, the images looks like ripped. someone can help me?

  5. Hi,
    Thanks for your article, all parts 1 to 4.

    Actually I came here in a try to find out the Instruction
    or a way to work around to be able to draw 3 Dimensional
    figures using: Line (x1,y1,z1)- (x2,y2,z2) if such an instruction could be achieved.
    Why ..?
    Because I want to show the drawing of a 3 dimensional
    skeleton of a building when its data is fully generated
    in a program.
    I already succeeded in doing that for 2 Dimensional
    skeletons plus its analysis and design.

    How to handle Z direction ? this is the Problem.
    Although there is a Transformation matrix composed of
    SIN & COS , 1 , etc to Rotate the diagram as I know, and I haven’t tried with it.

    Thanks for help .. if any body has got an Idea about that
    how to implement it.
    Best Regards.

    1. Hi Mohamad – if you are only drawing wireframe images, you can simply ignore the z-dimension when drawing the lines to the screen. Simply do .Line (x1, y1)-(x2, y2) and it will project the image just fine.

Leave a Reply