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.