reptile7's JavaScript blog
Friday, April 18, 2014
Stations of Color
Blog Entry #317

Welcome back to our analysis of the Java Goodies "Color Gradient Text" script. We will now go through the rest of the gradient( ) function and thereby apply a thecolors color spectrum to some thetext text. For the discussion that follows, we will work with
(a) a simplified ABCDEFG thetext string,
(b) the first gradient( ) call's ff0000 ffffff 0000ff thecolors string, and
(c) the[c] data structure as opposed to the split( ) structure I gave you at the end of the previous post.

A graphical view of the effect

For the thetext and thecolors strings given above, imagine a character axis running from A to G and a parallel color axis running from[0] to[2]:

The character axis comprises seven characters and six character-to-character transitions whereas the color axis comprises three colors and two color-to-color transitions. We will create the script's gradient effect by merging these axes.

You can see that the A character will have the[0] color, the D character will have the[1] color, and the G character will have the[2] color, i.e., the rendered A, D, and G will be pure red, pure white, and pure blue, respectively.

With respect to the color axis, the B and C characters are waypoints along the[0][1] transition whereas the E and F characters are waypoints along the[1][2] transition. The rendered B will be a reasonably dark but not pure red and the rendered C will be a pale red; complementarily, the rendered E will be a pale blue and the rendered F will be a reasonably dark but not pure blue.


gradient( ) guts

The gradient( ) function sets the thetext character colors with the help of three external functions - lowcolorindex( ), hicolorindex( ), and interpolate( ) - whose code is not so easy to grok: I'll explain it as best I can.

With the colors data structure in hand, gradient( ) registers
(a) the number of colors represented by the colors object and
(b) the number of characters composing the thetext string:

var numcolors = colors.len;
var numchars = thetext.length;

Next, gradient( ) declares a series of variables that will track our progress along the color axis:

var rr = 0;
var gg = 0;
var bb = 0;
var lci = 0; // Lower color index
var hci = 0; // Higher color index

We are at long last ready to color the thetext characters. The coloring action is coordinated by a numchars-iteration for loop:

for (i = 0; i < numchars; ++i) { ... }

The i counter will double as a thetext character index. The loop first calls lowcolorindex( ) and hicolorindex( ) functions

lci = lowcolorindex(i, numchars, numcolors);
hci = hicolorindex(i, numchars, numcolors, lci);

that locate each thetext character with respect to the color axis; the i index and the numchars and numcolors lengths are passed to both functions, for which they are renamed x, y, and z, respectively. The lowcolorindex( ) return, lci, is also passed to hicolorindex( ) although it doesn't need to be, as we shall see. The lowcolorindex( ) and hicolorindex( ) functions are given below:

// x = Index of letter, y = Number of letters, z = Number of colors function lowcolorindex(x, y, z) { if (y == 1) return 0; else return Math.floor((x * (z - 1)) / (y - 1)); }

function hicolorindex(x, y, z, low) { if (low * (y - 1) == x * (z - 1)) return low; else if (y == 1) return 0; else return Math.floor((x * (z - 1)) / (y - 1) + 1); }

Let us normalize the length of the color axis to 2 as it contains two color-to-color transitions. Each character-to-character transition - each increase in x - moves us (z - 1) / (y - 1) along the color axis.

The i = 0 loop iteration is for the A character and signifies that no movement occurs along either axis. As i is 0, the lowcolorindex( ) Math.floor((x * (z - 1)) / (y - 1)) operation gives 0, which is returned and assigned to lci. As i and lci are 0, the hicolorindex( ) low * (y - 1) == x * (z - 1) if condition is true, and therefore low (0) is returned and assigned to hci.

The lowcolorindex( ) and hicolorindex( ) functions are set up to return the same value whenever a thetext character horizontally lines up with a colors color. The A character horizontally lines up with the[0] color and its lci and hci indexes are both 0 in reflection of this alignment.

The B and C characters are respectively handled by the i = 1 and i = 2 loop iterations. For these characters the lci index is 0 and the hci index is 1 (⅓ and ⅔ are floor( )ed to 0, 1⅓ and 1⅔ are floor( )ed to 1), meaning that B and C are situated between the[0] and[1] colors.

The i = 3 iteration brings us to the D character, whose lci and hci indexes are both 1 - the x * (z - 1)) / (y - 1) calculation yields 1, the low * (y - 1) == x * (z - 1) if condition is true - per D's alignment with the[1] color.

At this point you should be able to intuit that:
(E-F) The E and F characters have a 1 lci index and a 2 hci index as they are situated between the[1] and[2] colors.
(G) The G character's lci and hci indexes are both 2 per its alignment with the[2] color.
You can work through the math if you want but it really isn't necessary.

Let's get back to the hicolorindex( ) low * (y - 1) == x * (z - 1) if condition for a moment. As intimated above, this condition flags those thetext characters that horizontally line up with colors colors. As it happens, the low term is superfluous. The x character will line up with a color whenever the x * (z - 1) product is a multiple of (y - 1) - e.g., if we were working with a 26-character thetext string and a 6-color colors object, then the x character would line up with a color for x = 0, 5, 10, 15, 20, and 25 - and we can therefore flag the alignments with an x * (z - 1) % (y - 1) == 0 condition. Wikipedia provides a detailed treatment of modulo operations here.

Is there a need for separate lowcolorindex( ) and hicolorindex( ) functions? Not in my book. We can recast the lowcolorindex( )/hicolorindex( ) functionality as:

if (1 <= (numchars - 1)) { // If we have at least one character-to-character transition lci = Math.floor(i * (numcolors - 1) / (numchars - 1)); hci = i * (numcolors - 1) % (numchars - 1) == 0 ? lci : Math.floor(i * (numcolors - 1) / (numchars - 1) + 1); }

The 1 <= (numchars - 1) if condition allows us to throw out the if (y == 1) return 0 statements in the original functions; recall that lci and hci are initialized to 0. The ?: conditional operator is documented here in the Mozilla JavaScript Reference.

We'll tackle the interpolate( ) function and wrap up our discourse on the Color Gradient Text script in the following entry.

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)