Penner Easing functions in Processing

Easing in Processing

I needed to use the raw Robert Penner easing functions. The functions are already implemented in the Ani Library from Benedikt Gross, but there wasn’t an example to use the functions directly without using the transition and time function. Apart from timed animations you could use the functions as well as a curve in audio-visualisations (for example the Sound library can give you back an amplitude on which you can apply these functions).

In the code below I use the object, but syntax like this also works:

float easingOutput = AniConstants.CUBIC_IN_OUT.calcEasing(normalizedValue,0.0,1.0,1.0);

In this example code I used normalized values.  Which means that we use an value between 0.0 – 1.0 as input and also get that back as output.

The functions accept 4 values:

  • t is time, in this case just 0.0 – 1.0
  • b is the start, we just use 0.0
  • c is the change, we use normalized so also 1.0
  • d is the duration, because we use normalised it’s 1.0

If you only need the functions you can also copy-paste them from the Ani-library source.

import de.looksgood.ani.AniConstants;
import de.looksgood.ani.easing.*;
import de.looksgood.ani.*;

Sine sine = new Sine();
Quad quad = new Quad();
Cubic cubic = new Cubic()
Quart quart = new Quart();
Quint quint = new Quint();
Expo expo = new Expo();
Circ circ = new Circ();
Back back = new Back();
Elastic elastic = new Elastic();
Bounce bounce = new Bounce();

// stuff it all in an array for display

Easing[] easingFunctions = { 
  sine, quad, cubic, quart, quint, expo, circ, back, elastic, bounce
};

String [] easingNames = {
  "sine", "quad", "cubic", "quart", "quint", "expo", "circ", "back", "elastic", "bounce"
};

String [] aniModes = { "IN", "OUT", "IN_OUT" };
int currentAniMode = AniConstants.IN_OUT;

float xLeftMargin  = 80;
float xRightMargin = 80;

void setup() {
  // tip FX2D looks really smooth on retina displays
  // some things won't work in this mode, but most things do
  size(640,480,FX2D);
  noStroke();
  frameRate(25);
  
  changeMode(currentAniMode);
}

void draw() {
  
  background(255);
  
  float durationInMilliSeconds = 4000;
  float normalizedValue        = (millis()%durationInMilliSeconds)/durationInMilliSeconds;
  
  // overwrite the calculated value when pressing the mouse
  if(mousePressed) normalizedValue = mouseX/(float)width;
  
  fill(0);
  text("easing mode : " + aniModes[currentAniMode] + " - " + nf(normalizedValue,0,2),10,20);
  
  fill(224);
  rect(xLeftMargin,40,(width-xLeftMargin-xRightMargin),height-80);
  
  // line to display the linear 0.0 - 1.0 progress
  stroke(196);
  float lineX = xLeftMargin + (normalizedValue * (width-xLeftMargin-xRightMargin));
  line(lineX,40,lineX,height-40);
  
  for(int i = 0; i < easingFunctions.length; i++) {
    
    float yPos = 60+(i*40);
    
    float easingOutput = easingFunctions[i].calcEasing(normalizedValue,0.0,1.0,1.0);
    
    // put the text in front and draw horizontal line
    fill(0);
    text(easingNames[i], 20, yPos);
    line(xLeftMargin,yPos,(width-xRightMargin),yPos);
    
    // draw the eased circle
    drawCircle(easingOutput, yPos, 20);
    
  }
  
  text("Press to mouse control the value manually. Change the Ease mode with the 1, 2, 3 keys.", 20, height-15);
  
  if(frameCount > 22 && frameCount<151) saveFrame("easing-#####.png");
  
}

void drawCircle(float normalizedValue, float yPos, float radius) {
  // calculate the x position and take the margins in account
  float xPos = xLeftMargin + (normalizedValue * (width-xLeftMargin-xRightMargin));
  noStroke();
  fill(64);
  ellipse(xPos,yPos,radius,radius);
}


void keyReleased() {
  
  if(key == '1') {
    changeMode(AniConstants.IN);
   }
  else if(key == '2') {
    changeMode(AniConstants.OUT);
  }
  if(key == '3') {
    changeMode(AniConstants.IN_OUT);
  }
  
}

void changeMode(int mode) {
  
  currentAniMode = mode;
  // change the mode for all easing objects
  for (Easing e : easingFunctions) {
     e.setMode(currentAniMode); 
  }
}