Special Effect: A Fading Circle

In these notes you will learn:

  • How to make an object fade away by changing its transparency.
  • How to make an object “pulse” by repeatedly fading in and out.
  • How to make an object bounce around the screen while pulsing.

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.

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 slowly fades away to invisibility.

We’ll need variables to store its (x, y) center. The circle won’t (initially) 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 totally opaque (i.e. not see-through), and then slowly becomes more transparent.

Recall that Processing lets you specify the alpha value of a color, which sets its transparency. 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.

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.

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 change 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, that’s probably a bad 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 and more consistent 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.

This works! Wherever you click on the window a circle appears and then slowly fades to away.

Warning

There’s a bug in this program that doesn’t matter much in this simple demo, but could cause problems in larger programs that use the same idea. The problem is that transparency quickly becomes negative, which means it is outside of the valid range 0 to 1. That causes no problem here, but in other situations it might. So to ensure that transparency is always in the valid range, we can modify the code by adding a new statement:

// ...
transparency += dtrans;
transparency = constrain(transparency, 0, 1);

The statement transparency = constrain(transparency, 0, 1) limits the value of transparency to be between 0 and 1. It is the same as writing this if-statement:

// ...
transparency += dtrans;
if (transparency < 0) {
   transparency = 0;
} else if (transparency > 1) {
   transparency = 1
}

Fading in and Fading Out

Now lets change the program so that after the circle becomes invisible, it starts to fade back in at the same rate that it faded out. The trick here is to use if-statements to check when the circle is totally invisible/opaque and then reverse the direction of the fading:

void draw() {

  // ...

  transparency += dtrans;

  if (transparency < 0) {
    dtrans = -dtrans;
  } else if (transparency > 1) {
    dtrans = -dtrans;
  }
}

We could also have written the if-statement like this:

void draw() {

  // ...

  transparency += dtrans;

  if (transparency < 0) {
    dtrans = -dtrans;
  }
  if (transparency > 1) {
    dtrans = -dtrans;
  }
}

Or even this:

void draw() {

  // ...

  transparency += dtrans;

  if (transparency < 0 || transparency > 1) {
    dtrans = -dtrans;
  }
}

For this particular program all three ways of writing the if-statement are practically equivalent, and so it does not matter too much which one you use. But, in general, writing if-statements in different forms can lead to significantly different code.

The statement dtran = -dtrans causes the “direction” of the fading to change by flipping the sign of dtrans, i.e. when dtrans is negative it becomes positive (fading in), and when it is positive it becomes negative (fading out).

Bouncing and Fading

Finally, lets make the circle bounce around the screen while fading. We’ve seen in previous notes how to make a ball bounce, and so we need to add the same sort of code here. In particular, to make the circle move we’ll use dx and dy to represent the velocity, and then add some if-statements that test for edge collisions. See the code at the end of these notes for the final program.

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?

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. Read about the tint() function and then modify the fading circle program to make an arbitrary image (loaded from a file) fade away.

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);
  smooth();

  transparency = 1;
  dtrans = -0.01;
}

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;
}

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

The Pulsing 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);
  smooth();

  transparency = 1;
  dtrans = -0.01;
}

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;

  if (transparency < 0) {
    dtrans = -dtrans;
  } else if (transparency > 1) {
    dtrans = -dtrans;
  }
}

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

The Bouncing and Pulsing Circle Program

float x;
float y;
float dx;
float dy;


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

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

  x = 250;
  y = 250;
  dx = -1.5;
  dy = 2.2;

  transparency = 1;
  dtrans = -0.01;
}

void draw() {
  background(255);

  noStroke();
  float a = map(transparency, 0, 1, 0, 255);
  fill(0, 0, 255, a);
  ellipse(x, y, 150, 150);

  x += dx;
  y += dy;
  if (x - 75 < 0) {  // hit left edge?
    x = 75;
    dx = -dx;
  }

  if (x + 75 > 499) {  // hit right edge?
    x = 499 - 75;
    dx = -dx;
  }

  if (y - 75 < 0) {  // hit top edge?
    y = 75;
    dy = -dy;
  }

  if (y + 75 > 499) {  // hit bottom edge?
    y = 499 - 75;
    dy = -dy;
  }

  transparency += dtrans;

  if (transparency < 0) {
    dtrans = -dtrans;
  } else if (transparency > 1) {
    dtrans = -dtrans;
  }
}

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