Tubefy Explained, part II:
Rounding Colors

This article is based on the [ first Tubefy article ]

When I first experimented "Tubefy", I was as happy as a kid with a new toy.
I enjoyed gradients I could never produce before, played with it for hours at a time and made countless experiments. I especially liked the effect on font strokes:

But rather quickly I started to feel uncomfortable.
Something was missing. It took me some time to find out what.

What I expected to look like a tube, was actually looking more like a neon tube or a slightly rounded triangular prism but did not *feel* like a solid tube.

Thinking about it, I should have expected just that.
If I use lerp, which is a linear (straight line) interpolation, to gradient the colors, the results will most probably look... well, linear. Simple.

It was obvious at that point that the result colors must be "rounded" some how.
But how exactly do you "round" colors?

Well, the solution came from another area of graphics on web pages - animation, in particular - motion.

Consider animation of an object moving from one point to another on the screen in the shortest way possible (straight line) and constant speed.
The same way we calculate the gradient colors by "walking" between two colors, the animation routine calculates each new position of the moving object by "walking" between two points on the screen. We lerp color components - RGB, and the animation is lerping XY coordinates (style.left/top).

Both use the *same* lerp function only the animation is using it with a timer.
Which brings us to a fundamental part of any motion - speed.

Early motion animations produced only constant speed movements and everybody was happy. But rather quickly the developers started to feel uncomfortable (sounds familiar?). What they expected to look like a natural motion looked artificial.
Thinking about it, there is actually no natural movement which starts and ends abruptly and move with constant speed all the way.
To make the motion feel more natural, they introduced the "easing" functions, which are basically lerp functions, but with a "twitch" that makes them non-linear.

At this point, I will not continue describing those functions because someone else has already produced what is considered *the* classic work on easing functions. This person is [ Robert Penner ] and though his work deals with Flash ActionScript, the resemblance to JavaScript and his good writing makes it easy to follow even if you are not familiar with Flash.
So I suggest you go and enjoy his [ Easing page ].
For the purpose of this article, I suggest you read major parts of the [ Tweening chapter of his book (pdf) ] and play with the [ Equations Visualizer ].

Before introducing any easing function to the Tubefy procedure, following graph demonstrate the current situation i.e. - colors are calculated linearly:

The upper stripe is a slice of the stroke Tubefied by the graphed function.

The actual graph is only the left half. The right part is created when each color is painted to a ring which is symmetrically spread on both sides of the skeleton.

As described in the [ first article ], the "p" argument (horizontal axis) is the part of the way between the edge (p=0) and the skeleton (p=1) and its values are the relative position of the ring we currently paint, out of all the rings we need to paint.

The left diagonal line is the lerp "mapping rule" between each ring and its color.
It's a straight line because lerp is linear.

Imagine vertical lines 1px apart (distance between rings), go from the horizontal axis up to the stroke stripe. Whatever shade of color is at the intersection of these lines with the function graph (the left diagonal) is mapped to where they hit the stroke stripe.

It's obvious that if we want the colors to be "rounded", the function graph must have a different shape, and this is where the easing function enters.

After experimenting with few easing functions, I have chosen the quad (parabola) ease out function. This parabola has the form f(p)=p(2-p) which is normalized to go through (0,0) - edge/blue intersection, (1,1) - skeleton/aqua intersection and has its flat (0 slope) maximum at (1,1) which ensures a smooth spread of colors around the skeleton:

Note the "p" argument passed to the lerp function is first manipulated by the easing quad. This is the *only* change from the original Tubefy.

As the quad curve is higher than the linear curve, it intersects colors which are *closer* to the skeleton color than the colors intersected by the linear curve.

Another way to look at it is like the quad curve draws higher colors *earlier* than if they were drawn by the linear curve.

And as the quad curve is smooth at the skeleton, so is the gradient produced by it.

In the following demo the upper worm Tubefied by the regular, linear lerp, and the lower by quad eased lerp:

Actually, the rounding task is completed. As a result we now have two ways we can tubefy a stroke, each with its own characteristic appearance.

But as I was experimenting the rounded tubes with different color combinations,
I noticed different "rounded" effects created by different color sets. While one color set gives a good tube feeling, another looks too flat. Besides, even if we get a good rounded effect, maybe we would like to give it some degree of metalic shine, perhaps not as sharp as linear but still some sort of specular touch.
The necessity of a way to control the amount of rounding became apparent.

To achieve this goal, a rounding factor "r" is introduced to the Tubefy function. Tubefy grabs this factor from a new orphan attribute "tubeR" in the stroke tag we wish to tubefy.

Tubefy use this factor as the "p" (part) argument to lerp from linear tubefy to "full" quad (p(2-p)) tubefy. If "r" is 0 (default), no rounding is done. If "r" is 1, rounding is done as before (p(2-p)). Anywhere between 0 and 1 is the amount of rounding.
More formally:

       if(r) p = lerp(r, p, quad(p));

Note "p" and "quad(p)" are now the interpolated points while "r" plays the part "p" is playing in the color lerp. The result is then assigned back to "p" to be used in the final color lerp.

Without getting too mathematical, lerping from linear line to the original p(2-p) quad, will always result in a new quad (parabola) which lies between the linear, straight line and the original quad. The new quad passes through (0,0) and (1,1),
but does *not* have its maximum at (1,1). Nevertheless, it is always more round than the linear line and having a parabola curvature, it creates pleasant results:

We now have countless ways to calibrate the amount of rounding:

Now for a litle experiment with the new easing toy, I have chosen an easing function I could not find in any animation library. It is a quad, but instead of using only one half of it up to the point where it meets the second color, we let it go all the way back to the first color. In simple words, it's a full quad.

It has the form f(p)=4p(1-p) so it passes through (0,0) - edge/blue intersection
and (1,0) - skeleton/blue intersection and has a maximum at (0.5,1) - halfway between edge-skeleton where it touches pure aqua:

And here is how it looks applied to the worm and some text:
(if you use FireFox and Alba font is not embed in your operating system,
you might see the text with different font (Sep2009))

[ Large ]
You can find more Tubefy experiments [ here ] and some of my 3D experiments
[ here ]. When time allows I will continue explaining Tubefy and how it is expanded
to provide also fill gradients which are not supported by the SVG/Canvas specs
(can be seen in some of the above mentioned Tubefy experiments) but for the moment articles will stick to stroke:

Next: Variable Stroke Width I: The simple way

Both Tubefy and Variable Stroke Width deal with stroke features and share the same philosophy: desired feature is not in the spec - create it.

Generally speaking, this series of articles present a set of methods to produce
the suggestions made by [ Doug Schepers ] in his [ article ] on the subject.
What you see below will be covered by the first article, which is more of an appetizer to the articles following it.

Copyright © 2009 Israel Eisenberg. All rights reserved.