IntroductionIn the previous tutorial we examined the color object data, properties and methods. It became apparent that we were missing some useful functions, so in this tutorial we will add in a constructor that we can use to initialize the color object with an HSV color, and we also implement the +=, -= and *= operators for the RGB color model.
Adding a New ConstructorSince we have the ability to initialize the color object with various forms of an RGB color, we need to also add the ability to initialize our color object with an HSV color as well. The first thing we need to do is to check the HSV data type and see what kind of data we are working with.
'HSV color object.
Type colorhsv
h As Double
s As Double
v As Double
End Type
All of the data items are doubles, so we will use three doubles as input into the constructor. We will start with the declaration statement and add that in first. Remember, that we can only overload a method if the parameter set is unique so we need to check the existing constructors to make sure our new constructor is unique.
Declare Constructor () 'Builds a default color of black.
Declare Constructor (clr As clrobj) 'Pass a color object here.
Declare Constructor (r As UByte, g As UByte, b As UByte, a As UByte = 255) 'Build a color using values.
Declare Constructor (clr As UInteger) 'Build a color using RGB value.
It all looks good. None of the existing constructors use any double parameters, so we can add in our new constructor declaration.
Declare Constructor () 'Builds a default color of black.
Declare Constructor (clr As clrobj) 'Pass a color object here.
Declare Constructor (r As UByte, g As UByte, b As UByte, a As UByte = 255) 'Build a color using values.
Declare Constructor (clr As UInteger) 'Build a color using RGB value.
Declare Constructor (hue As Double, sat As Double, value As Double) 'Build a color using HSV value.
Our new constructor is the last one in the list. Now we need to add in the actual code for our new constructor. We need to keep in mind that we need to validate the data in the constructor just as we would in a property so that we make sure we have good data. For the HSV color model, the hue ranges from 0 to 359, and the saturation and value range from 0 to 255. With this in mind we can write our new constructor code.
'Build a color using HSV value.
Constructor clrobj (hue As Double, sat As Double, value As Double)
If hue < 0 Then
_hsv.h = 0
ElseIf hue > 359.0 Then
_hsv.h = 359.0
Else
_hsv.h = hue
EndIf
If sat < 0 Then
_hsv.s = 0
ElseIf sat > 255.0 Then
_hsv.s = 255.0
Else
_hsv.s = sat
EndIf
If value < 0 Then
_hsv.v = 0
ElseIf value > 255.0 Then
_hsv.v = 255.0
Else
_hsv.v = value
EndIf
_hsv2rgb
End Constructor
Before we go any further we need to test the code to make sure everything works according to plan. To test our new constructor, we will use the following code.
Dim As clrobj myColor = clrobj(360, 128, 128)
'Make sure the screen intilizled properly.
If aScreen.GetStatus = FALSE Then
End
EndIf
aScreen.ClearBuffer myColor
aScreen.Redraw
Sleep
Since we are developing our object we want to compile this with the -exx option. -exx will let us know if we are doing something dumb like using an invalid pointer or writing outside the bounds of an array or mixing our data types incorrectly. When the above code is compiled we get the following warning.
D:\FreeBASIC\\fbc -exx "fbgrtest.bas"
fbgrtest.bas(23) warning 25(0): Overflow in constant conversion
Make done
We are getting an overflow warning on line 23 which happens to be:
Dim As clrobj myColor = clrobj(360, 128, 128)
An overflow means we are passing data too large for the receiving variable, but we defined our constructor with doubles, which wouldn’t overflow with the data we used. Something else is going on here. Let’s look at our constructors again.
Declare Constructor () 'Builds a default color of black.
Declare Constructor (clr As clrobj) 'Pass a color object here.
Declare Constructor (r As UByte, g As UByte, b As UByte, a As UByte = 255) 'Build a color using values.
Declare Constructor (clr As UInteger) 'Build a color using RGB value.
Declare Constructor (hue As Double, sat As Double, value As Double) 'Build a color using HSV value.
Since we are passing three parameters, we only need to look at the constructors that have three or more parameters, which leaves us with the third and last constructor.
Declare Constructor (r As UByte, g As UByte, b As UByte, a As UByte = 255) 'Build a color using values.
Declare Constructor (hue As Double, sat As Double, value As Double) 'Build a color using HSV value.
The double values wouldn’t overflow so the problem must be the constructor with the ubyte parameters. However, it has four parameters, not three or does it? Look at the last parameter:
a As UByte = 255. This is an optional parameter which means we don’t have to supply it, so we can concentrate on the first three parameters. These are ubyte parameters which means they have a range of 0 to 255. Since we are passing 360 as the first parameter, and this would overflow a ubyte, this has to be our problem. But why would the compiler be using this constructor rather than the new one we created? Let’s look at how we are calling the the constructor again.
Dim As clrobj myColor = clrobj(360, 128, 128)
It looks ok, or does it? How does the compiler know that we are passing doubles here since they look like integers? The answer is that it doesn’t. As far as the compiler is concerned we are passing integers and it is trying to use the ubyte constructor since it is the closest match to the data we supplied. The solution is simple, but subtle. We need to let the compiler know we are passing doubles not integers, so we need to change the above code to this:
Dim As clrobj myColor = clrobj(360.0, 128.0, 128.0)
When we compile it now, we get:
D:\FreeBASIC\\fbc -exx "fbgrtest.bas"
Make done
No warnings, and the program works as expected when run. These are the types of subtle bugs we can get when we add in new functionality, so it always important to test the object after each new change. If we had waited to test this after we had implemented other changes, the above bug may have been harder to detect. This type of problem also needs to be documented in the object’s help file as well, so someone using the object will know that they explicitly have to write floating point numbers when using this constructor.
Adding New OperatorsThere are three operators that we haven’t implemented that would be quite handy to have when working with color; the addition-assignment += operator, the subtraction-assignment -= operator and the multiplication-assignment *= operator. Each of these operators perform the operation on the current object, and then assign the new value to the current object. Since these are assignment operators, we define them within the object itself.
Declare Operator += (rhs As Integer) 'Addition-assignment operator.
Declare Operator -= (rhs As Integer) 'Subtraction-assignment operator.
Declare Operator *= (rhs As Double) 'Multiplication-assignment operator.
Once we have the declarations in place, we can write the code for the operators.
'Addition-assignment operator.
Operator clrobj.+= (rhs As Integer)
Dim As Integer i
i = _color.channel.r + rhs
If i > 255 Then i = 255
_color.channel.r = i
i = _color.channel.g + rhs
If i > 255 Then i = 255
_color.channel.g = i
i = _color.channel.b + rhs
If i > 255 Then i = 255
_color.channel.b = i
_rgb2hsv
End Operator
'Subtraction assignment operator.
Operator clrobj.-= (rhs As Integer)
Dim As Integer i
i = _color.channel.r - rhs
If i < 0 Then i = 0
_color.channel.r = i
i = _color.channel.g - rhs
If i < 0 Then i = 0
_color.channel.g = i
i = _color.channel.b - rhs
If i < 0 Then i = 0
_color.channel.b = i
_rgb2hsv
End Operator
'Multiply assignment operator.
Operator clrobj.*= (rhs As Double)
Dim As Double i
i = _color.channel.r * rhs
If i > 255.0 Then i = 255.0
_color.channel.r = CByte(i)
i = _color.channel.g * rhs
If i > 255.0 Then i = 255.0
_color.channel.g = CByte(i)
i = _color.channel.b * rhs
If i > 255.0 Then i = 255.0
_color.channel.b = CByte(i)
_rgb2hsv
End Operator
Once the operator code is in place, we need to add some test code to the demo program to make sure everything is working as expected.
'Test the += operator.
myColor = RGBA(0, 0, 0, 255)
For x As Integer = 0 To 254
myColor += 1
For y As Integer = 0 To aScreen.ScreenHeight - 1
aScreen.PokeBuffer x, y, myColor
Next
Next
aScreen.Redraw
Sleep
'Test the -= operator.
myColor = RGBA(255, 255, 255, 255)
For x As Integer = 0 To 254
myColor -= 1
For y As Integer = 0 To aScreen.ScreenHeight - 1
aScreen.PokeBuffer x, y, myColor
Next
Next
aScreen.Redraw
Sleep
'Test the *= operator.
myColor = RGBA(255, 255, 255, 255)
aScreen.ClearBuffer mycolor
aScreen.Redraw
Sleep
myColor *= .5
aScreen.ClearBuffer mycolor
aScreen.Redraw
Sleep
We can now compile and test the code, and it compiles without any errors or warnings and does what we would expect. We now have some new operators for our color object.
SummaryIn this tutorial we have added some new functionality to our color object, did some troubleshooting and fixed some bugs in our code. At some point in the future we may need to add other methods to our color object, but for now it has enough functionality to be useful.