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 colors.codes[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 colors.codes[0] to colors.codes[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 colors.codes[0] color, the D
character will have the colors.codes[1] color, and the G
character will have the colors.codes[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 colors.codes[0]-to-colors.codes[1] transition whereas the E
and F
characters are waypoints along the colors.codes[1]-to-colors.codes[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.ABCDEFG
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 colors.codes[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 colors.codes[0] and colors.codes[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 colors.codes[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 colors.codes[1] and colors.codes[2] colors.(G) The
G
character's lci and hci indexes are both 2 per its alignment with the colors.codes[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.
Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)