Q & A

Extract Color Values Painlessly

by Karl E. Peterson


QColor Breakout
I use the GetPixel API to retrieve color values from within images. It returns red, green, and blue color values, packaged as a single Long value. I've seen tips that show how to convert a Long to a hexadecimal string, then use the Mid function to extract two-character portions of the string, but that's far too slow for my needs. Is there an API to break out these individual color values rapidly?

A. I've seen those tips too, and they certainly seem awkward at best. If you're unfamiliar with how color values are stored within a Long variable, or if you simply have trouble remembering (as I do), here's an easy trick to jog the memory. Drop into the Immediate window and type the first line:

?Hex(vbRed),	Hex(vbGreen),	Hex(vbBlue)
FF	FF00	FF0000

This tells you the four bytes composing a Long are arranged as xxBBGGRR, where "xx" is a filler byte, "BB" is the blue value, "GG" is the green value, and "RR" is the red value. You've heard that a little knowledge can be dangerous, hence the tips you've seen built on this understanding. Extracting a single color value from a Long using string manipulation would be a killer if you had to do it more than a handful of times. For example, you need these gyrations merely to extract red (the easiest of the three!):

Red = CByte("&H" & _
	Right$(Hex$(value), 2))
Figure 1 | Extracting Color Values Needn't Be Scary.  

If you have color values stored in Long variables already, you can use a bitmask to extract only the byte(s) you're interested in. I think you'll agree that simply masking off the last byte to determine the red component is much easier:

Red = (Value And &HFF)

You can use similar bit-masking operations to extract green and blue values, although they require some bit-shifting as well (see Listing 1). But even these efficient methods can prove slower than desired if you're in a highly iterative situation, such as when working with all the pixels in a bitmap. In such a case, especially if you needed all three color components, you'd want to break apart the byte-encoded value as quickly as possible. You can do this with a single CopyMemory call, which transfers the Long to a user-defined type (UDT) defined to match the known byte ordering.

The Windows API provides the PALETTEENTRY structure, which matches exactly the byte ordering within a 32-bit memory value. You're not actually dealing with palettes, so it makes sense to define your own similar structure to avoid confusion (see Listing 2). But this is still one step shy of the ultimate optimization. If you need the individual color bytes and not the fully combined color value, why not just ask for that in the first place? You avoid the extraction issue altogether by redefining the GetPixel API call so it returns a custom RGB32 structure rather than a Long:

Private Declare Function GetPixelRGB _
	Lib "gdi32" Alias "GetPixel" _
	(ByVal hDC As Long, _
	ByVal x As Long, _
	ByVal y As Long) As RGB32

If your needs are highly iterative and you need the RGB values separated more often than combined, this is the only way to go (see Figure 1).

QAdd a Border to Borderless Forms
I'm using your CFormBorder class with no problems (see Resources). However, I'm trying to create a 3-D form (much like the one you get with the WS_DLGFRAME style) without a titlebar. Can this be done? I need a 3-D border that doesn't allow or accept resizing, yet doesn't have a titlebar either.

A.  You have two choices. You can either use the DrawEdge API on a normal borderless form, or you can subclass and eat the WM_SIZING messages sent to your form. The first is by far the easiest and cleanest, unless you have other needs that also dictate subclassing the form.

The DrawEdge API is also an incredibly useful function, especially when creating custom UserControls that completely draw their own interface. Call DrawEdge from the Form_Paint event if you're leaving AutoRedraw set to the default, False. Pass your form's hDC property as the device context (DC) to draw on, a RECT structure indicating where to draw, the edge style you'd prefer, and a flag (BF_RECT) indicating DrawEdge should draw a rectangle.

I usually find it easiest to set my form's ScaleMode to vbPixels. In this case, that also facilitates stuffing the passed RECT structure, as you can simply assign the form's ScaleWidth and ScaleHeight as the Left and Bottom rectangle dimensions (see Listing 3). Alternatively, use your form's Scale method to convert coordinates from another ScaleMode.

QLonger Timer Intervals
I've been working on a VB app that downloads images from five network cameras, and I need to set the interval for each cam individually. I want the cams to download the images to the hard drive at different times and varying intervals ranging from one to 10 minutes. I know I can't use the VB timer, which is limited to 63 seconds. A great idea from you would bail me out of my biggest problem so far with this application.

A. This is an extremely common misconception regarding VB's intrinsic Timer control. VB does retain many of its 16-bit roots and, yes, the Interval property is indeed limited to 65,535 milliseconds—about 651/2 seconds—but that doesn't mean you can't use it for firing at a longer interval. There's absolutely nothing wrong with receiving more frequent notifications, as long as you're aware of when the needed duration has elapsed, right?

Whenever you need notifications of periods longer than supported directly, go ahead and use a standard VB Timer control. Set its Interval property to the greatest common denominator of your various needs. In this case, your needs range from one to 10 minutes, so you could use a one-minute interval (60,000). If you need notifications on the half-minute marks, say at 31/2 and 51/2, you could use a half-minute duration (30,000).

Use a static counter within the Timer event to keep track of how many notifications have passed. When you reach the magic multiple, fire off the code that needs to run. For example, say you need to perform a given task every five minutes. I like to set the timer's Interval property to a value no more than half the resolution I'm seeking. In a case like this, I'd probably want an event every 30 seconds or so. This code within the Timer event keeps track of how many times the event has fired, so it can determine when to execute the periodic code:

Private Sub Timer1_Timer()
	Static Counter As Long
	Counter = Counter + 1
	If Counter >= 10 Then
		' With a 30-second interval,
		' 10 would represent five min.
		Call RunFiveMinuteCode
		Counter = 0
	End If
End Sub

Another way to code the counter would be to add the Timer.Interval on each notification. This would result in slightly more readable code:

Private Sub Timer1_Timer()
	Static Counter As Long
	Counter = Counter + Timer1.Interval
	If Counter / 1000 >= 5 Then
		' Divide by 1000 to convert
		' milliseconds to seconds.
		Call RunFiveMinuteCode
		Counter = 0
	End If
End Sub

For even greater accuracy, consider calling either the timeGetTime or GetTickCount API to keep track of how long it's been since you last fired off the periodic code. Both of these APIs tell you the number of milliseconds since Windows started. (If you think you might be running on a machine that hasn't been rebooted in more than 50 days <snicker>, you'll have to code for roll-over!)

Remember, timers aren't guaranteed to fire when you ask, only no sooner than you ask. If the system is busy, you might miss occasional timer ticks. So it makes sense to check an elapsed time source when you can't afford to be too latent. Also, when firing optimally, system timers operate at a multiple of the system clock—roughly one beat every 55 milliseconds on 9x systems—so you need to determine what's "close enough" to consider a given interval has elapsed (see Listing 4).

Karl E. Peterson is a GIS analyst with a regional transportation-planning agency and serves as a member of the VSM Technical Review and Editorial Advisory boards. Online, he's a Microsoft MVP and a section leader on several VSM forums. Find more of Karl's VB samples at www.mvps.org/vb.

  Get the original code for this article here.

Updated samples based on this article:

• Ask the VB Pro, "Toggle ShowInTaskbar at Run Time" item, by Karl E. Peterson [Visual Basic Programmer's Journal, January 2001]