Introduction
This article is about how the structure System.Drawing.Color
works.
New Color
Take this example:
Dim color1 As New Color
Color color1= new Color();
When a variable is declared as a color
, what happens is:
Shared Sub New()
Color.Empty = New Color
Color.StateKnownColorValid = 1
Color.StateARGBValueValid = 2
Color.StateValueMask = Color.StateARGBValueValid
Color.StateNameValid = 8
Color.NotDefinedValue = 0
End Sub
static Color()
{
Empty = new Color();
StateKnownColorValid = 1;
StateARGBValueValid = 2;
StateValueMask = StateARGBValueValid;
StateNameValid = 8;
NotDefinedValue = 0L;
}
static Color()
{
Color::Empty = ();
Color::StateKnownColorValid = 1;
Color::StateARGBValueValid = 2;
Color::StateValueMask = Color::StateARGBValueValid;
Color::StateNameValid = 8;
Color::NotDefinedValue = 0;
}
Empty
is a private color
declaration which is of not much interest.
StateKnownColorValid
is a private Short
declaration. This is used in the Color.IsKnownColor
(determines if the given color is present in the pre-defined list) property as the AND mask.
StateARGBValueValid
is a private Short
declaration. This is used in IsKnownColor
property, IsNamed
property, etc. There are more inaccessible New
sub-routines in which this variable is assigned to the variable state
used in color-validity checking routines.
StateValueMask
has the same uses of StateARGBValueValid
.
StateNameValid
is used in the Color.ToString
function. When we call this function, if that color
has a name (like black
, etc.), Black
is included in the return value. This variable is used as the AND mask in that function to determine if there is a valid name for the color.
NotDefinedValue
is used in the Color.FromName
function. In that function, if the argument(Object
type) is not a valid color, then, the function returns a color
. The returned color
will have the specification that the color
is not defined.
There is an Enumeration
called KnownColor
. This is the source of all color
s. All the color
listings in Color
are properties.
They look like:
Public Shared ReadOnly Property AliceBlue As Color
Get
Return New Color(KnownColor.AliceBlue)
End Get
End Property
public static Color AliceBlue
{
get
{
return new Color(KnownColor.AliceBlue);
}
}
public: __property static Color __gc* get_AliceBlue()
{
return (KnownColor::AliceBlue);
}
But, in our programming environment, we cannot declare like this. This is because there is another Friend
(this keyword specifies that the routine or function or variable can be used only by the assembly in which it is present) declaration of New
. The declaration is:
Friend Sub New(ByVal knownColor As KnownColor)
Me.value = 0
Me.state = Color.StateKnownColorValid
Me.name = Nothing
Me.knownColor = CShort(knownColor)
End Sub
internal Color(KnownColor knownColor)
{
this.value = 0L;
this.state = StateKnownColorValid;
this.name = null;
this.knownColor = (short) knownColor;
}
public private: Color(KnownColor __gc* knownColor)
{
this->value = 0;
this->state = Color::StateKnownColorValid;
this->name = 0;
this->knownColor = *static_cast<__box Int16*>(knownColor);
}
The primary use of this is in returning a color
like Color.AliceBlue
. This is merely to shorten the code. Writing 4 lines for each and every color
is time consuming and increases the size of the assembly. So, this practice is implemented.
There is one last Private
declaration of New
. The source code for it is:
Private Sub New(ByVal value As Long, ByVal state As Short, _
ByVal name As String, ByVal knownColor As KnownColor)
Me.value = value
Me.state = state
Me.name = name
Me.knownColor = CShort(knownColor)
End Sub
private Color(long value, short state, string name, KnownColor knownColor)
{
this.value = value;
this.state = state;
this.name = name;
this.knownColor = (short) knownColor;
}
private: Color(Int64 __gc* value, Int16 __gc* state, String __gc* name,
KnownColor __gc* knownColor)
{
this->value = value;
this->state = state;
this->name = name;
this->knownColor = *static_cast<__box Int16*>(knownColor);
}
This is a Private
declaration first of all because the values are directly assigned without any error checking. The main source of error could be the first long argument value
. This argument is the ARGB value that is stored in the internal variable value
in Color. Since retrieving the Alpha
, Red
, Green
and Blue
components of a color
from value involves bitwise operations (I will be telling about this soon) - machine-level operations which are intimidating to beginners.
This declaration of New
is used in the FromARGB
function and the FromName
function.
ARGB - Alpha Red Green Blue
What is ARGB?
ARGB is nothing but transparency added to the standard RGB color composition. Alpha can range from 0 to 255 like the Red
, Green
and Blue
components.
How Is It Stored
Instead of wasting 4 byte data types (sums up to 32 bits), ARGB values are stored using one single long data type or a 32 bit integer for efficiency purposes. Operating on 1 variable is way easier than performing operations on 4 different variables. This is just like the way DOS and few versions of Windows stored the date on which a file was created/modified in a single variable (2-byte entry).
Every component is allowed 8 bits because the highest value they can have is 255 and 255 in binary is 11111111.
In earlier versions like VB6, we could create a color using a function called RGB and it is still supported in the .NET versions.
But, the .NET programming languages use ARGB for all colors.
This is how we can make our own color:
Color.FromArgb(alpha, red, green, blue)
Color.FromArgb(alpha, red, green, blue);
Color::FromArgb(alpha, red, green, blue);
The code for this FromArgb
function is as follows:
Public Shared Function FromArgb(ByVal alpha As Integer, _
ByVal red As Integer, ByVal green As Integer, ByVal blue As Integer) As Color
Color.CheckByte(alpha, "alpha")
Color.CheckByte(red, "red")
Color.CheckByte(green, "green")
Color.CheckByte(blue, "blue")
Return New Color(Color.MakeArgb(CByte(alpha), _
CByte(red), CByte(green), CByte(blue)), Color.StateARGBValueValid, _
Nothing, DirectCast(0, KnownColor))
End Function
public static Color FromArgb(int alpha, int red, int green, int blue)
{
CheckByte(alpha, "alpha");
CheckByte(red, "red");
CheckByte(green, "green");
CheckByte(blue, "blue");
return new Color(MakeArgb((byte) alpha, (byte) red, (byte) green,
(byte) blue), StateARGBValueValid, null, (KnownColor) 0);
}
public: static Color __gc* FromArgb(Int32 __gc* alpha,
Int32 __gc* red, Int32 __gc* green, Int32 __gc* blue)
{
Color::CheckByte(alpha, S"alpha");
Color::CheckByte(red, S"red");
Color::CheckByte(green, S"green");
Color::CheckByte(blue, S"blue");
return (Color::MakeArgb(*static_cast<__box Byte*>(alpha),
*static_cast<__box Byte*>(red), *static_cast<__box Byte*>(green),
*static_cast<__box Byte*>(blue)), Color::StateARGBValueValid, 0,
*static_cast<__box KnownColor*>(0));
}
First, the function checks if all the arguments alpha
, red
, blue
and green
have valid values(0-255). This is what the CheckByte
function does.
Color.MakeArgb
function is the function that converts the separate components into one single 32-bit Integer
or Long
. Its source code is:
Private Shared Function MakeArgb(ByVal alpha As Byte, _
ByVal red As Byte, ByVal green As Byte, ByVal blue As Byte) As Long
Return CLng((CULng(((((red << &H10) Or (green << 8)) Or blue) _
Or (alpha << &H18))) And &HFFFFFFFF))
End Function
private static long MakeArgb(byte alpha, byte red, byte green, byte blue)
{
return (long) (((ulong) ((((red << 0x10) | (green << 8))
| blue) | (alpha << 0x18))) & 0xffffffffL);
}
private: static Int64 __gc* MakeArgb(Byte __gc* alpha,
Byte __gc* red, Byte __gc* green, Byte __gc* blue)
{
return *static_cast<__box Int64*>((*static_cast<__box UInt64*>
(((((red << 0x10) | (green << 8)) | blue) | (alpha << 0x18))) & 0xffffffff));
}
If you don't know what Bit-wise operators are or if you have some problems regarding them, read this article on CodeProject - an excellent place to start with.
First, we need to convert the hexadecimal numbers &H10 etc. and octal numbers like 0x10, etc. to human-readable decimal form.
Then, the statements will be:
CLng((CULng(((((red << 16) Or (green << 8)) Or blue) _
Or (alpha << 24))) And 4294967295))
(long) (((ulong) ((((red << 16) | (green << 8)) | blue) |
(alpha << 24))) & 4294967295L))
*static_cast<__box Int64*>((*static_cast<__box UInt64*>(((((red << 16) |
(green << 8)) | blue) | (alpha << 24))) & 4294967295)
Let us analyze this expression. I will take the C# expression for analyzing.
((((red << 16) | (green << 8)) | blue) | (alpha << 24)) & 4294967295
red
is left shifted 16 bits.
green
is left shifted 8 bits.
- Both the results are OR-ed.
- The OR-ed result is again OR-ed by
blue
alpha
is left shifted 24 bits.
- Then it is OR-ed with the result in (4).
- The result in (6) is AND-ed with
4294967295
Let us take an example to understand it better.
Color : Gold
Alpha : 255 (11111111)
Red : 255 (11111111)
Green : 215 (11010111)
Blue : 0 (00000000)
Red << 16 :
16711680
111111110000000000000000
Green << 8 :
55040
1101011100000000
(Red << 16) | (Green << 8) :
11111111 00000000 00000000 (Red's value shifted to first 8 bits)
00000000 11010111 00000000 (Green's value shifted to second 8 bits)
_________________________
11111111 11010111 00000000 (OR-ed result)
Notice that the first eight bits are occupied by red and
the second eight bits are occupied by green.
((Red << 16) | (Green << 8))|Blue :
11111111 11010111 00000000 (The last 8 bits are for Blue)
00000000 000000000 0000000 (The last 8 bits are Blue. In this case, 0)
_________________________
11111111 11010111 00000000 (These 3 values are Red, Blue and Green)
Alpha << 24 :
4278190080
11111111000000000000000000000000
(((Red << 16) | (Green << 8))|Blue) | Alpha << 24 :
00000000 11111111 11010111 00000000 (24-bit to 32-bit leaving the first 8 bits for Alpha)
11111111 00000000 00000000 00000000 (First 8 Bits have the value)
__________________________________
11111111 11111111 11010111 00000000 (Alpha, Red, Green, Blue)
((((Red << 16) | (Green << 8))|Blue) | Alpha << 24) & 4294967295 :
11111111 11111111 11010111 00000000
11111111 11111111 11111111 11111111 (256^4)
___________________________________
11111111 11111111 11010111 00000000
| | | |
ALPHA RED GREEN BLUE
BITS 32-24 24-16 16-8 8-0
Finally, we AND by 256^4 because there are 4 values with range 0-255(256 values)
Note: This isn't the value returned by Color.ToArgb()
function because it returns an Integer
value which is a conversion of a Long
value. Since the limit exceeds, a negative value is returned.
Retrieving the Components from an ARGB Value
ALPHA
Public ReadOnly Property A As Byte
Get
Return CByte(((Me.Value >> &H18) And &HFF)) End Get
End Property
public byte A
{
get
{
return (byte) ((this.Value >> 0x18) & 0xffL); }
}
public: __property Byte __gc* get_A()
{
return *static_cast<__box Byte*>(((this->Value >> 0x18) & 0xff));
}
value
is right shifted 24
bits and is AND-ed with 255
to get the Alpha
.
11111111 11111111 11010111 00000000 >> 24 = 00000000 00000000 00000000 11111111
00000000 00000000 00000000 11111111 & 255 = 11111111 = 255
The same explanation applies to the following.
RED
Public ReadOnly Property R As Byte
Get
Return CByte(((Me.Value >> &H10) And &HFF))
End Get
End Property
public byte R
{
get
{
return (byte) ((this.Value >> 0x10) & 0xffL);
}
}
public: __property Byte __gc* get_R()
{
return *static_cast<__box Byte*>(((this->Value >> 0x10) & 0xff));
}
Red = value >> 16 & 255
BLUE
Public ReadOnly Property B As Byte
Get
Return CByte((Me.Value And &HFF))
End Get
End Property
public byte B
{
get
{
return (byte) (this.Value & 0xffL);
}
}
public: __property Byte __gc* get_B()
{
return *static_cast<__box Byte*>((this->Value & 0xff));
}
Since blue
wasn't shifted, AND-ing it with 255
will give blue
.
blue = value & 255
GREEN
Public ReadOnly Property G As Byte
Get
Return CByte(((Me.Value >> 8) And &HFF))
End Get
End Property
public byte G
{
get
{
return (byte) ((this.Value >> 8) & 0xffL);
}
}
public: __property Byte __gc* get_G()
{
return *static_cast<__box Byte*>(((this->Value >> 8) & 0xff));
}
green = value >> 8 & 255
That is all!