|
The HSB Color Model

Some time ago, while browsing the posts at the Atlanta
VB List, to which I subscribe, I've found this intriguing
one:
> I have a logic problem.
> I need to produce a sorted palette.
[Snip]
> I'm trying to come up with logic to group all the
blues together,
> then the greens, then the reds, etc. I expect in
part that it
> depends on how I define the colors (which will be
arbitrary, anyway),
> but I'm unclear how to increment them so that
0,0,255 is close to
> 90,0,255 yet in an appropriate order so that
someone with a
> discerning eye will see a logical gradation of
color.
> Jim McFadden |
This post is implicitly talking about the "RGB Color
Model", which is used to represent color in Windows. The
name comes from the fact that each screen color is created
varying the intensities of the Red, Green and Blue light beams
that exist inside the monitor. VB even has a built in function
to generate a given color using this model:
|
Function RGB(RedValue, GreenValue,
BlueValue) as Long
|
The post was odd because Jim McFadden is an experienced VB
programmer and the subject seemed so simple that any one whith
little knowledge about RGB could probably come up with an
algorithm right from the top of his/her head.
It certainly was just a matter of varying the intensities of
each light beam so we would get a linear pattern of color shades
where all the Reds would be grouped together, followed by all
the Greens and, finally, all the Blues while the missing colors
would be placed between each of these base shades.
Alas, it's not so simple. This is what the latter approach
would come up with:

This seemed like a challenge to me, so I put my battle suit
on and went to war. That is, I started looking for answers in
the Internet.
After researching the net, I came up with a real better
understanding of color (well, I already knew about
"Additive" and "Subtractive" colors because
of my work with Desktop Publishing).
I also got formally presented to this mathematical color
model called HSB, to which I didn't had a clue before. I had
seen it when using Photoshop and other similar tools, but I had
no idea how it worked and why it was the better way to achieve
color categorization in a computer.
What the numbers mean
HSB means "Hue, Saturation and Brightness":
- Hue: This is the "color" how we use to
see it. This value ranges from 0 to 359, starting with Red
(0) and going through Orange, Yellow, Green, Blue, Purple
and coming back to Red again (359). As you can see, this
puts the color in a rainbow-like pattern that is very easy
to use.

- Saturation: A colored spot can have a variation on
the amount of color applied to it, that is, how vivid the
color is. This is given by a percentage, where 0% means no
color (White) and 100% means "normal" (very
vivid).

- Brightness: Finally, the color spot can be
classified as how dark or bright it is. This is also a
percentage, where 0% means it is very dark (Black) and 100%
means it's "normal".

Looking for HSB
desperatelly (almost)
It became clear that, to come up with what Jim was proposing,
I had to treat color under a different model than RGB. This is
an interesting computing proposal, by the way: putting matters
under different lights and perspectives can give you answers
that the original approach could not.
The HSB model seemed perfect, but, what was the algorithm to
translate forth an back between RGB and HSB? I spent a few hours
jumping from site to site, but no one seemed to have this simple
(I guessed) information. Then, from an University research site,
this tidbit of information crossed my way: "using the Java
color library"...
Ops! What is this that you said? That Java has this color
library? Well, Java was just a step away, I had just to install
VJ++ and browse to the classes source code. And there they were,
in the Java.awt.Color.java class: the functions to convert from
HSB to RGB and back. Uff!
Armed with the knowledge I needed (and silently blaming
Microsoft for the humiliation of having to borrow Java code to
use in a VB project) I started coding the class that would do
the color conversions and provide other goodies.
It turns out that the Java library treats the Saturation and
Brightness values in a range that goes from 0 to 1.0. It wasn't
exactly how I wanted them: my purpose was to package an HSB
value in a Long variable, so it had to change slightly. You can
find the final conversion code in the accompanying project
(ColorConverter.cls). It strikes me because I can't come up with
the explanation of how the actual conversion takes place.
Although this upsets me somewhat, it does work (kudos to Sun, by
the way).
That would be the end of the Color battle, but the war was
not over. I had yet to build the application that would use the
color conversion class. Building it made use of one or two
tricks that I'd like to share.
Using ScaleWidth and
ScaleHeight
When plotting the RGB colors converted from each HSB value, I
had a few different kinds of graphics to draw. The current Hue,
Saturation and Brightness values were kept by the main form in
global scope. They could be used as reference points for at
least three types of graphics:
- A Saturation x Brightness graphic, with a fixed Hue
value
- A Hue x Brightness graphic, with a fixed Saturation
value, and
- A Hue x Saturation graphic, with a fixed Brightness
value.
I could, of course, derive the necessary positions in the
screen based on the width in pixels of the PictureBox where I
was painting the graphics and the range for each axis (0-359 for
Hue, 0-100 for Saturation and Brightness).
This would be my usual approach, but this time I wanted a
simpler way. So I just set the PictureBox's ScaleWidth and
ScaleHeight to the maximum value for each axis, and the main
loop of the painting routine had simply to iterate the full
range of each one. Although very inefficient if the PictureBox
dimmension in pixels is less than a particular range, it scales
well when the dimmension is greater than that range. Besides,
code becomes much more readable.
This is easier to understand, I guess, with a snippet:
|
'--------------------------------------------------------
Private Sub xHueBrightGraph()
'--------------------------------------------------------
' Draws the Hue x Brightness Graph
' (uses
a constant Saturation value)
'--------------------------------------------------------
Dim lX As Long, lY As Long
Dim lColor As Long
Dim lStepX As Long, lStepY As Long
'--------------------------------------------------------
With pctGraph
.Cls
'Sets up each
axis' range
.ScaleWidth = clMaxHue + 1 '360
.ScaleHeight = clMaxBrightness + 1
'100
'Converts the
quality factor from pixels to the
'current
ScaleMode. Using ScaleX and ScaleY
'will keep it in the correct
proportion:
lStepX = .ScaleX(mlGraphQuality,
vbPixels, .ScaleMode)
lStepY = .ScaleY(mlGraphQuality,
vbPixels, .ScaleMode)
If lStepX = 0 Then lStepX = 1
If lStepY = 0 Then lStepY = 1
'lX iterates
the Hue range (0..360)
For lX = 0 To .ScaleWidth Step lStepX
'lY
iterates the Brightness range (0..100)
For lY = 0 To .ScaleWidth
Step lStepY
'Converts
HSB to RGB
With
mobjColors
lColor = .HSBToRGB( _
.HSBToLong(lX, clMaxSaturation, lY))
End With
'Draws
a box in the desired color
pctGraph.Line
(lX, lY)-Step(lStepX, lStepY), _
lColor, BF
Next
Next
End With
End Sub
|
Drawing a Cursor
I wanted the user to drag the mouse over the graphs to alter
the H, S and B values of the sample color. The user should be
able also to see in the graph the correponding position of these
"coordinates". To achieve this, I could use, for
instance, an Image control placed over the PictureBox, but a
wiser approach, I supposed, would be a Xor cursor.
A Xor cursor is based on the binary Xor operation. The
carachteristics of it is that if you take a binary value and Xor
it with another value and then Xor the result with the same
value again, you'll get the original value back. Besides that,
if you Xor any value with a number where each bit is 1, you'll
get the binary inverse of the value:
|
11110001 XOR 11111111 =
00001110
00001110 XOR 11111111 = 11110001 |
Seeing from this description, maybe you wouldn't realize that
this simple operation is perfect for cursors: It guarantees
that, no matter the color of the pixels where the Xor operation
is performed, the cursor will allways be visible. Likewise, to
restore the screen to it's original state, we just need to
perform the same operation again.
Another use of the Xor binary operation is in creating
encripted text: you take a secret key (the user password) and
use it's letters as binary values to Xor with a given text. To
restore the text back you need the same password again to
perform the Xor operation one more time (MS Word used to use
this scheme to password protect it's files).
Again, lets see how it cuts in code:
|
'----------------------------------------
Private Sub xToggleGraphCursor()
'----------------------------------------
' Shows/Hides the Graph cursor
'----------------------------------------
Dim lX As Long, lY As Long
'----------------------------------------
'Gets the actual
'coordinates' from
'global variables, based on the
'current GraphMode:
Select Case mlGraphMode
Case gtByHue
lX = mlCurBrightness
lY = mlCurSaturation
Case gtBySaturation
lX = mlCurHue
lY = mlCurBrightness
Case gtByBrightness
lX = mlCurHue
lY = mlCurSaturation
End Select
With pctGraph
'The vbInvert
mode 'Xors' the pixels
'with
the equivalent to RGB(255,255,255)
.DrawMode = vbInvert
'After this
operation, it will show or hide
'the cursor at the lX,lY coordinates:
pctGraph.Circle (lX, lY), _
.ScaleX(CURSORSIZE, vbPixels,
.ScaleMode)
.DrawMode = vbCopyPen
End With
End Sub |
Final words (gulp)
As you can see, the HSB color model comes in handy whenever
you need to present colors in a categorized way. Being able to
pick a given color (er... Hue) by a very specific index and
altering this color's Brightness and Saturation is far easier
than dealing with RGB values. I hope you enjoy. ;-)
|