Bonus: Waves


int i, w=1024, h=768, x, y, s=10;
float k, m, r, j=.01;

void setup() {
  size(w, h, P3D);
  noStroke();
}

void draw()
{
  background(#A4D6E8);
  lights();
  beginShape(8);
  for (i=0; i < w*h; i+=s)
  {
    x=i%w;
    y=i/w;     
    k=y+s;
    m=x+s;
    fill(100, 100, random(220,255), 200);
    vertex(x, n(y*w+x), y);
    vertex(m, n(y*w+m), y);
    vertex(m, n(k*w+m), k);
    vertex(m, n(k*w+m), k);
    vertex(x, n(k*w+x), k);
    vertex(x, n(y*w+x), y);
    i+=i%w==0?w*(s-1):0;
  }
  endShape();
  r-=j;
    fill(100, 100, random(220,255), 200);
  rect(0, width* .5, width, 400);
}
float n(float i) {
  return noise(i%w*j, i*j/w+r)*s*8+h/2;
}

Creating a lamp with Processing

We will use an existing Processing library to create the pieces for the laser-cut lamp. Let’s follow the instructions here.

Codeable Objects Library
Codeable Objects is a library for Processing that allows anyone to design and construct an artifact using geometric computation and digital fabrication. This tutorial will show you how to use the library to make a laser cut lamp. The library allows you to customize the size, shape and decorative patterns of the lamp.

Download the library here.
http://highlowtech.org/?p=2329

Bonus: Circular Galaxy


int num = 40, circles=80, frames=60;
float theta;
 
void setup() {
  size(540, 540);
}
 
void draw() {
  randomSeed(3453);
  background(0);
  noStroke();
  float angle = 0;
  for (int j=0; j < circles; j++) {
    fill(255, 25);
    float r = random(TWO_PI);
    beginShape(TRIANGLE_STRIP);
    float r2 = random(30, 50);
    for (int i=0; i < num; i++) {
      angle=TWO_PI/num*i+r;
      float offSet=TWO_PI/num*i;
      float d = 120+cos(offSet*3)*r2;
      float x = width/2 +sin(theta+offSet)*10 + sin(angle)*d;
      float y = height/2 + cos(theta+offSet)*10 + cos(angle)*d;
      vertex(x, y);
      ellipse(x, y, 3, 3);
    }  
    endShape(CLOSE);
  }
  theta+=TWO_PI/frames;
  //if (frameCount <= frames) saveFrame("imge-###.gif");
}

Bonus: Glitch 2


PImage img;
int min = 40, max = 80;
int r = 10;
 
void setup() {
  img = loadImage("https://s-media-cache-ak0.pinimg.com/736x/2c/f6/a5/2cf6a58f75b368949cf98db03b925f1b.jpg");
  size(img.width, img.height);
  image(img, 0, 0);
}
 
void draw() {
  doStuff();
}
 
void keyPressed() {
  save(random(123456)+".jpg");
}
 
void doStuff() {
  PImage tmp = createImage(width, height, RGB);
  tmp.loadPixels();
  float rd = random(25, 100);
  for (int px=0; px < width; px++) {
    for (int py=0; py < height; py++) {
      float br = brightness(img.get(px, py));
      if (br>rd) tmp.pixels[py*width+px]=get(px, py);
    }
  }
  tmp.updatePixels();
  int v = (int)random(-r, r);
  int v2 = (int)random(-r, r);
  image(tmp, v, v2);
}
 
void mouseClicked() {
  //doStuff();
}

Bonus: Mouse Follower


// P_2_2_3_02.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de
/**
 * form mophing process by connected random agents
 * two forms: circle and line
 * 
 * MOUSE
 * click               : start a new circe
 * position x/y        : direction and speed of floating
 * 
 * KEYS
 * 1-2                 : fill styles
 * 3-4                 : form styles circle/line
 * arrow up/down       : step size +/-
 * f                   : freeze. loop on/off
 * Delete/Backspace    : clear display
 * s                   : save png
 * r                   : start pdf recording
 * e                   : stop pdf recording
 */

import processing.pdf.*;
import java.util.Calendar;

boolean recordPDF = false;

int formResolution = 15;
int stepSize = 2;
float distortionFactor = 1;
float initRadius = 150;
float centerX, centerY;
float[] x = new float[formResolution];
float[] y = new float[formResolution];

boolean filled = false;
boolean freeze = false;
int mode = 0;


void setup(){
  // use fullscreen size 
  size(displayWidth, displayHeight);
  smooth();

  // init form
  centerX = width/2; 
  centerY = height/2;
  float angle = radians(360/float(formResolution));
  for (int i=0; i < formResolution; i++){
    x[i] = cos(angle*i) * initRadius;
    y[i] = sin(angle*i) * initRadius;  
  }

  stroke(0, 50);
  background(255);
}


void draw(){
  // floating towards mouse position
  if (mouseX != 0 || mouseY != 0) {
    centerX += (mouseX-centerX) * 0.01;
    centerY += (mouseY-centerY) * 0.01;
  }

  // calculate new points
  for (int i=0; i < formResolution; i++){
    x[i] += random(-stepSize,stepSize);
    y[i] += random(-stepSize,stepSize);
    // ellipse(x[i], y[i], 5, 5);
  }

  strokeWeight(0.75);
  if (filled) fill(random(255));
  else noFill();

  if (mode == 0) {
    beginShape();
    // start controlpoint
    curveVertex(x[formResolution-1]+centerX, y[formResolution-1]+centerY);

    // only these points are drawn
    for (int i=0; i < formResolution; i++){
      curveVertex(x[i]+centerX, y[i]+centerY);
    }
    curveVertex(x[0]+centerX, y[0]+centerY);

    // end controlpoint
    curveVertex(x[1]+centerX, y[1]+centerY);
    endShape();
  }

  if (mode == 1) {
    beginShape();
    // start controlpoint
    curveVertex(x[0]+centerX, y[0]+centerY);

    // only these points are drawn
    for (int i=0; i < formResolution; i++){
      curveVertex(x[i]+centerX, y[i]+centerY);
    }

    // end controlpoint
    curveVertex(x[formResolution-1]+centerX, y[formResolution-1]+centerY);
    endShape();
  }

}


void mousePressed() {
  // init forms on mouse position
  centerX = mouseX; 
  centerY = mouseY;

  // circle
  if (mode == 0) {
    centerX = mouseX;
    centerY = mouseY;
    float angle = radians(360/float(formResolution));
    float radius = initRadius * random(0.5,1.0);
    for (int i=0; i < formResolution; i++){
      x[i] = cos(angle*i) * radius;
      y[i] = sin(angle*i) * radius;
    }
  } 

  // line
  if (mode == 1) {
    centerX = mouseX;
    centerY = mouseY;
    float radius = initRadius * random(0.5,5.0);
    float angle = random(PI);
    radius = initRadius*4;
    angle = 0;
    
    float x1 = cos(angle) * radius;
    float y1 = sin(angle) * radius;
    float x2 = cos(angle-PI) * radius;
    float y2 = sin(angle-PI) * radius;
    for(int i=0; i < formResolution; i++) {
      x[i] = lerp(x1, x2, i/(float)formResolution);
      y[i] = lerp(y1, y2, i/(float)formResolution);
    }
  }
}


void keyPressed() {
  if (keyCode == UP) stepSize++;
  if (keyCode == DOWN) stepSize--;
  stepSize = max(stepSize, 1);
  println("stepSize: " + stepSize);
}

void keyReleased() {
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
  if (key == DELETE || key == BACKSPACE) background(255);

  if (key == '1') filled = false;
  if (key == '2') filled = true;
  if (key == '3') mode = 0;
  if (key == '4') mode = 1;

  // ------ pdf export ------
  // press 'r' to start pdf recording and 'e' to stop it
  // ONLY by pressing 'e' the pdf is saved to disk!
  if (key =='r' || key =='R') {
    if (recordPDF == false) {
      beginRecord(PDF, timestamp()+".pdf");
      println("recording started");
      recordPDF = true;
      stroke(0, 50);
    }
  } 
  else if (key == 'e' || key =='E') {
    if (recordPDF) {
      println("recording stopped");
      endRecord();
      recordPDF = false;
      background(255); 
    }
  } 

  // switch draw loop on/off
  if (key == 'f' || key == 'F') freeze = !freeze;
  if (freeze == true) noLoop();
  else loop();
}


// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Exercise 55


int _numChildren = 4;
int _maxLevels = 4;
Branch _trunk;
void setup() {
size(750,500);
background(255);
noFill();
smooth();
newTree();
}
void newTree() {
_trunk = new Branch(1, 0, width/2, height/2);
_trunk.drawMe();
}
void draw() {
background(255);
_trunk.updateMe(width/2, height/2);
_trunk.drawMe();
}


class Branch {
  float level, index;
  float x, y;
  float endx, endy;
  float strokeW, alph;
  float len, lenChange;
  float rot, rotChange;

  Branch[] children = new Branch[0];
  Branch(float lev, float ind, float ex, float why) {
    level = lev;
    index = ind;
    strokeW = (1/level) * 10;
    alph = 255 / level;
    len = (1/level) * random(500);
    rot = random(360);
    lenChange = random(10) - 5;
    rotChange = random(10) - 5;
    updateMe(ex, why);
    if (level < _maxLevels) {
      children = new Branch[_numChildren];
      for (int x=0; x < _numChildren; x++) {
        children[x] = new Branch(level+1, x, endx, endy);
      }
    }
  }
  void updateMe(float ex, float why) {
    x = ex;
    y = why;
    rot += rotChange;
    if (rot > 360) { 
      rot = 0;
    } else if (rot < 0) { 
      rot = 360;
    }
    len -= lenChange;
    if (len < 0) { 
      lenChange *= -1;
    } else if (len > 500) { 
      lenChange *= -1;
    }
    float radian = radians(rot);
    endx = x + (len * cos(radian));
    endy = y + (len * sin(radian));
    for (int i=0; i < children.length; i++) { 
        children[i].updateMe(endx, endy);
    }
  }
  void drawMe() {
    if (level > 1) {
      strokeWeight(strokeW);
      stroke(0, alph);
      fill(255, alph);
      line(x, y, endx, endy);
      ellipse(endx, endy, len/12, len/12);
    }
    for (int i=0; i < children.length; i++) {
      children[i].drawMe();
    }
  }
}

Exercise 54


// P_2_1_3_02.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de
/**
 * draw a module made of lines in a grid
 * 	 
 * MOUSE
 * position x          : number of tiles horizontally
 * position y          : number of tiles vertically
 * 
 * KEYS
 * 1-3                 : draw mode
 * s                   : save png
 * p                   : save pdf
 */


import processing.pdf.*;
import java.util.Calendar;

boolean savePDF = false;

float tileCountX = 5;
float tileCountY = 5;

int count = 10;
int colorStep = 20;

int lineWeight = 0;
int strokeColor = 0;

color backgroundColor = 0;

int drawMode = 1;

void setup() { 
  size(600, 600);
} 

void draw() { 
  if (savePDF) beginRecord(PDF, timestamp()+".pdf");

  colorMode(HSB, 360, 100, 100); 
  smooth();
  strokeWeight(0.5);
  strokeCap(ROUND);

  tileCountX = mouseX/30+1;
  tileCountY = mouseY/30+1;

  background(backgroundColor);

  for (int gridY=0; gridY<= tileCountY; gridY++) {
    for (int gridX=0; gridX<= tileCountX; gridX++) {  

      float tileWidth = width/tileCountX;
      float tileHeight = height/tileCountY;
      float posX = tileWidth*gridX;
      float posY = tileHeight*gridY;

      float x1 = tileWidth/2;
      float y1 = tileHeight/2;
      float x2 = 0;
      float y2 = 0;

      pushMatrix();
      translate(posX, posY);

      for(int side = 0; side < 4; side++) {
        for(int i=0; i< count; i++) {

           // move end point around the four sides of the tile
          if(side == 0){     
            x2 += tileWidth/count;
            y2 = 0;
          }
          if(side == 1){     
            x2 = tileWidth;
            y2 += tileHeight/count;
          }
          if(side == 2){     
            x2 -= tileWidth/count;
            y2 = tileHeight;
          }
          if(side == 3){     
            x2 = 0;
            y2 -= tileHeight/count;
          }

          // adjust weight and color of the line
          if(i < count/2){
            lineWeight += 1;
            strokeColor += 60;
          } 
          else {
            lineWeight -= 1;
            strokeColor -= 60;
          }

          // set colors depending on draw mode
          switch(drawMode){
          case 1:
            backgroundColor = 360;
            stroke(0);
            break;
          case 2:
            backgroundColor = 360;
            stroke(0);
            strokeWeight(lineWeight);
            break;
          case 3:
            backgroundColor = 0;
            stroke(strokeColor);
            strokeWeight(mouseX/100);
            break;
          }

          // draw the line
          line(x1, y1, x2, y2);
        }
      }

      popMatrix();
    }
  }

  if (savePDF) {
    savePDF = false;
    endRecord();
  }
} 


void keyPressed() {
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
  if (key == 'p' || key == 'P') savePDF = true;

  if (key == '1') drawMode = 1;
  if (key == '2') drawMode = 2;
  if (key == '3') drawMode = 3;
}


// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Exercise 53


// P_2_1_3_04.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de
/**
 * changing positions of stapled circles in a grid
 * 	 
 * MOUSE
 * position x          : module detail
 * position y          : module parameter
 * 
 * KEYS
 * 1-3                 : draw mode
 * arrow left/right    : number of tiles horizontally
 * arrow up/down       : number of tiles vertically
 * s                   : save png
 * p                   : save pdf
 */


import processing.pdf.*;
import java.util.Calendar;

boolean savePDF = false;

float tileCountX = 6;
float tileCountY = 6;
int count = 0;

int drawMode = 1;


void setup() { 
  size(550, 550);
} 


void draw() { 
  if (savePDF) beginRecord(PDF, timestamp()+".pdf");

  colorMode(HSB, 360, 100, 100); 
  rectMode(CENTER);
  smooth();
  stroke(0);
  noFill();
  background(360); 
  
  count = mouseX/10 + 10;
  float para = (float)mouseY/height;

  for (int gridY=0; gridY <= tileCountY; gridY++) {
    for (int gridX=0; gridX <= tileCountX; gridX++) {  

      float tileWidth = width / tileCountX;
      float tileHeight = height / tileCountY;
      float posX = tileWidth*gridX + tileWidth/2;
      float posY = tileHeight*gridY + tileHeight/2;

      pushMatrix();
      translate(posX, posY);

      // switch between modules
      switch (drawMode) {
      case 1:
        for(int i=0; i < count; i++) {
          rect(0, 0, tileWidth, tileHeight);
          scale(1 - 3.0/count);
          rotate(para*0.1);
        }
        break;

      case 2:
        for(float i=0; i < count; i++) {
          noStroke();
          color gradient = lerpColor(color(0), color(52, 100, 71), i/count);
          fill(gradient, i/count*200);
          rotate(PI/4);
          rect(0, 0, tileWidth, tileHeight);
          scale(1 - 3.0/count);
          rotate(para*1.5);
        }
        break;

      case 3:
        colorMode(RGB, 255);
        for(float i=0; i < count; i++) {
          noStroke();
          color gradient = lerpColor(color(0, 130, 164), color(255), i/count);
          fill(gradient,170);

          pushMatrix();
          translate(4*i,0);
          ellipse(0, 0, tileWidth/4, tileHeight/4);
          popMatrix();

          pushMatrix();
          translate(-4*i,0);
          ellipse(0, 0, tileWidth/4, tileHeight/4);
          popMatrix();

          scale(1 - 1.5/count);
          rotate(para*1.5);
        }

        break;
      }

      popMatrix();

    }
  }
  if (savePDF) {
    savePDF = false;
    endRecord();
  }
} 


void keyReleased(){
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
  if (key == 'p' || key == 'P') savePDF = true;

  if (key == '1') drawMode = 1;
  if (key == '2') drawMode = 2;
  if (key == '3') drawMode = 3;

  if (keyCode == DOWN) tileCountY = max(tileCountY-1, 1);
  if (keyCode == UP) tileCountY += 1;
  if (keyCode == LEFT) tileCountX = max(tileCountX-1, 1);
  if (keyCode == RIGHT) tileCountX += 1;

}

// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Exercise 52


// P_2_1_2_02.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de
/**
 * changing module color and positions in a grid
 * 	 
 * MOUSE
 * position x          : offset x
 * position y          : offset y
 * left click          : random position
 * 
 * KEYS
 * 1-3                 : different sets of colors
 * 0                   : default
 * arrow up/down       : background module size
 * arrow left/right    : foreground module size
 * s                   : save png
 * p                   : save pdf
 */

import processing.pdf.*;
import java.util.Calendar;

boolean savePDF = false;

color moduleColorBackground = color(0);
color moduleColorForeground = color(255);

color moduleAlphaBackground = 100;
color moduleAlphaForeground = 100;

float moduleRadiusBackground = 30;
float moduleRadiusForeground = 15;

color backColor = color(255);


float tileCount = 20;
int actRandomSeed = 0;

void setup(){
  size(600, 600);
}

void draw() {
  if (savePDF) beginRecord(PDF, timestamp()+".pdf");

  translate(width/tileCount/2, height/tileCount/2);

  colorMode(HSB, 360, 100, 100, 100);
  background(backColor);
  smooth();
  noStroke();

  randomSeed(actRandomSeed);

  for (int gridY=0; gridY < tileCount; gridY++) {
    for (int gridX=0; gridX < tileCount; gridX++) {
      float posX = width/tileCount * gridX;
      float posY = height/tileCount * gridY;

      float shiftX =  random(-1, 1) * mouseX/20;
      float shiftY =  random(-1, 1) * mouseY/20;

      fill(moduleColorBackground, moduleAlphaBackground);
      ellipse(posX+shiftX, posY+shiftY, moduleRadiusBackground, moduleRadiusBackground);
    }
  }

  for (int gridY=0; gridY < tileCount; gridY++) {
    for (int gridX=0; gridX < tileCount; gridX++) {

      float posX = width/tileCount * gridX;
      float posY = height/tileCount * gridY;

      fill(moduleColorForeground, moduleAlphaForeground);
      ellipse(posX, posY, moduleRadiusForeground, moduleRadiusForeground);
    }
  }

  if (savePDF) {
    savePDF = false;
    endRecord();
  }
}

void mousePressed() {
  actRandomSeed = (int) random(100000);
}

void keyReleased(){
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
  if (key == 'p' || key == 'P') savePDF = true;

  if (key == '1'){
    if (moduleColorBackground == color(0)) {
      moduleColorBackground = color(273, 73, 51);
    } 
    else {
      moduleColorBackground = color(0);
    } 
  }
  if (key == '2'){
    if (moduleColorForeground == color(360)) {
      moduleColorForeground = color(323, 100, 77);
    } 
    else {
      moduleColorForeground = color(360);
    } 
  }

  if (key == '3'){
    if (moduleAlphaBackground == 100) {
      moduleAlphaBackground = 50;
      moduleAlphaForeground = 50;
    } 
    else {
      moduleAlphaBackground = 100;
      moduleAlphaForeground = 100;
    } 
  }


  if (key == '0'){  
    moduleColorBackground = color(0);
    moduleColorForeground = color(360);
    moduleAlphaBackground = 100;
    moduleAlphaForeground = 100;
    moduleRadiusBackground = 20;
    moduleRadiusForeground = 10;
  }

  if (keyCode == UP) moduleRadiusBackground += 2;
  if (keyCode == DOWN) moduleRadiusBackground = max(moduleRadiusBackground-2, 10);
  if (keyCode == LEFT) moduleRadiusForeground = max(moduleRadiusForeground-2, 5);
  if (keyCode == RIGHT) moduleRadiusForeground += 2;

}

// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Exercise 51


// P_2_1_3_01.pde
// 
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
//
// http://www.generative-gestaltung.de

/**
 * changing circle amount, size and position in a grid
 * 	 
 * MOUSE
 * position x          : circle amount and size
 * position y          : circle position
 * left click          : random position
 * 
 * KEYS
 * s                   : save png
 * p                   : save pdf
 */

import processing.pdf.*;
import java.util.Calendar;

boolean savePDF = false;

float tileCountX = 10;
float tileCountY = 10;
float tileWidth, tileHeight;

int count = 0;
int colorStep = 15;
int circleCount;
float endSize, endOffset;

int actRandomSeed = 0;


void setup() { 
  size(800, 800);
  tileWidth = width / tileCountX;
  tileHeight = height / tileCountY;
} 


void draw() { 
  if (savePDF) beginRecord(PDF, timestamp()+".pdf");

  smooth();
  noFill();
  stroke(0, 128);
  background(255); 
  randomSeed(actRandomSeed);

  translate((width/tileCountX)/2, (height/tileCountY)/2);

  circleCount = mouseX/30 + 1;
  endSize = map(mouseX, 0, width, tileWidth/2.0, 0);
  endOffset = map(mouseY, 0, height, 0, (tileWidth-endSize)/2);

  for (int gridY=0; gridY <= tileCountY; gridY++) {
    for (int gridX=0; gridX <= tileCountX; gridX++) {  
      pushMatrix();
      translate(tileWidth*gridX, tileHeight*gridY);
      scale(1, tileHeight/tileWidth);

      int toggle = (int) random(0, 4);
      if (toggle == 0) rotate(-HALF_PI);  
      if (toggle == 1) rotate(0);  
      if (toggle == 2) rotate(HALF_PI);  
      if (toggle == 3) rotate(PI);  

      // draw module
      for (int i=0; i < circleCount; i++) {
        float diameter = map(i, 0, circleCount-1, tileWidth, endSize);
        float offset = map(i, 0, circleCount-1, 0, endOffset);
        ellipse(offset, 0, diameter, diameter);
      }
      popMatrix();
    }
  }

  if (savePDF) {
    savePDF = false;
    endRecord();
  }
} 


void mousePressed() {
  actRandomSeed = (int) random(100000);
}


void keyReleased() {
  if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
  if (key == 'p' || key == 'P') savePDF = true;
}


// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}