reptile7's JavaScript blog
Sunday, April 27, 2014
 
Character Interlopers
Blog Entry #318

We have one more auxiliary function in the Color Gradient Text script to cover.

The interpolate( ) function

At the end of our last episode we had determined "lower color" (lci) and "higher color" (hci) indexes for the characters of the ABCDEFG string as regards the ff0000 ffffff 0000ff color spectrum via the lowcolorindex( ) and hicolorindex( ) functions. Returning to the gradient( ) function, the lowcolorindex( )/hicolorindex( ) calls are followed by three statements

rr = Math.round(interpolate(lci / (numcolors - 1), colors.codes[lci].r, hci / (numcolors - 1), colors.codes[hci].r, i / (numchars - 1)));
gg = Math.round(interpolate(lci / (numcolors - 1), colors.codes[lci].g, hci / (numcolors - 1), colors.codes[hci].g, i / (numchars - 1)));
bb = Math.round(interpolate(lci / (numcolors - 1), colors.codes[lci].b, hci / (numcolors - 1), colors.codes[hci].b, i / (numchars - 1)));


that call on an interpolate( ) function

function interpolate(x1, y1, x3, y3, x2) { if (x3 == x1) return y1; else return (x2 - x1) * (y3 - y1) / (x3 - x1) + y1; }

to determine new rgb(rr, gg, bb) color values for the ABCDEFG characters. (I warned you this would get hairy, didn't I?)

The interpolate( ) function is where all of the script's data comes together; it tells us whether the rr/gg/bb values change or don't change, and if they change by how much, as we move along the character and color axes.



Five arguments are passed to the interpolate( ) function, in source order:
(0) lci / (numcolors - 1), which is renamed x1;
(1) colors.codes[lci].(r|g|b), which is renamed y1;
(2) hci / (numcolors - 1), which is renamed x3;
(3) colors.codes[hci].(r|g|b), which is renamed y3; and
(4) i / (numchars - 1), which is renamed x2.

Theoretical analysis

The y1 values represent a starting thecolors color and the y3 values represent the following thecolors color, e.g., red and white, respectively; the y3 - y1 term in the interpolate( ) function therefore represents a thecolors color distance that we will traverse over the course of one or more for (i = 0; i < numchars; ++i) { ... } loop iterations.

The x2 variable measures the total movement along the character axis since the beginning of the loop, i.e., the total movement from the axis origin at the A character, whereas the x1 variable measures the corresponding movement along the color axis. At the same time, the x1 variable is a stand-in for the previous (most recently encountered) 'station' along the color axis whereas the x3 variable is a stand-in for the following station; when a character horizontally lines up with a station, x1 and x3 are equal. In the interpolate( ) function, the x2 - x1 term measures the movement along the character axis from the previous color axis station whereas the x3 - x1 term serves as a coefficient that converts the x2 - x1 distance to the corresponding distance along the color axis.

In practice

For the i = 0 loop iteration there's no movement along either axis, as noted earlier. As the A character's lci and hci indexes are both 0, x1 and x3 are equal and therefore the y1 r/g/b values for the colors.codes[0] station - 255, 0, and 0 - are returned and respectively assigned to rr, gg, and bb.

Over the next three iterations we will go from the ff0000 thecolors color to the ffffff thecolors color, or decimally a y3 - y1 transition from rgb(255, 0, 0) to rgb(255, 255, 255).

For the i = 1 iteration, we move ⅙ of the way along the character axis but there's no movement along the color axis in that the B character hasn't gotten to the colors.codes[1] station yet. As lci is 0 and hci is 1, the y1 arguments are per the interpolate( ) else clause shifted by (y3 - y1) × ⅙ ÷ ½ to give 255, 85, and 85 values for rr, gg, and bb, respectively.

For the i = 2 iteration and C character, we are ⅓ of the way along the character axis but are still stuck at the colors.codes[0] station as regards the color axis. As for B, C's lci is 0 and hci is 1, and therefore the y1s are shifted by (y3 - y1)*2/3 to give 255, 170, and 170 values for rr, gg, and bb, respectively.

The i = 3 iteration and D character bring us to the midpoints of the character and color axes. As D's lci and hci are both 1, the colors.codes[1] station's 255, 255, and 255 values are assigned to rr, gg, and bb.

Over the last three iterations we go from the ffffff thecolors color to the 0000ff thecolors color, or decimally a y3 - y1 transition from rgb(255, 255, 255) to rgb(0, 0, 255).

Let's pick up the pace:
• For the i = 4 iteration and E character, we are ⅔ of the way along the character axis and halfway along the color axis; as lci is 1 and hci is 2, the y1s are shifted to 170, 170, and 255, which are respectively assigned to rr, gg, and bb.
• For the i = 5 iteration and F character, we are ⅚ of the way along the character axis and halfway along the color axis; lci and hci are still 1 and 2, respectively, and the y1s are shifted to 85, 85, and 255, which are respectively assigned to rr, gg, and bb.
• The i = 6 iteration and G character bring us to the ends of the character and color axes; G's lci and hci are both 2 and the colors.codes[2] station's 0, 0, and 255 values are respectively assigned to rr, gg, and bb.

You may have noticed that in this example we didn't need to Math.round( ) the interpolate( ) returns, but let me assure you that the round( ) operations are indeed necessary in almost all other cases (without getting into the details, their removal doesn't bring the script to a halt but does change the script's effect, and not for the better).

An alternative interpolate( )

The interpolate( ) code does not really require its own function and could use some major help in the readability department. Here's how I would recast it:

var numcolortransitions = numcolors - 1;
var numchartransitions = numchars - 1;
var lowcolor = colors.codes[lci];
var hicolor = colors.codes[hci];
var charCoord = i / numchartransitions; // x2 in the original script
var colorCoord = lci / numcolortransitions; // x1 in the original script
var char_to_colorAxisConvert = (charCoord - colorCoord) * numcolortransitions; // (x2 - x1) / (x3 - x1) in the original script
rr = lci == hci ? lowcolor.r : Math.round(lowcolor.r + (hicolor.r - lowcolor.r) * char_to_colorAxisConvert);
gg = lci == hci ? lowcolor.g : Math.round(lowcolor.g + (hicolor.g - lowcolor.g) * char_to_colorAxisConvert);
bb = lci == hci ? lowcolor.b : Math.round(lowcolor.b + (hicolor.b - lowcolor.b) * char_to_colorAxisConvert);


Regarding the char_to_colorAxisConvert definition, hci - lci is 1 when hci and lci are not equal and therefore 1 / (x3 - x1) equals numcolortransitions.

To recap, here are the interpolate( )d values:

A: 255, 0, 0
B: 255, 85, 85
C: 255, 170, 170
D: 255, 255, 255
E: 170, 170, 255
F: 85, 85, 255
G: 0, 0, 255


And just how do we make use of the rr/gg/bb values? All will be revealed in the next entry, which will definitely conclude our discussion of the Color Gradient Text script.

Comments: Post a Comment

<< Home

Powered by Blogger

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