You are here: irt.org | Articles | VBScript | un-Gratuitous Gradients [ previous next ]
Published on: Tuesday 31st August 1999 By: Ryan Detert
un-Gratuitous Gradients |
Originally, this article dealt with how to make a string appear gradiated. However, the previous method used good technology, but it was not applied very well. In this updated article we will apply the same kind of technology but instead we will alter the background colors of tables to create a fading bar of some sort. You may find other uses for this script with a little bit of creativity as it pertains to your specific need.
How Color Works |
In case you don't already know, your monitor displays everything via pixels, which are tiny dots on the screen that are composed of a red, green, and blue part. In fact, these colors are the also the primary colors of our color spectrum. ALL colors that we see are composed of a mixture of wavelengths of red, green, and blue light. In order to color in a pixel, we are able to adjust the intensities of the red, green, and blue parts of the pixel to form different colors. For example, if all of these parts are turned off the pixel is black, if all of them are on the pixel is white, and if only the blue part of the pixel is turned on the pixel will be blue.
Hexadecimal Number System |
The hexadecimal numbering system is a base 16 number system, unfortunately, we all use a base 10 number system. What does this mean you ask? Well, all that it means is that you have six extra digits to deal with. In EVERY number system that you will ever see, they ALL work the same way, except you write them using digits from either a wider or narrower variety. In the base 10 numbering system, decimal, we use the digits from 0-9 to write all our numbers. In base 16, hexadecimal, we use the digits of 0-F. Where A=10 decimal, B=11 decimal, C=12 decimal, D=13 decimal, E=14 decimal, and F=15 decimal. How do I count in this strange number system? Why the same way that you count in decimal of course except you have to use ALL the digits.
So instead of counting like this in decimal: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20...
We will instead count in hexadecimal in this manner: 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14...
Reading Hex |
Reading hexadecimal numbers is done the same way that you read in decimal, although you may not realize it. In the decimal system that we use, everything is a power of 10, even though we don't really think of it in this way.
For example, the number 16345 in decimal is really:
1 x 104 + 6 x 103 + 3 x 102 + 4 x 101 + 5 x 100 = 16345
So, in hexadecimal, this number would be 3FD9 (and remember that F=15 decimal and D=13 decimal), which may also be expressed as:
3 x 163 + F x 162 + D x 161 + 9 x 160 = 3FD9
Amazing! So now all of you have a general idea of the hexadecimal numbering system and you all realize that there is no difference really between our decimal system and the hexadecimal numbering system or any other number base system. Computer gurus have decided on making hexadecimal the standard for computer languages and such even though the computer itself operates in a base 2 system, which we'll discuss later. If you have ever done any lower level programming you will realize that hexadecimal is easier to read because it also allows us to write huge decimal numbers using fewer digits. I will not go into how to convert between bases because it is beyond the scope of this tutorial and all that you really need is a calculator for converting and an understanding of what hexadecimal is.
Hexadecimal & Pixels |
Well in most programming languages, even HTML, there is what is known as an RGB triplet, which is how we assign color values to the red, green, and blue components of a pixel. An RGB triplet, for our purposes, will be 3 bytes, which is 1 byte for each component of the RGB triplet. Notice that since there are 8 bits in a byte, an RGB triplet is also 24-bit color. The range of a single byte is from between 0-255 in decimal, which is equivalent to 0-FF in hexadecimal. In HTML you may have seen something like this, <font color="#FF0000">, well the color value is actually an RGB triplet that is being read in hexadecimal by the computer. Notice that it is 6 digits long, an RGB triplet CANNOT be more than six digits long because it can only contain 3 bytes. In this HTML RGB triplet, '#FF12AC', the first two digits, 'FF', represent the intensity of the red component of the pixel, which is at full strength because a byte cannot be greater than FF. The next two digits, '12', represent the intensity of the green component of the pixel, it is fairly low. The last two digits, 'AC', represent the intensity of the blue part of the pixel, it is fairly high. The end result is a pixel being displayed that is a kind of pink.
Please note however that in most programming language, and JavaScript, hexadecimal number MUST be preceeded by an '0x'. This is so the computer will not confuse decimal with hexadecimal. So to write the previous RGB triplet using JavaScript, we would go 0xFF12AC. As an example of how to use RGB triplets in JavaScript we can make the background color of a page pink by going:
document.bgColor = 0xFF12AC;
By using an RGB triplet, we are able to use 256 shades of both red, green, and blue to make our colors, this works out to be somewhere around 16.4 million colors!
Gourad Shading |
Now that we have a general idea of how pixels and color work, let's begin to talk about how on earth we are going to write a string that has a gradient like the title of this tutorial. One common remedy for this is known as GOURAD SHADING. This is a fairly simple technique that is used often in 2-Dimensional graphics programming. All that you have to do is assign two variables a starting color and an ending color and then find out how many step increments it will take to change the first color into the second. It can be represented like this:
(secondcolor - firstcolor) / number_of_steps;
The variable 'secondcolor' is the end color and the variable 'firstcolor' is the starting color. The variable 'number_of_steps' will be the number of cells in our table and we divide by this variable in order to determine how much to increment the pixel intensities per cell background.
Beginning The Transition |
Although Gourad Shading is a fairly simple concept, we need to take one MAJOR thing into account. We MUST transition EACH of the red, green, and blue pixel components individually. If we were to merely subtract red(0xFF0000) and blue(0x0000FF) and then divide by some number, the color transition steps would not work correctly according to the physics of our color spectrum and we would end up with some pretty crumby results. Thus, we have to separate each of the red, green, and blue components of our RGB triplet and then increment each component by the same amount individually.
You may be wondering how on earth you are going to do this! Well it involves a more advanced technique known as bit-masking. Bit-masking is the art of taking away all of the bits that we don't want or don't need.
Bit Masking |
All information stored in the computer is in a binary (base 2) format, which means that the only digits that you are allowed to use are either 1 or 0. (To an electrical engineer, a 1 may mean a high charge and a 0 a low charge but just know that the computer operates in binary) So the number 20 in decimal, 0x14 in hexadecimal, is 10100 in binary. Like hexadecimal, we also count the same in binary as we do in hexadecimal or decimal, so counting from 0 - 10 would be like so:
0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010
Also notice that ten in binary, 1010 is also:
1 x 23 + 0 x 22 + 1 x 21 + 0 x 20 = 10
JavaScript, C, and just about all other real programming languages allow you to get rid of bits (binary numbers) that you don't want.
There are three basic bit-mask operators. They are the AND(&), OR( | ), and XOR(^) operators. Here is a table of how they work
AND |
OR |
XOR |
1 & 1 = 1 | 1 | 1 = 1 | 1 ^ 1 = 0 |
1 & 0 = 0 | 1 | 0 = 1 | 1 ^ 0 = 1 |
0 & 0 = 0 | 0 | 0 = 0 | 0 ^ 0 = 0 |
So if I want to get rid of the first three 1's in the binary number 11101011 you would go:
11101011 |
AND 00001011 |
00001011 |
Conversely, if I wanted to attach the three 1's to the front of the binary number 00001011 you would go:
00001011 |
OR 11100000 |
11101011 |
Now wait a dog-gone minute here, we can't use straight binary in JavaScript. Well guess what, we don't need to! Since all number systems work in exactly the same way, we can translate the binary numbers to hexadecimal or decimal on a sheet of paper and then get the same results using regular decimal or hexadecimal. So in decimal the first example would look like 235 & 11 = 11 and 0xEB & 0xB = 0xB in hexadecimal. Pretty cool huh!
Bit Shifting |
Before we can fully separate the components of the RGB triplet, we need to cover one more important topic, bit-shifting. All that this does is literally shift the bit right or left. In JavaScript, there is a right-shift operator(>>) and left-shift operator (<<). Note that these operators work with 32-bit numbers and they fill in any gaps that may result with zeros. Here are some examples using bit-shifting in binary:
As it looks in binary:
|
As it looks in hexadecimal:
|
Note that a single left shift is a quicker way of multiplying by 2 while a single right shift is a quicker way of dividing by 2 also.
Separating The RGB Triplet |
Throughout this section, we will use 0xAABBCC as our RGB triplet. First, we need to get the red portion of the triplet. Since a full strength red pixel is 0xFF0000, we need to get rid the 'BBCC' in our triplet. We do not have to deal with bit masking to get the red component, all that we need to do is shift out the 'BBCC'. This can be accomplished by using a right-shift operator. However, like in the rest of the tutorial, there is a trick that is easy to miss. We cannot simply shift 0xFF0000 four places to the right like below because it doesn't work mathematically.
0xFF0000 >> 4 = 0x0FF000
This is because the shift operators shift individual bits, and there are eight bits in a byte, so the above example is only shifting 0xFF0000 half a byte, and we want to shift it two whole bytes, or 16 bits. So the correct way to separate the red component in our RGB triplet is by going:
0xAABBCC >> 16 = 0xAA;
To find the green component of the triplet, we need to incorporate both or knowledge of bit-masking as well as bit-shifting. In our triplet, we want only the middle two digits, now how on earth are we going to do this? Well, since we know that anything AND'ed with 0 equals 0, we can begin by doing this:
0xAABBCC & 0x00FF00 = 0x00BB00
However, this number is too large so we must shift it right 1 byte by going:
(0xAABBCC & 0x00FF00) >> 8 = 0xBB
To get the blue component of the RGB, all that we need to do is cancel out the first two bytes, which can be done by going:
0xAABBCC & 0x0000FF = 0xCC;
Gourad Shading 24-bit Color |
After separating the RGB triplet into its three components, we must then calculate how much to increment EACH part of the RGB. It will be done as follows:
RedStep = (endRed - startRed) / numberCells
GreenStep = (endGreen - startGreen) / numberCells
BlueStep = (endBlue - startBlue) / numberCells
Using Tables |
Finally, we begin to discuss the table that we would like to transform into a gradient. In order to do this, our general strategy will be to write each data cell individually to the document and alter the bgcolor attribute of each cell accordingly.
When writing to a table using IE 4+, in order to display properly, there does not need to be anything inside the data cells as long as you specify the cell's width in pixels and not a percentage. However, Netscape requires that you have some "hard code" in the table such as a space ( ) character, for the table to display properly. Unfortunately, if you use a space the height of the table is limited to the height of the smallest font size. To overcome this, we will put a bogus image inside each table cell for users other than IE 4+ and set the picture's dimensions to 1x1 pixels. This will allow the user to adjust the height of the table to any value and will also fool browsers into thinking that there is actual data in the cells. Unfortunately, this causes a minor set back. There will be tiny dots in the gradient from the empty pictures, however, uless the bar is a pixel high, this should go unnoticed by the client. In IE 4+ we will fool the browser a different way by placing a 1x1 pixel borderless table in each cell. This will ensure that the gradient displays properly. In addition, there won't be any chance for annoying dots to occur.
To write the table to the document we will use the document.write(); method. So before be begin looping through the color schemes we need to initialize the table by making it borderless, defining its dimensions, and removing spacing between cells:
document.write("<TABLE WIDTH=\"100%\" BORDER=0 HEIGHT=", variable, "CELLSPACING=0 CELLPADDING=0><TR>");
The looping section of the following code is really the heart of the script as it writes new data cells every loop while it increments the cells' background colors accordingly. As you will soon see in the finished code we first increment the RGB components, then merge them and convert them to hexadecimal, and finally write the data cell to the screen with a different background color. The code will look something like this for Netscape users (remember the bogus pic):
document.write("<TD ALIGN=\"RIGHT\" VALIGN=\"BOTTOM\" WIDTH=", colWidth, " bgcolor=\"", color, "\">>img src=\"pix.gif\" border=0 width=1 height=1></TD>");
And something like this for IE 4+ users:
document.write("<TD ALIGN=\"RIGHT\" VALIGN=\"BOTTOM\" WIDTH=", colWidth, " bgcolor=\"", color, "\"><TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 HEIGHT=1 WIDTH=1></TABLE></TD>");
After all of the cells have been implemented all that needs to be done is close off the table like so:
document.write("</TR></TABLE>");
Now in order for this to work, we must first convert our background color variable to hexadecimal every time and we must also add a pound sign (#) in front of it as HTML syntax dictates. Note that the RGB triplet MUST be in hexadecimal form to function properly.
Converting Back To Decimal |
Before we begin to convert the RGB triplet to hexadecimal, since we alread tore apart the triplet, we have to put it back together temporarily. This is an easy task that can be accomplished with the bit-mask OR operator and some simple left bit-shifting to get all of the bits to fall into place:
var merged = (red << 16) | (green << 8) | blue;
Basically, to convert from base 16 to base 10 you just need to divide the original number by 16, save the remainder, and then continue to divide its remainder until it can be divided by 16 no more. Fortunately, JavaScript has a built in number conversion function that will suit our needs. It is the toString() (implemented in Navigator 2.0) function. This will convert decimal (base 10) numbers into any base number between 2 and 16 and return the conversion in the form of a string. So, to convert the number 234 into a base 16 string we must go:
(234).toString(16)
Final Trick |
Okay, now that we have gone through all of this work, there is a minute detail that we must not overlook. It is such a small detail that even I had trouble catching it. An RGB triplet in HTML must be 6 digits in hexadecimal. If it is not, the browser will append the necessary amount of zeros needed to make the triplet 6 digits. Notice that in hexadecimal, 0x001234 is the same as 0x1234, but when dealing with RGB triplets in HTML, the browser will transform #1234 into #123400 instead of #001234, as one may think. What is the reasoning behind this you might ask? Well, let's say that we start at the color black, which is 0x000000 and increment each component by 0x09 which yields 0x090909. However, our hexadecimal conversion function will return the number with only 5 digits like so 0x90909, so when we try to place this number inside the color attribute of the font tags we will end up with this result:
<font color="#909090">string.charAt(index)</font>
This isn't what we want, instead, we want the trailing zero to be placed at the beginning of the triplet. Fortunately this problem is easily solved by attaching a pound sign and the appropriate number of preceeding zeros to the number that our hexadecimal conversion function returns like so:
var FullRGB = "#000000"; color = hex(merge(red, green, blue)); color = FullRGB.substring(0, 6 - color.length + 1) + color;
The Finished Code |
function protocol:
<SCRIPT LANGUAGE="JavaScript"><!-- var h="0123456789ABCDEF" var Fullrgb = "#000000"; function merge(r, g, b){ return (r << 16 | g << 8 | b); } function gradient(startcolor, endcolor, numCol, colWidth, rowHeight) { var color; var start_red = startcolor >> 16; var start_green = (startcolor & 0x00FF00) >> 8; var start_blue = startcolor & 0x0000FF; var end_red = endcolor >> 16; var end_green= (endcolor & 0x00FF00) >> 8; var end_blue = endcolor & 0x0000FF; var incRed = Math.floor((end_red - start_red) / numCol); var incGreen = Math.floor((end_green - start_green) / numCol); var incBlue = Math.floor((end_blue - start_blue) / numCol); document.write("<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=\"100%\" HEIGHT=", rowHeight, "><TR>"); for(var x=0; x < numCol; x++) { if(start_red + incRed >= 0x00 && start_red + incRed <= 0xFF) start_red += incRed; if(start_green + incGreen >= 0x00 && start_green + incGreen <= 0xFF) start_green += incGreen; if(start_blue + incBlue >= 0x00 && start_blue + incBlue <= 0xFF) start_blue += incBlue; color = (merge(start_red, start_green, start_blue)).toString(16); color = Fullrgb.substring(0, 6 - color.length + 1) + color; if(navigator.appName.toLowerCase().indexOf("explorer") > -1 && parseInt(navigator.appVersion) >= 4) document.write("<TD ALIGN=\"RIGHT\" WIDTH=", colWidth, " bgcolor=", color, "><TABLE BORDER=0 WIDTH=1 HEIGHT=1 CELLPADDING=0 CELLSPACING=0></TABLE></TD>"); else document.write("<TD ALIGN=\"RIGHT\" VALIGN=\"BOTTOM\" WIDTH=", colWidth, " bgcolor=", color, "><img src=\"pix.gif\" border=0 width=1 height=1></TD>"); } document.write("</TR></TABLE><NOBR>"); } //--></SCRIPT>
Here are some examples of how to use this script with the code featured below.
gradient(0xFF0000, 0xFFFFFF, 12, "10%", 8);
gradient(0xFFFFFF, 0xFF, 100, 8, 3);
Creating this blended effect is by no means a simple task, but by using your knowledge of bit-masking, bit shifting, the hexadecimal number system, and the gourad shading technique it is possible. First, you must select a starting color to blend and then select an ending color to blend into. Then separate each part of the RGB triplet into its red, green, and blue components, and then calculate the change in the starting color components and the ending color components over a finite length, we will call them (delta)red, (delta)green, and (delta)blue. Then increment each component of the starting color by its (delta)counterpart while displaying each table cell individually as we go along. So have fun messing around with the color schemes and amazing your friends.
Browser Redirection using VBScript
Rattling Keys and Chasing Mice With VBScript
Introduction to Visual Basic Scripting (VBScript)