10. Special Effect: A Fading Circle

In these notes you will learn:

  • How to make an object fade away by changing its transparency.
  • How to use the constrain function.
  • How to write your own version of the constrain function.

10.1. Introduction

So far, all the animations we’ve created move an object by changing its position. In this note we will see another kind of animation: fading. When the user clicks on the screen a big blue circle appears. It then begins to fade away until it is invisible.

10.2. Organizing Our Demo Program

The first step in writing any program is to decide exactly what it should do. Since we are writing a demonstration of a new kind of animation, we want to keep it relatively simple. So lets agree that the program works as follows: when the user clicks on the screen, a circle appears at the click-point and then fades away to invisibility.

We’ll need variables to store its (x, y) center. The circle won’t be moving or changing shape, so we don’t need dx, dy, or diam.

How do we get the circle to fade? Fading means that the circle starts out with some totally opaque (i.e. not see-through) color, and then slowly becomes more transparent, finally disappearing completely.

Recall that Processing lets you specify a colors alpha value, which sets the transparency of a color. Alpha values in Processing range from 0 to 255, where 0 is totally transparent (i.e. invisible), and 255 is totally opaque (i.e. not transparent at all).

So we will implement fading by slowly decreasing the alpha value of the circle’s fill color.

10.3. First Draft: Just the Circle

Lets write a program that displays a big blue circle wherever the user clicks with the mouse. There is no fading yet; we’ll add that next after we get this working:

float x;
float y;

void setup() {
  size(500, 500);
  smooth();
}

void draw() {
  background(255);
  noStroke();
  fill(0, 0, 255);
  ellipse(x, y, 150, 150);
}

void mousePressed() {
  x = mouseX;
  y = mouseY;
}

Recall that the mousePressed() function is automatically called by Processing whenever the user clicks a mouse button. When it’s called here, it sets x and y to be the position of the mouse, and so when draw() is called the circle will be drawn centered at the most recent mouse-click.

10.4. Adding Fading

Now lets add the fading effect. We’ll need a variable to keep track of the current transparency:

float transparency;  // ranges from 0 to 1 (0 = totally transparent)

Lets agree that transparency ranges from 0 (totally transparent) to 1 (totally opaque). A range of 0 to 1 is pretty common because it makes it easy to talk about percentages, e.g. 0.73 is exactly 73% of the way between 0 and 1.

We will also need a variable that tells us how much to decrease the transparency on each call to draw(). In other words, we need a variable that stores how fast the transparency change:

float dtrans;

We call this variable dtrans following the convention of starting “speed” variables (like dx and dy) with the letter d.

Now we can add the fading effect. We need to make only a few changes:

  • Whenever the user clicks the mouse, the circle is drawn at a new location with a totally opaque transparency. Thus we need to set transparency to 1 at the end of mouseClick():

    void mousePressed() {
      x = mouseX;
      y = mouseY;
      transparency = 1;
    }
    
  • Whenever the circle is drawn, its fill color must be drawn taking the transparency into account. This is not completely straightforward. For instance, this doesn’t work:

    void draw() {
      background(255);
      noStroke();
    
      fill(0, 0, 255, transparency);  // wrong!
    
      ellipse(x, y, 150, 150);
    }
    

    The problem is that the fill function expects a value in the range 0 to 255 (you learn this by reading the fill() function documentation). But transparency ranges from 0 to 1.

    We could, of course, change our previous decision and let transparency range from 0 to 255. That would be fine for this one program. But in general, it’s probably not a good idea. In large programs, you may have many different ranges, and if these ranges all have different begin and end points you will soon find it takes a lot off mental effort to keep track of them. It’s simpler if you require that all ranges start at 0 and end at 1.

    So lets keep the range of transparency as it is, and instead use the map function to convert transparency into a corresponding value in the range 0 to 255:

    void draw() {
      background(255);
      noStroke();
    
      float a = map(transparency, 0, 1, 0, 255);
      fill(0, 0, 255, a);
    
      ellipse(x, y, 150, 150);
    }
    

    This works! The expression map(transparency, 0, 1, 0, 255) returns the a number in the range 0 to 255 that corresponds to the same value of transparency from 0 to 1.

  • After the circle is drawn, we need to change transparency by adding dtrans to it:

    void draw() {
      background(255);
      noStroke();
    
      float a = map(transparency, 0, 1, 0, 255);
      fill(0, 0, 255, a);
    
      ellipse(x, y, 150, 150);
    
      transparency += dtrans;
    }
    

    Since we’ve decided to add dtrans to transparency (instead of subtracting it), we must remember to initialize dtrans to a negative value in order to get a fading effect.

    An important detail arises here. We’ve decided that transparency ranges from 0 to 1, but the statement transparency += dtrans does not respect that range. For instance, if dtrans is negative, then if you add dtrans enough times transparency will become negative, i.e. it will be outside of the agreed-upon range of 0 to 1.

    Sometimes this might not matter. In fact, in this program, it doesn’t matter because it turns out that if you call fill with a negative alpha value then the alpha is treated as if it were 0.

    But in general it is unwise to rely on functions handling errors the way you want. So we should make sure that the value of transparency is always in the correct range. The easiest way to do that in Processing is to use the constrain function:

    void draw() {
      background(255);
      noStroke();
    
      float a = map(transparency, 0, 1, 0, 255);
      fill(0, 0, 255, a);
    
      ellipse(x, y, 150, 150);
    
      transparency += dtrans;
      transparency = constrain(transparency, 0, 1);
    }
    

    The expression constrain(transparency, 0, 1) always returns a value from 0 to 1. If transparency happens to be between 0 and 1, then it’s value is returned. Otherwise, if transparency is less than 0, 0 is returned; if transparency is greater than 1, 1 is returned.

    In fact, it is not hard to write your own version constrain:

    float myConstrain(float x, float lo, float hi) {
       if (x < lo) {
          return lo;
       } else if (x > hi) {
          return hi;
       } else {
          return x;
       }
    }
    

10.5. Questions

  1. What are alpha values?
  2. What is the range of alpha values in a Processing color function such as fill?
  3. When is mousePressed() called?
  4. Why does the variable transparency range from 0 to 1?
  5. Why is dtrans set to a negative value in the fading circle program?
  6. What does the constrain function do?

10.6. Programming Questions

  1. Add a background image to the fading circle program. As the circle fades away, the underlying image should become clearer.
  2. Modify the fading circle program so the circle fades in (instead of fades out). When the user clicks with the mouse, the circle begins invisible and then slowly becomes fully opaque.
  3. Modify the fading circle program so that after the transparency of the circle fades to 0, it starts to fade back in, and then it fades back out, and so on forever. The circle should appear to slowly pulse.
  4. Read about the tint() function and then modify the fading circle program to make an arbitrary image (loaded from a file) fade away.

10.7. The Fading Circle Program

float x;
float y;

float transparency; // 0 <= transparency <= 1
float dtrans;       // the change in transparency on each call to draw

void setup() {
  size(500, 500);
  dtrans = -0.01;
  smooth();
}

void draw() {
  background(255);
  noStroke();
  float a = map(transparency, 0, 1, 0, 255);
  fill(0, 0, 255, a);
  ellipse(x, y, 150, 150);
  transparency += dtrans;
  transparency = constrain(transparency, 0, 1);
}

void mousePressed() {
  x = mouseX;
  y = mouseY;
  transparency = 1;
}