Tubefy Explained III:
The Normal Gradient


The normalGradient emerged as a byproduct while developing first phase of
[ Variable Stroke-Width ] It creates a gradient between any two colors from beginning to end of a stroke with stripes of same color always normal (perpendicular) to the stroke skeleton.

This article is to expand this capability from only two colors to any number of colors on the same stroke. The method to be developed assume colors are equally spread along the stroke length. The equivalent in a native gradient would be multiple stops with all stop-offsets equally spaced.

Before I continue, I think this article and the particular gradient described in it,
is a good place to mention [ Prof. David P. Dailey ]

David is the guy who coined the phrase "gradients which are neither linear nor radial" which I used in the first sentence of my first Tubefy article. I saw this phrase in [ The Edges of Plausibility ] presentation he gave at SVG Open 2008.

Few tech words. The term px, pix or pixel is used loosely to describe a single length unit of an element. Thus, if an element constructed with no specific units (px, pt, mm etc.), like all the demos in this article, and is not scaled at all, not even indirectly by any viewBox, then 1px will be equivalent to a single screen pixel.

Demo note: normalGradient clone paths, sometimes many hundreds of them. To avoid overheated SVG engine, some demos are not rendered on load but instead ask you to activate them onclick, thus spreading the work load and keeping the SVG engine happy.

I find it helpful to visualize the set of colors to be graduated, as separated 1px lines normal to the skeleton. First color at the first px of the stroke, last color on last px of the stroke, rest are spread equally between them:

[ Large ]

Note how n colors create n-1 sections and index of last color is also n-1.
Our task is to create inside each section a gradient between its two borders.

First, let's examine what we already have.
The tools we employed so far to graduate colors are the lerp and lerpA functions. A quick reminder from [ Lerping Colors ], the lerpA method is a simple "organizer" function to receive the two colors we want to graduate and hand over to lerp two equivalent components of these colors one pair at a time. Thus, the only reason for the existence of lerpA is because lerp cannot handle more than two numbers
in one call.

But now, lerpA has the same problem like lerp before, only with different entities:
it cannot handle more than two arrays (colors) at a time. We need to find for lerpA a similar "organizer" to provide it the same service it provides to lerp.

Because of this similarity and being one level up, I named the new method lerpAA.

First argument lerpAA needs is the data lerpA cannot handle i.e. - the list of all colors. We pass this data to lerpAA as a single object - array of arrays (colors)
(obvious now what AA stand for :)).
One good reason for sending the colors in an array is that array is ordered, so together with the colors, we also send information in what order we would like them to be painted on the stroke.

Second argument is the same "p", so far sent to lerpA, which is actually our current position on the stroke, expressed as part of the total stroke length.
Bear in mind "p", being *part* of the stroke length, is always in the range [0,1].
If you inspect under the hood the way "p" is calculated as the loop runs, you find
it is *always* 0 on first cycle and *always* 1 on last cycle, which ensures correct mapping of both start and end colors to their respective positions.

lerpAA task is to map "p" to its exact position among *all* colors, not just two.

If we concentrate on the colors indices in the colors array being simple integers, what we have is a range of numbers which *always* start with 0 (index of first member of any array) and ends with an integer which is 1 less than the total number of colors (that's because first index is 0 and not 1). So the actual task of lerpAA boils down to mapping from a range of [0,1] (the range of "p"), to the range of the colors indices, for n colors, this range is [0,n-1].
You can clearly visualize this range on the above demo.

Mapping from [0,1] to [0,n-1] range is a simple scaling, or in simple words,
a multiplication with the multiplier being n-1 i.e. - to map "p" to its exact place in the range of n colors, all we need is to multiply it by n-1.

Now, for lerpAA particular job, the real amazing quality of this mapping is that the scaled "p" carry on *itself* all the information lerpAA need to send to lerpA:

- Its integer part is the index of the start color.
- array Indices being consecutive integers, the index of the end color is just
  the start color index + 1
- Its decimal part is its exact position between start and end colors, a direct
  outcome from our basic assumption of equally spaced colors.

Thus, lerpAA job is done by a single multiplication! rest is just breaking the result
to its integer/decimal parts and send them to lerpA.

Time to kick tires. Here is the house worm dressed with all hues of the rainbow.
It lost some weight so we can demo more of it without a mile long page:

[ Large ]

Under the hood: First two arguments of normalGrad are the stroke owner and the colors array, both mandatory. Rest of the arguments are optional.

The "n" argument is how many sections we want to split the stroke to, and is what dictates the granularity of the gradient. If omitted, it defaults to the stroke length which in most cases is too much so I recommend to provide this argument.
Start low and increase until you are satisfy with the granularity of the results.

On the other hand, keeping "n" low, create solid patterns like worms 2 and 4.
Sending as "n" the number of colors will create equivalent number of sections. lerpA doing its job regardless of the number of sections, thus assign each section its ordered color from the colors array.

The "addPix" argument, as its name implies, add pixels to the dash width.
The basic width of the dash is the width of one section so theoretically, if the dash array offset is accurate, there should be no visible seams between any two dashes. In practice those seams are visible most of the time. Though slightly and in most cases only part of them, but still visible.
A 1px additional width, which is the default of "addPix", is sufficient to hide those seams in the great majority of cases so usually, you don't need to bother with it.
Nevertheless, there are cases where 1px is not enough, like very sharp curves or very wide strokes, so just add pixels until the seams disappear.
As the dashes lie one on top of its predecessor, and as dashes preserve the precise shape of their stroke, those additional pixels cannot interrupt the overall appearance of the stroke and just do what they suppose to do: hide visible seams.

Note one of the browsers is problematic rendering seams between any two stroked curves, not just dashed curves , so you might find yourself adding pixels just for this particular browser.

Of course "addPix" is not limited to adding pixels but can also subtract them, thus thinning dashes to create some interesting effects, like the following moire patterns:
When all major browsers support thin dashes of curved strokes properly, we could further experiment in this direction. Click to animate, wait till finish to click again.

[ Large ]

First extension to the basic tool is what we already tried in [ vsw ] - multiple copies of the pattern. In this case, repeated sets of all the colors. In vsw we achieved it by letting more dashes enter the stroke. This method is susceptible to rounding errors so a preferred method would be to just multiply the colors and let lerpAA do the rest. Yet, if high number of patterns involved, pre-calculating the slices is needed.

Click square/circle at top left to switch round/butt line caps. Click worm right part to double the number of patterns and left to half it.

[ Large ]

As sometimes happens, I create a tool for a particular task, and when task accomplished, I discover that the opposite of the goal is not less interesting than the original goal.
I introduced the "n" argument to control granularity with a goal of achieving finest grain, and discovered coarse grain, which turn out to be a source of countless solid patterns. But only when the "repeat" argument introduced, a sufficient control over these patterns achieved.

Following demo pre-calculate the number of slices, guided by the desired size of a square and how many of them we want on each slice:

[ Large ]

Next demo manipulate the colors to get a normal pattern in addition to the length pattern. Click to activate then click to toggle zoom:

[ Large ]

Last two demos mimic Tubefy by drawing clones on top of wider predecessors.

The type of path still not handled by normalGrad is closed path.
In order to get a smooth transition at the end/beginning of the stroke, the rule of thumb is simple - after all "repeat" colors manipulation done, push a copy of the first color to the end of the colors array.

With all tweaking arguments now set, next demo is as easy as writing one path.
4 fold pattern, smooth connection and the 3D effect are all taken care of by normalGrad. Click to activate then click to move colors:

[ Large ]

Which is not the case with this demo. To achieve the 3D effect here, sub-paths must be assembled in a certain order:

[ Large ]

Though normalGrad is already a pretty capable method, it cannot handle all cases. In the following demo, if a star is drawn without normalGrad, all vertices will have the miter linejoin because last command is z. As we apply normalGrad, it breaks the stroke to dashes and lose the miter linejoin of the first vertex.

To fix it, we add the first section to the end of the path, thus making the original first vertex a linejoin instead of a linecap, and we notify normalGrad we now want the pattern to repeat 6 times instead of 5.

Note how this new section change the 3D configuration of the right star.
Click to activate then click to move colors on right star:

[ Large ] [ Large right ]

I thought to introduce also a demo, not just technical but also useful :)
Hues are circular (360=0) which makes circular presentation self requested.
Relate to this demo, you might want to visit David [ Of rainbows and doughnuts ] post on svg-developers forum, one more reason why I wanted David to be present in this particular article.
Click to activate then mouseover the hues disc:
Under the hood: Note how simple it is to raise the face of this gadget with Tubefy.

[ Large ]

Time to experiment normalGradient combined with other tools in our arsenal.

Following demo is a hybrid of normalGradient with variable stroke-width.
Basic idea is to let normalGradient follow its own grain while vsw maintain finest granularity for smooth edges:

[ Large ]

As sometimes happens, I create a tool for a particular task, and when task accomplished, I discover...
I would like to return to that point and stretch solid patterns in a different direction this time.

Following 3 demos graduate each stripe of the pattern individually along the stroke. The colors array is the pallet and the "pats" (patterns) arrays are instructions how to use this pallet on each stripe of the pattern, numbers are indices of the colors in the pallet.

For example first demo below, left conch pattern - [ [0,1], [2,3], [4,5] ] means:
First stripe of the pattern goes *gradually* from color 0 (red) at the beginning of the stroke to color 1 (yellow) at the end of the stroke. Second stripe goes from 2 (lime) to 3 (aqua), third stripe goes from 4 (blue) to 5 (magenta). The pattern advance in groups of the composing stripes from beginning to end of the stroke.

The instructions for the right conch have two sub-arrays means there are only two stripes in the pattern but each now has three colors gradient: First goes from 0 (red) through 1 (yellow) to 2 (lime) and second stripe goes from 3 (aqua) through 4 (blue) to 5 (magenta).

In the third demo note an instruction (one sub array) has only one color, in such case the corresponding stripe will go from beginning to end with this single color.

Under the hood: To keep precise constant width of the stripes, only certain numbers of stripes are optimal for the particular path. If this optimum is not followed, different stroke widths will appear, sometimes with interesting sub-patterns but most of the time asymmetric thus disturbing.

Click to activate then click stripes <> to decrease/increase their number, note they follow pre-calculated optimums. Click conch to toggle solid/gradient.

[ Large ]

[ Large ]

[ Large ]

Last hybrid is of course with old Tubefy. Highlight colors pure rainbow, stroke same colors dimmed 0.5 towards black:

[ Large ]

I am still looking for an interesting demo of normalGradient with non-equal spread colors. When I find one, method will be included in a later article.

Now the very few words regarding Tubefy gradients in the SVG spec:

When a curve degenerates to a straight line, normalGradient behave like native linearGradient.
When a curve degenerates to a dot (0 length path with a round linecap stroke) tubeGradient behave like native radialGradient, as demonstrated below:
Note: Though stroking dots is clearly described in the [ spec ] not all browsers support this basic requirement (Jan2010).

[ Large ]

In simple words, both native gradients are private cases of Tubefy gradients.
What can be more natural extensions than these to be integrated into the SVG spec?

(There is also the yet-to-be-born paraGradient (parallel gradient) but I leave it to after its introduction in a later article).

As for the next article, I still hesitate to put it under the Variable Stroke-Width umbrella:

Next article: Calligraphy pen ribbons and flags

Copyright © 2010 Israel Eisenberg. All rights reserved.