Shading (Wait/Loading Animation Tutorials)
Wait/Loading Animation

Site Map Feedback


←Previous Index Next→
Up Button Noise Wait Wall

The last tutorial covered the basics of drawing on a Browser Canvas. Now that the drawing surface is available, the first part of the magic can be introduced, Unsigned Unit Intervals: variables which only vary between zero and one inclusive (written [0,1]), so 0.5 is mid-range like 50%.

The gradient in the previous tutorial was drawn with a single Unsigned Unit Interval: the variable t in the source code which will be zero (black) when y is zero, and one (white) when y is height. This is simply a division: t=y/c.height; check it out because this is the heart of everything that follows: when y is zero, t is zero, when y is height, t is one and there is no danger of division by zero as long as height is not zero. Since the pixel values vary in the range [0,255], t is finally scaled up simply by multiplying by 255 but this is not done until the pixel data is written because Unit Intervals are more valuable for mixing and filtering.

Here's the code again in case you didn't already paste it into a file called shade.htm for editing and viewing in your web browser to see your changes:

<!DOCTYPE html>
  <canvas id="Drawing" width="50" height="50">This browser doesn't support the canvas element :-(</canvas>
  <script type="text/javascript">
    var c=document.getElementById("Drawing");
    var ctx=c.getContext("2d");
    var imgData=ctx.getImageData(0,0,c.width,c.height);
    for(var y=c.height; y--;) {
      var t=y/c.height;          // t varies in the range [0,1]. 0 will be black, 1 will be white.
      for(var x=c.width; x--;) {
        var i=4*(c.width*y+x);   // locate the point (x,y) in the imgData array[i+0]=255*t; // Red scaled up to byte size varying between [0,255][i+1]=255*t; // Green[i+2]=255*t; // Blue[i+3]=255;   // Alpha (Opaque) = Overwrite the screen data
    } }
You can change the code to shade over x instead of y; edit three lines of the source to look like this by moving the t declaration into the inner loop and changing y to x and height to width:
    for(var y=c.height; y--;) {
      for(var x=c.width; x--;) {
        var t=x/c.width;          // t varies in the range [0,1]. 0 will be black, 1 will be white.
Save your changes and open the file in your browser to see the result (or Refresh if it's already open - usually by pressing F5 at the top of your keyboard or clicking a circular arrow icon).

Now it's time to start playing with Unit Intervals by mixing the x shading with the y shading. Make both shading declarations and give them separate names - I've used dx and dy which culturally mean a change in x and a change in y, then create t by mixing (multiplying) them:

    for(var y=c.height; y--;) {
      var dy=y/c.height;          // [0,1]
      for(var x=c.width; x--;) {
        var dx=x/c.width;          // [0,1]
        var t=dx*dy; // t varies in the range [0,1]. 0 will be black, 1 will be white.

This will give black top and left edges where either x or y are zero and a white bottom right corner where both x and y are one, with rounded rectangular shading of greys inbetween. You can see the rounded rectangle by adding the next few lines of code immediately after the previous lines:

        if(t>0.1) t=1;
        else t*=10;
That was a filter, filtering out values greater than 0.1 and spreading out what was left to cover the full range of shades. That technique is used a lot in the Button and Wall Procedural Textures on this site (in the gold menu bar at the top of this page). Here are a couple of functions that will be used later that spread out either the upper or lower fraction of the Unsigned Unit Interval's range:
function Lower(n,t) {return t>1/n ? 1 : t*n;} // if n=5, returns the lower fifth spread out to [0,1]
function Upper(n,t) {return t<(n-1)/n ? 0 : (t-(n-1)/n)*n;} // if n=5, returns the upper fifth spread out to [0,1]
You can see that the filter used could be written as Lower(10,t) which spreads out the lower tenth of the scale. It's like zooming in to the Lower tenth (throwing away the rest) and bringing that area's detail out to full range.

Multiplying Unit Intervals is a safe thing to do because the result is always a another Unit Interval. Look at the minimum: zero, multiplied by anything is always zero - the result can't get smaller (negative); and the maximum? Multiply anything by one and the result can't be bigger than the number you started with - so if that is an Unsigned Unit Interval [0,1] it won't exceed 1: it's still a Unit Interval. Signed Unit Intervals [-1,1] behave similarly: when multiplied, the result will always be another Signed Unit Interval. It's also worth noting that there is a very useful operation (1-t) that can be thought of as the 'opposite' of t. You can use t=(1-t) to invert the shading in your existing code, for example.

The last thing to play with is the RGB values (Red, Green, Blue) in the following three lines:[i+0]=255*t; // Red scaled up to byte size varying between [0,255][i+1]=255*t; // Green[i+2]=255*t; // Blue
Try replacing the t on one or more lines with zero or one or 0.5 or (1-t). This is where you can learn to control what you create.
A gradient going from black to red would be (t,0,0) going down the t column:
                              ↓[i+0]=255*t; // Red scaled up to byte size varying between [0,255][i+1]=255*0; // Green[i+2]=255*0; // Blue
going from red to black would be ((1-t),0,0):[i+0]=255*(1-t); // Red scaled up to byte size varying between [0,255][i+1]=255*0; // Green[i+2]=255*0; // Blue
Try getting from sky blue to sun yellow (yellow is full red and full green): This browser doesn't support the canvas element :-(

This is one solution (as you'll see if you look at this web page's HTML and javascript source code):[i+0]=255*(t/2+0.5);  // Red scaled up to byte size varying between [0,255][i+1]=255*(t/4+0.75); // Green[i+2]=255*(1-t);      // Blue
Just like the Upper and Lower functions mentioned previously, two functions could be used to squeeze a parameter so that it varies in a limited range:
function SqueezeAbove(n,t) {return t/n+(1-1/n);} // if n=5, returns the range mapped to [1/5,1]
function SqueezeBelow(n,t) {return t<(1/n) ? t/n : 1;} // if n=5, returns the range mapped to [0,1/5]
The sky blue to sun yellow fade would then be written as follows:[i+0]=255*SqueezeAbove(t,2);  // Red scaled up to byte size varying between [0,255][i+1]=255*SqueezeAbove(t,4); // Green[i+2]=255*(1-t);      // Blue

Nice as these shadings are, they aren't what is needed for a Wait/Loading animation. A circular shading is developed in the next tutorial.

 (1050) Last modified: Sun, 02 Sep 2012 14:20:10 +0100