Andrew Sweet

20 Jan 2014

asweet Lenticular

 

I was looking for inspiration in other media I’ve consumed recently. I thought of rhythmic, trance-like repetition for a GIF, and ultimately went to Hotline Miami for inspiration. The image was inspired by the game’s rhythmic hot-neon colors and rotating text for it’s title screen: (Feel free to mute)

I started iteration by working toward an exact replica of the original title screen. I realized there wasn’t much value to direct imitation in this case, and tried to figure out where I could diverge and create something different. I decided on Hello World since this is the first homework assignment, and well anytime you’re starting something new in CS, you’ve got to make a Hello World.

When I’ve seen lenticular images in the past, I’ve noticed that the delta of any image is the most interesting part. Having something that kind of graphically comes out at you in 3D made more sense than a simple rotation. Stylistically, I also decided to layer multiple text images on top of each other to create the illusion of depth, rather than creating 3D protruding objects. I saw this as kind of a challenge to myself to try to create the illusion of depth mathematically, without relying on a 3D camera to create the sort of effect I was looking for. After trying out some tests on GIFPOP, I realized that I could have an image that appeared to have more depth to it if I had the image angle change with the angle of the Lenticular. The popping out illusion with added skew really works well on the digital lenticular preview, and I hope it works well in the real version as well.

Having never really worked with GIFs before, I didn’t realize that they were limited to a 256 color palette. I’m not upset with the result, but it was not planned to have color banding. Had I been aware, I might have avoided gradients… (especially after Golan stated he hated rainbows). I actually like seeing the artifact of my lack of knowledge in starting with a new tool. Overall, it was a nice re-introduction to the Processing tools which I haven’t used in over 2 years.

sketch

// This is based on a template for creating a looping animation 
// in Processing by Prof. Golan Levin:
 
// When you press a key, this program will export a series of images
// into an "output" directory located in its sketch folder. 
// These can then be combined into an animated GIF. 
// Prof. Golan Levin, January 2014 - CMU IACD
 
// Andrew Sweet, January 2014
 
//===================================================
// Global variables. 
 
int     nFramesInLoop = 30; // for lenticular export, change this to 10!
int     nElapsedFrames;
boolean bRecording; 
int     Y_AXIS = 1;
int     X_AXIS = 2;
color   b1, b2, b3;
PFont   font;
float   theta;
float[] ringRadii;
int     numRings;
float   screenDiagonal;
 
String  myName = "andrewsweet";
 
void prepopulateRings(){
  // a^2 + b^2 = c^2
  screenDiagonal = sqrt(sq(width) + sq(height));
 
  for (int i = 0; i < numRings; i++){
    ringRadii[i] = (i*screenDiagonal)/numRings;
  }
}
 
//===================================================
void setup() {
  size (500, 500); 
  bRecording = false;
  nElapsedFrames = 0;
  numRings = 6;
  ringRadii = new float[numRings];
 
  prepopulateRings();
 
  // Define colors
  b1 = color(254, 31, 146);
  b2 = color(48, 52, 210);
  b3 = color(43, 214, 184);
  font = createFont("Chicago",16,true); // STEP 3 Create Font
 
  frameRate (nFramesInLoop); 
}
//===================================================
void keyPressed() { 
  // Press a key to export frames to the output folder
  bRecording = true;
  nElapsedFrames = 0;
}
 
//===================================================
void draw() { 
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } 
  else {
    float modFrame = (float) (frameCount % nFramesInLoop);
    percentCompleteFraction = modFrame / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("output/"+ myName + "-loop-" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames == nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//Function from http://processing.org/examples/lineargradient.html
  void setGradient(int x, int y, float w, float h, color c1, color c2, int axis ) {
 
  noFill();
 
  if (axis == Y_AXIS) {  // Top to bottom gradient
    for (int i = y; i <= y+h; i++) {
      float inter = map(i, y, y+h, 0, 1);
      color c = lerpColor(c1, c2, inter);
      stroke(c);
      line(x, i, x+w, i);
    }
  }  
  else if (axis == X_AXIS) {  // Left to right gradient
    for (int i = x; i <= x+w; i++) {
      float inter = map(i, x, x+w, 0, 1);
      color c = lerpColor(c1, c2, inter);
      stroke(c);
      line(i, y, i, y+h);
    }
  }
}
 
//===================================================
void pulseText3D (String text, float x, float y, float percent){
  int numLayers = 20 + int((sin(percent * 2 * PI) * 17));
 
  translate(x, y);   // Translate to the center
  rotate(theta);     // Rotate by theta
  textAlign(CENTER);  
 
  for (int i = 0; i < numLayers; i++){
    textFont(font, (width/10) + (i * (width/500.0)) + (sin(percent * 2 * PI) * (width/100)));
 
    float inter = map(i, 0, numLayers, 0, 1 - abs(sin(percent * 2 * PI)/8));
    color c = lerpColor(b2, b1, inter);
    fill(c);
    text(text, cos(percent * 2 * PI) * (numLayers/2 - i) * width/500.0, sin(percent * 2 * PI) * (numLayers/2 - i) * width/500.0);  // STEP 6 Display Text
  }
  rotate(-theta);
  theta += 0.01 * cos (percent * 2 * PI);                // Increase rotation
  translate(-x, -y);
}
 
void drawRings (float percent){
  float maxRadiusDelta = screenDiagonal/numRings;
  int maxStroke = width/50;
 
  for (int i = 0; i < numRings; i++){
    float radius = ringRadii[i] + (percent * maxRadiusDelta);
 
    float weight = map(radius, 0, width, 0, maxStroke);
    float alpha = map(radius, 0, width/4 * 5, 255, 0);
 
    noFill();
 
    strokeWeight(weight);
    stroke(255, 255, 255, alpha);
 
    ellipse(width/2, height/2, radius, radius);
  }
}  
 
void renderMyDesign (float percent) {
  // This is an example of a function that renders a temporally looping design. 
  // It takes a "percent", between 0 and 1, indicating where we are in the loop. 
  // This example uses two different graphical techniques. 
  // Use or delete whatever you prefer from this example. 
  // Remember to SKETCH FIRST!
 
  //----------------------
 
  int stretch = height/9;
 
  setGradient(0, - stretch - int((sin(percent * 2 * PI) * stretch)), 
              width, height + stretch + int(sin(percent * 2 * PI) * stretch), b1, b3, Y_AXIS);
 
  drawRings(percent);
 
  pulseText3D("Hello World", width/2, height/2, percent);
 
  smooth(); 
  stroke (0, 0, 0); 
  strokeWeight (2); 
}