#3 (Loop, Due 9/11)

This set of deliverables has 3 parts, and is due Wednesday 9/11 at the beginning of class:

  • An optional fun reading about loops;
  • A brief reading-response;
  • A looping GIF using arcs.

0. Optional Fun Reading about Loops

Check out the article "On Repeat: How to Use Loops to Explain Anything" by Lena V. Groeger, a journalist/developer/information-designer at ProPublica. This article is purely for your edification/entertainment; there's no deliverable. It should take about 10-15 minutes to view this elegant stack.


1. Reading #03: First Word Art, Last Word Art

Please read the one-page essay, "First Word Art / Last Word Art" by Michael Naimark. A backup PDF of this article can be found here. You are asked to write a brief (two-paragraph) response to this essay.

Please contemplate technical novelty in relation to the arts. The author of this reading, Michael Naimark, is a new-media artist who has been active in experimental cinema and virtual reality since the mid-1970s. The duality Naimark describes is one attempt to understand how culture accommodates new technologies, delineating a spectrum from the well-understood to the utterly novel.

Make a new blog post in this WordPress site. In your blog post, please write about 150-200 words reflecting on Naimark's article. Some possible starting points for your reflection could include (but are not limited to):

  • Where do you locate your interests along this spectrum?
  • What are some ways in which new technologies shape culture?
  • What are some ways in which culture shapes technological development?
  • We might aspire to make stuff of lasting importance, but when our work is technologically novel, it doesn't always age well. Discuss.

When you are done, please give your blog post the title: nickname-Reading03, and categorize your blog post with the WordPress category, 03-Reading.


2. A Looping, Animated, Generated GIF using Arcs.

This is the "main" part of Deliverables #2, and will be the only part of this week's assignment which is critiqued in class. In this project, you will create a looping, animated GIF strictly using the arc() command. No other drawing primitives are permissible (e.g. no ellipse, rect, bezier, line, point, image, etc.). 

Your GIF must be uploaded to this site no later than 8:00am on Wednesday, 9/11. So:

    1. SKETCH FIRST! Before doing anything, make some (real) sketches in your notebook. Try to develop a graphic concept.
    2. Your canvas must be square, with dimensions of precisely 640x640 pixels. (Why? Ask me about my EV2730Q)
    3. Write code which generates a (seamlessly) looping animation using arcs. You may use the code templates below to get started. I've provided templates in p5.js (JavaScript), Processing (Java), and Python, using Processing's Python mode.
    4. It is recommended that you restrict yourself to just a few, well-chosen colors. Remember, an animated GIF must represent all of its frames with a single a palette of just 256 colors. You may find this resource helpful: tips for making animated GIFs.
    5. Limit the duration of your GIF to approximately 30-300 frames long (~1-10 seconds).
    6. Export your animation frames from your code computationally (i.e. from code), and then use a tool (such as one of these) to assemble the animated GIF from the frames. Do NOT use a screencapture program to make your GIF! Also:
      • be sure to create your GIF so that it loops infinitely, with a duration of forever. Make sure it loops without a "hiccup".
      • be sure to create your GIF so that it plays back at least 20 frames per second (preferably 30 FPS). These are options you can set with proper GIF creation tools. You may need to specify the frame rate using milliseconds per frame (e.g. 30 FPS = 33 ms/f).
      • be sure your GIF is under 10MB in filesize, preferably under 5MB. You can optimize (compress) your GIF with a tool like https://ezgif.com/.
    7. OH HEY WAIT. You are required to use an easing function to create a nuanced articulation of animated form. (Why? because you'll learn a ton.) Select an easing function from p5-func (p5.js/JavaScript) 0r Pattern_Master (Processing/Java). [The p5-func is an external library. By contrast, note that the Pattern_Master is just a grab-bag of functions; although it is a working Processing project, it is not formatted as a proper Processing library. You'll need to hunt through the code to find the function you want, and then copy-paste it into your project.] These easing functions take a number from 0...1, and return an altered number from 0...1, which has a different character or quality.
    8. Want more optional challenge? Motion blur can make GIF loops look amaaazing. David Whyte from BeesAndBombs has posted some Processing template code for achieving motion blur, and Étienne Jacob has written a detailed tutorial about using this code, available here.
    9. In a blog post, upload and embed your animated GIF. Important: Embed the GIF at the original resolution of 640x640 pixels. Be sure not to embed any version that has been automatically resized by WordPress; it will not be animated! Also important: please upload your GIF directly to this WordPress site. GIFs embedded from Giphy or elsewhere in the cloud are not acceptable and will receive zero credit.
    10. Write a paragraph about the experience of creating the piece. Which easing function did you select, and why? Critique your work: where you feel you succeeded, where you feel you fell short of what you'd hoped to achieve.
    11. If you used p5.js, make sure your project is uploaded to the online editor.p5js.org, and link to it from your blog. If you used Java or Python, embed your code in your blog post using the pre tag in the Text edit mode.
    12. Include a scan or photo of your pen-and-paper sketches.
    13. Label your blog post with the Category, 03-AnimatedLoop.
    14. Title your blog post, nickname-AnimatedLoop.

Some Help: Code Templates for Generating Looping GIFs

Below are Processing, p5.js, and Processing.py templates for exporting frames to make an animated GIF loop. If you decide to use the template code, you'll need to rewrite the renderMyDesign() method, which takes a percentage from 0..1 (NOTE: not a frame number!) as its argument.

If you're using the JavaScript version, in order to export the frames, you'll need to set bEnableExport to true, and you may need to be running a local server; for help, see this explanation/instruction Wiki and this Shiffman video.

Here is an animated GIF, and the code template (in JS and Java) that produced it. Observe how the small pink square is moving nonlinearly, using one of the Pattern_Master functions. It has some character!

// This is a template for creating a looping animation in p5.js (JavaScript). 
// When you press the 'F' key, this program will export a series of images into
// your default Downloads folder. These can then be made into an animated gif. 
// This code is known to work with p5.js version 0.6.0
// Prof. Golan Levin, 28 January 2018

// INSTRUCTIONS FOR EXPORTING FRAMES (from which to make a GIF): 
// 1. Run a local server, using instructions from here:
//    https://github.com/processing/p5.js/wiki/Local-server
// 2. Set the bEnableExport variable to true.
// 3. Set the myNickname variable to your name.
// 4. Run the program from Chrome, press 'f'. 
//    Look in your 'Downloads' folder for the generated frames.
// 5. Note: Retina screens may export frames at twice the resolution.


//===================================================
// User-modifiable global variables. 
var myNickname = "nickname";
var nFramesInLoop = 120;
var bEnableExport = false;

// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;

//===================================================
function setup() {
  theCanvas = createCanvas(500, 200);
  bRecording = false;
  nElapsedFrames = 0;
}

//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      frameRate(2); // while we're exporting
      nElapsedFrames = 0;
    }
  }
}

//===================================================
function draw() {

  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }

  // Render the design, based on that percentage. 
  // This function renderMyDesign() is the one for you to change. 
  renderMyDesign(percentCompleteFraction);

  // If we're recording the output, save the frame to a file. 
  // Note that the output images may be 2x large if you have a Retina mac. 
  // You can compile these frames into an animated GIF using a tool like: 
  if (bRecording && bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;

    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
      frameRate(60);
    }
  }
}

//===================================================
function renderMyDesign(percent) {
  //
  // THIS IS WHERE YOUR ART GOES. 
  // 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. 
  // Use, modify, or delete whatever you prefer from this example. 
  // This example uses several different graphical techniques. 
  // Remember to SKETCH FIRST!

  //----------------------
  // here, I set the background and some other graphical properties
  background(180);
  smooth();
  stroke(0, 0, 0);
  strokeWeight(2);

  //----------------------
  // Here, I assign some handy variables. 
  var cx = 100;
  var cy = 100;

  //----------------------
  // Here, I use trigonometry to render a rotating element.
  var radius = 80;
  var rotatingArmAngle = percent * TWO_PI;
  var px = cx + radius * cos(rotatingArmAngle);
  var py = cy + radius * sin(rotatingArmAngle);
  fill(255);
  line(cx, cy, px, py);
  ellipse(px, py, 20, 20);

  //----------------------
  // Here, I use graphical transformations to render a rotated square. 
  // Notice the use of push(), pop(), translate(), etc. 
  push();
  translate(cx, cy);
  var rotatingSquareAngle = percent * TWO_PI * -0.25;
  rotate(rotatingSquareAngle);
  fill(255, 128);
  rect(-40, -40, 80, 80);
  pop();

  //----------------------
  // Here's a linearly-moving square
  var squareSize = 20;
  var topY = 0 - squareSize - 2;
  var botY = height + 2;
  var sPercent = (percent + 0.5) % 1.0; // shifted by a half-loop
  var yPosition = map(sPercent, 0, 1, topY, botY);
  fill(255, 255, 255);
  rect(230, yPosition, 20, 20);

  //----------------------
  // Here's a sigmoidally-moving pink square!
  // This uses the "Double-Exponential Sigmoid" easing function 
  // ripped from From: https://idmnyu.github.io/p5.js-func/
  // Really, you should just include this library!!
  var eased = doubleExponentialSigmoid(percent, 0.7);
  eased = (eased + 0.5) % 1.0; // shifted by a half-loop, for fun
  var yPosition2 = map(eased, 0, 1, topY, botY);
  fill(255, 200, 200);
  rect(260, yPosition2, 20, 20);

  //----------------------
  // Here's a pulsating ellipse
  var ellipsePulse = sin(3.0 * percent * TWO_PI);
  var ellipseW = map(ellipsePulse, -1, 1, 20, 50);
  var ellipseH = map(ellipsePulse, -1, 1, 50, 30);
  var ellipseColor = map(ellipsePulse, -1, 1, 128, 255);
  fill(255, ellipseColor, ellipseColor);
  ellipse(350, cy, ellipseW, ellipseH);

  //----------------------
  // Here's a traveling sine wave
  stroke(0, 0, 0);
  for (var sy = 0; sy <= height; sy += 4) {
    var t = map(sy, 0, height, 0.0, 0.25);
    var sx = 450 + 25.0 * sin((t + percent) * TWO_PI);
    ellipse(sx, sy, 1, 1);
  }

  //----------------------
  // Include some visual feedback. 
  fill(255, 0, 0);
  noStroke();
  textAlign(CENTER);
  var percentDisplayString = "" + nf(percent, 1, 3);
  text(percentDisplayString, cx, cy - 15);
}


// Symmetric double-element sigmoid function ('_a' is the slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
function doubleExponentialSigmoid(_x, _a) {
  if (!_a) _a = 0.75; // default

  var min_param_a = 0.0 + Number.EPSILON;
  var max_param_a = 1.0 - Number.EPSILON;
  _a = constrain(_a, min_param_a, max_param_a);
  _a = 1 - _a;

  var _y = 0;
  if (_x <= 0.5) {
    _y = (pow(2.0 * _x, 1.0 / _a)) / 2.0;
  } else {
    _y = 1.0 - (pow(2.0 * (1.0 - _x), 1.0 / _a)) / 2.0;
  }
  return (_y);
}
// This is a template for creating a looping animation in Processing/Java. 
// When you press the 'F' key, this program will export a series of images
// into a "frames" directory located in its sketch folder. 
// These can then be combined into an animated gif. 
// Known to work with Processing 3.4
// Prof. Golan Levin, January 2018
 
//===================================================
// Global variables. 
String  myNickname = "nickname"; 
int     nFramesInLoop = 120;
int     nElapsedFrames;
boolean bRecording; 
 
//===================================================
void setup() {
  size (500, 200); 
  bRecording = false;
  nElapsedFrames = 0;
}
//===================================================
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    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 {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (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("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
void renderMyDesign (float percent) {
  //
  // YOUR ART GOES HERE.
  // 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!
 
  //----------------------
  // here, I set the background and some other graphical properties
  background (180);
  smooth(); 
  stroke (0, 0, 0); 
  strokeWeight (2); 
 
  //----------------------
  // Here, I assign some handy variables. 
  float cx = 100;
  float cy = 100;
 
  //----------------------
  // Here, I use trigonometry to render a rotating element.
  float radius = 80; 
  float rotatingArmAngle = percent * TWO_PI; 
  float px = cx + radius*cos(rotatingArmAngle); 
  float py = cy + radius*sin(rotatingArmAngle); 
  fill    (255); 
  line    (cx, cy, px, py); 
  ellipse (px, py, 20, 20);
 
  //----------------------
  // Here, I use graphical transformations to render a rotated square. 
  pushMatrix(); 
  translate (cx, cy);
  float rotatingSquareAngle =  percent * TWO_PI * -0.25;
  rotate (rotatingSquareAngle); 
  fill (255, 128); 
  rect (-40, -40, 80, 80);
  popMatrix(); 
 
  //----------------------
  // Here's a linearly-moving white square
  float squareSize = 20;
  float topY = 0 - squareSize - 2;
  float botY = height + 2;
  float sPercent = (percent + 0.5)%1.0; // shifted by a half-loop
  float yPosition1 = map(sPercent, 0, 1, topY, botY); 
  fill (255, 255, 255); 
  rect (230, yPosition1, 20, 20); 
 
  //----------------------
  // Here's a sigmoidally-moving pink square!
  // This uses the "Double-Exponential Sigmoid" easing function 
  // from https://github.com/golanlevin/Pattern_Master
  float eased = function_DoubleExponentialSigmoid (percent, 0.7); 
  eased = (eased + 0.5)%1.0; // again, shifted by a half-loop, for fun
  float yPosition2 = map(eased, 0, 1, topY, botY); 
  fill (255, 200, 200); 
  rect (260, yPosition2, 20, 20); 
 
  //----------------------
  // Here's a pulsating ellipse
  float ellipsePulse = sin ( 3.0 * percent * TWO_PI); 
  float ellipseW = map(ellipsePulse, -1, 1, 20, 50); 
  float ellipseH = map(ellipsePulse, -1, 1, 50, 30); 
  float ellipseColor = map(ellipsePulse, -1, 1, 128, 255); 
  fill (255, ellipseColor, ellipseColor); 
  ellipse (350, cy, ellipseW, ellipseH); 
 
  //----------------------
  // Here's a traveling sine wave, 
  // of which one-quarter is visible
  for (int sy=0; sy <= height; sy+=4) {
    float t = map (sy, 0, height, 0.0, 0.25); 
    float sx = 450 + 25.0 * sin ((t + percent)*TWO_PI); 
    point (sx, sy);
  }
 
  //----------------------
  // If we're recording, I include some visual feedback. 
  fill (255, 0, 0);
  textAlign (CENTER); 
  String percentDisplayString = nf(percent, 1, 3);
  text (percentDisplayString, cx, cy-15);
}
 
 
 
//===================================================
// Taken from https://github.com/golanlevin/Pattern_Master
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x<=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}

Also worth checking out:

For more ideas, here's Dan Shiffman from Coding Train, in which he re-codes a classic GIF loop by Bees and Bombs:

 

And here's a helpful Coding Train about making GIF loops: