creatyde-Scope

Praxinoscope Hands!

My design is of a moving hand opening then closing.  I personally like hands because of their expressive nature.  I took about 10 to 12 pictures of my hand against a white background in bright light then used OpenCV to extract the Scharr Edges.  I had to mess around with some of the blur and thresholding to get the image of the hand’s shadow to look sharp.

Please click on the link to view the actual praxinoscope design:
Praxinoscope Hands

Here is the associated code:

// Template for KidzLabs/4M/Toysmith Animation Praxinoscope
// https://www.amazon.com/4M-3474-Animation-Praxinoscope/dp/B000P02HYC
// https://www.walmart.com/ip/Animation-Praxinoscope-Science-Kits-by-Toysmith-3474/45681503
// Developed for Processing 3.3.6 * http://processing.org
// 23 January 2018 * Golan Levin 

// See information about Processing PDF export at: 
// https://processing.org/reference/libraries/pdf/index.html
// PDF generated by Processing can be opened in Adobe Illustrator.
import processing.pdf.*;

// Thanks to OpenCV made by Greg Borenstein
import gab.opencv.*;

PImage img;
OpenCV opencv;

boolean bRecordingPDF = false;

float inch = 72; 
float diamArtInner = inch * 1.50; 
float diamArtOuter = inch * 4.80; 
float diamCutInner = inch * 1.41; 
float diamCutOuter = inch * 4.875; 
float holeDy = inch * 0.23;
float holeDx = inch * 0.20;
float holeD = inch * 0.1;

final int nFrames = 10; 
int myFrameCount = 0;
int exportFrameCount = 0; 
boolean bAnimate = true; 
boolean bExportFrameImages = false;

//-------------------------------------------------------
void setup() {
  size(792, 612); // 11x8.5" at 72DPI
  frameRate(15);
  smooth();
} 

//-------------------------------------------------------
void draw() {
  background(240); 
  if (bRecordingPDF) {
    beginRecord(PDF, "praxinoscope-output.pdf");
  }

  // Do all the drawing. 
  pushMatrix(); 
  translate(width/2, height/2);
  drawCutLines(); 
  drawGuides(); 
  drawAllFrames();
  popMatrix();
  
  if (bExportFrameImages) {
    // If activated, export .PNG frames 
    if (exportFrameCount < nFrames) { 
      String filename = "frame_" 
        + nf((exportFrameCount%nFrames), 3) + ".png"; 
      saveFrame("frames/" + filename); 
      println("Saved: " + filename); 
      exportFrameCount++; 
      if (exportFrameCount >= nFrames) {
        bExportFrameImages = false;
        exportFrameCount = 0;
      }
    }
  }

//-------------------------------------------------------
void keyPressed() {
  switch (key) {
  case ' ': 
    // Press spacebar to pause/unpause the animation. 
    bAnimate = !bAnimate;
    break;

  case 'p': 
  case 'P':
    // Press 'p' to export a PDF for the Praxinoscope.
    bRecordingPDF = true; 
    break;

  case 'f': 
  case 'F': 
    // Press 'f' to export .png Frames (to make an animated .GIF)
    myFrameCount = 0; 
    exportFrameCount = 0; 
    bExportFrameImages = true;
    bAnimate = true; 
    break;
  }
}

//-------------------------------------------------------
void drawCutLines() {
  fill(0); 
  textAlign(CENTER, BOTTOM); 
  text("Praxinoscope Template", 0, 0-diamCutOuter/2-6); 
  
  stroke(0); 
  strokeWeight(1.0);
  
  noFill(); 
  if (!bRecordingPDF) {
    fill(255); 
  }
  ellipse(0, 0, diamCutOuter, diamCutOuter);
  
  noFill(); 
  if (!bRecordingPDF) {
    fill(240); 
  }
  ellipse(0, 0, diamCutInner, diamCutInner);
  
  noFill(); 
  ellipse(diamCutOuter/2 - holeDx, 0-holeDy, holeD, holeD); 
  
  line (diamCutInner/2, 0, diamCutOuter/2, 0);
}

//-------------------------------------------------------
void drawGuides() {
  // This function draws the guidelines. 
  // Don't draw these when we're exporting the PDF. 
  if (!bRecordingPDF) {

    noFill(); 
    stroke(128); 
    strokeWeight(0.2); 
    ellipse(0, 0, diamArtInner, diamArtInner); 
    ellipse(0, 0, diamArtOuter, diamArtOuter);

    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(128); 
      strokeWeight(0.2);
      line (pxi, pyi, pxo, pyo);
    }

    // Draw the red wedge outline, highlighting the main view.
    int redWedge = 7; // assuming nFrames = 10
    for (int i=redWedge; i<=(redWedge+1); i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(255, 0, 0); 
      strokeWeight(2.0);
      line (pxi, pyi, pxo, pyo);
    }
    noFill(); 
    stroke(255, 0, 0); 
    strokeWeight(2.0);
    float startAngle = redWedge*TWO_PI/nFrames;
    float endAngle = (redWedge+1)*TWO_PI/nFrames;
    arc(0, 0, diamArtInner, diamArtInner, startAngle, endAngle); 
    arc(0, 0, diamArtOuter, diamArtOuter, startAngle, endAngle); 


    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 

      pushMatrix();
      rotate(angle); 
      float originY = ((diamArtOuter + diamArtInner)/2)/2;
      translate(0, 0-originY); 

      noFill(); 
      stroke(128); 
      strokeWeight(0.2);
      line (-inch/2, 0, inch/2, 0); 
      line (0, -inch/2, 0, inch/2); 

      popMatrix();
    }
  }
}

//-------------------------------------------------------
void drawAllFrames() {
  for (int i=0; i<nFrames; i++) {
    float angle = map(i, 0, nFrames, 0, TWO_PI); 
    float originY = ((diamArtOuter + diamArtInner)/2)/2;

    pushMatrix();
    rotate(angle); 
    translate(0, 0-originY); 
    scale(0.8, 0.8); // feel free to ditch this 

    int whichFrame = i; 
    if (bAnimate) {
      whichFrame = (i+myFrameCount)%nFrames;
    }
    drawArtFrame (whichFrame); 
    // drawArtFrameAlternate (whichFrame); 

    popMatrix();
  }
  myFrameCount++;
}


//-------------------------------------------------------
void drawArtFrame (int whichFrame) { 
  // Draw the artwork for a generic frame of the Praxinoscope, 
  // given the framenumber (whichFrame) out of nFrames.
  // NOTE #1: The "origin" for the frame is in the center of the wedge.
  // NOTE #2: Remember that everything will appear upside-down!

  // Draw the frame number
  fill(0); 
  noStroke(); 
  textAlign(CENTER, CENTER); 
  text (whichFrame, -1, -47);

  img = loadImage("faces_small/image" + whichFrame +".png");
  img.resize(75, 75);
  opencv = new OpenCV(this, img);

  //opencv.threshold(80);
  opencv.blur(5);
  //opencv.findCannyEdges(40, 75);
  opencv.findScharrEdges(OpenCV.HORIZONTAL);
  //opencv.findSobelEdges(1,0);
  opencv.contrast(1.5);//(int)map(mouseX,0,width,-255,255));
  opencv.threshold(175);
  opencv.invert();
  
  //opencv.blur(2);
  image(opencv.getOutput(),-37.5,-37.5);

  /**
  // Draw a pulsating ellipse
  noFill(); 
  stroke(0);
  strokeWeight(1); 
  float t = map(whichFrame, 0, nFrames, 0, 1); 
  float diam = map(cos(t*TWO_PI), -1, 1, 25, 50); 
  ellipse(0, -45, diam, diam*0.8); 

  // Draw some expanding boxes, centered on the local origin
  int nBoxes = 3;
  for (int i=0; i<nBoxes; i++) {
    float F = ((whichFrame + i*nFrames)/(float)nBoxes)%nFrames;
    float rs = map(F, 0, nFrames-1, 0, 35); 
    float rx = 0; 
    float ry = 0; 
    float rg = map(F, 0, nFrames, 0, 255); 
    stroke(rg); 
    strokeWeight(1.0);
    rect(rx-rs/2, ry-rs/2, rs, rs);
  }

  // Draw some rotating spokes
  int nSpokes = 7;
  for (int i=0; i<nSpokes; i++) {
    float cx = 0; 
    float cy = 45; 
    float u = 0 - map(whichFrame + i*nFrames, 0, nFrames*nSpokes, 0, 1);
    float sx = cx + 15 * cos(u * TWO_PI); 
    float sy = cy + 15 * sin(u * TWO_PI); 
    stroke(0); 
    strokeWeight(1);
    line (cx, cy, sx, sy);
  }
  **/
}

//-------------------------------------------------------
void drawArtFrameAlternate(int whichFrame) { 
  // An alternate drawing test. 
  // Draw a falling object. 


  // Draw a little splat on the frame when it hits the ground. 
  if (whichFrame == (nFrames-1)) {
    stroke(0, 0, 0); 
    strokeWeight(0.5); 
    int nL = 10;
    for (int i=0; i<nL; i++) {
      float a = HALF_PI + map(i, 0, nL-1, 0, TWO_PI);
      float cx = 12 * cos(a);
      float cy = 10 * sin(a); 
      float dx = 16 * cos(a);
      float dy = 13 * sin(a); 
      line (cx, 45+cy, dx, 45+dy);
    }
  }

  // Draw a little box frame
  fill(255); 
  stroke(0, 0, 0);
  strokeWeight(1); 
  rect(-5, -50, 10, 100); 

  // Make the puck accelerate downward
  float t = map(whichFrame, 0, nFrames-1, 0, 1); 
  float t2 = pow(t, 2.0); 
  float rh = 8 + whichFrame * 0.5; // wee stretch
  float ry = map(t2, 0, 1, 0, 100-rh) - 50; 

  noStroke(); 
  fill(0, 0, 0);
  rect(-5, ry, 10, rh);
}