Close

Processing on the Pi - Assembly

A project log for Networked Low Resolution DMD Projectors

WiFi networked electro-mechanical 7x7 pixel flip-dot displays as giant DMDs (Digital Micromirror Devices) for low resolution projection

Michael ShaubMichael Shaub 02/25/2016 at 04:280 Comments

Date: 2/20/2016

The next step in Processing was to put the 3 animations together, with timers to run each for a certain amount of time and then automatically advance to the next. When I was assembling the code for the sequences, I also decided that a fourth sequence of a random dot pattern.

Below is the combined code. This also includes the first attempt to write the pixel display to the networked displays:

// Combined 5
// 
// For LinkSprite V3 and AlfaZeta flip-dot display
// by Michael Shaub 2016

import processing.serial.*;

import processing.net.*;

//Server and client stuff
Server s; 
Client c;

//switches
boolean drawPreview = true;
boolean useSerialPort = false;

// The serial port:
Serial myPort;       

int frameDelay = 50;

//variables for flipDot
int rowCounter = 0;
int row1 = 0; //hold variable for row1
int row2 = 0; //hold variable for row2
int row3 = 0; //hold variable for row3
int row4 = 0; //hold variable for row4
int row5 = 0; //hold variable for row5
int row6 = 0; //hold variable for row6
int row7 = 0; //hold variable for row7
//int rowArray[ ] = {0,0,0,0,0,0,0};
int rowArray[ ] = {0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0};
boolean mirrorX = true; //flip Left & Right?
boolean mirrorY = false; //flip Top & Bottom?

int dotSize = 50; //diameter of the dots - for preview
PImage pix = createImage(28,7, RGB); //PImage to hold the actual size image
int pixLength = pix.width*pix.height; //total amount of pixels

//snow mode variables
boolean drawRow = false; //use to alternate drawing a row of dots or skipping
int snowFrameRepeat = 0;
int snowFrames = 3; // repeat each snow frame 3 times
int snowCounter = 0;
int totalSnowFrames = 100; // how many times should we draw snow before moving on to the next mode

//gradation mode variables
int gradFrameRepeat = 0;
int gradFrames = 2; // repeat each snow frame 3 times
int gradCounter = 0;
int totalGradFrames = 100; // how many times should we draw snow before moving on to the next mode

//random mode variables
int randCounter = 0;
int randLikelyhood = 100; // how unlikely is it that a dot will be added (larger number = less likely)
int randTwinkle = 600; // how unlikely is it that a dot will be added (larger number = less likely)

float a = 0.0;
float inc = TWO_PI/25.0;
PGraphics pixOrig; //PGraphics to hold the actual size image

PImage text1;
PImage text2;
PImage text3;

int scrollCounter = 0;
int textSelector = 1;
int modeSelector = 4; //1=Scrolling Text, 2=snow, 3=gradation, 4=random noise

//set the number of columns that are being calculated
int numColumns = 28; //switch back to 7?

void setup(){
  size(1400,350);
  
  //Server and client stuff
  s = new Server(this, 12345);  // Start a simple server on a port
  
  // List all the available serial ports:
  printArray(Serial.list());
  
  // Open the port you are using at the rate you want:
  if(useSerialPort) myPort = new Serial(this, Serial.list()[0], 57600);

  ellipseMode(CORNER);
  noStroke();
  //frameRate(3);
  
  text1 = loadImage("artatthemca.png"); //PImage for text image
  text2 = loadImage("gensler.png"); //PImage for text image
  text3 = loadImage("fiftyon.png"); //PImage for text image
  
  pixOrig = createGraphics(28,7); //PGraphics to hold the actual size image
  
  //preset the PImage
  pix.loadPixels();
  for(int i=0; i<pixLength; i++){
    pix.pixels[i] = color(0); //set each pixel to black
  }
  pix.updatePixels();
}

void draw(){
  background(35);
  
  if(modeSelector > 4) { 
    modeSelector = 1; //reset any values that are too high
    //println("mode reset to: " + modeSelector);
  }
  
  switch(modeSelector){
    case(1):
      textScroller(); //Draw Text
      //println("I'm drawing text");
      break;
    case(2):
      pixelSnow(); //Draw Snow
      //println("I'm making snow");
      break;
    case(3):
      //gradation
      gradation();      
      //modeSelector = 1; //go back to the beginning
      break;
    case(4):
      //gradation
      randomSparkle();      
      //modeSelector = 1; //go back to the beginning
      break;
  }
  
  if(modeSelector != 2 && modeSelector != 3){
  //do dithering biz here
    pix.loadPixels();
    pixOrig.loadPixels();
    for(int y=0; y<7; y++){
      for(int x=0; x<28; x++){
        color oldpixel = pixOrig.get(x, y);
        color newpixel = findClosestColor( color(brightness(oldpixel) + random(-100,100)) ); //the wider the range of randomness the "softer" the result. Lower is harder edge
        pix.pixels[x+y*pix.width] = newpixel; //copy colors from one image to the other
      }
    }
    pix.updatePixels();
  }
  
  if(drawPreview) drawOnScreen(); //draw on screen
  
  sendToClients(); //send out to clients
  
  printToFlipDots(); //call the flipDot conversion
  
  delay (frameDelay);
}

void drawOnScreen(){
  //draw on screen
  pix.loadPixels();
  for(int j=0; j<7; j++){
    for(int i=0; i<28; i++){
      if(i < numColumns) { //for now, only do for the 1st 7 columns?
        if(mirrorX){
          rowArray[numColumns-1-i] = int( red(pix.pixels[i+j*pix.width])/255); //for now, only do for the 1st 7 columns
        }else{
          rowArray[i] = int( red(pix.pixels[i+j*pix.width])/255); //for now, only do for the 1st 7 columns
        }
        //println( rowArray[i] );
      }
      fill( pix.pixels[i+j*pix.width] ); //read pixel values, set fill color to that
      ellipse(i*dotSize,j*dotSize,dotSize,dotSize);
    }
    rowCounter = j;
    prepDotRow(j); //set the row through bitwise operations
  }
}

void prepDotRow(int rowNum){
  int tempRowCounter = rowNum+1;
  if(mirrorY) tempRowCounter = 7-rowNum;
  //bitshift values from array
  for(int i=0; i<rowArray.length; i++){
    int a = rowArray[i];
    int b = a << i; //bitshift the value the number of positions equal to the digit
    switch(tempRowCounter){
    case 1:
      row1 = row1 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 2:
      row2 = row2 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 3:
      row3 = row3 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 4:
      row4 = row4 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 5:
      row5 = row5 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 6:
      row6 = row6 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    case 7:
      row7 = row7 | b; //bitwise OR addition of the "b" value to the existing "row3" value
      break;
    }
  }
}

void sendToClients(){
  //send a single string that has comma deliminated values for each row and a line return at the end of the "frame"
  s.write(row1 + "," + row2 + "," + row3 + "," + row4 + "," + row5 + "," + row6 + "," + row7 + "\n");
}

void printToFlipDots(){
  int test_2[]= {0x80, 0x87, 0xFF, row1, row2, row3, row4, row5, row6, row7, 0x8F};
  //int test_2[]= {0x80, 0x87, 0xFF, 0, rowCounter, scrollCounter, 0, 0, 0, 0, 0x8F};
  
  for(int i=0; i<test_2.length; i++){
    if(useSerialPort) myPort.write(test_2[i]); 
    //println(test_2[i]);
  }
  
  //reset row values for next frame
  row1 = 0;
  row2 = 0;
  row3 = 0;
  row4 = 0;
  row5 = 0;
  row6 = 0;
  row7 = 0;
}

void randomSparkle() { //MODE 4
    //Draw Randomly
    int totalBright = 0;

    pixOrig.beginDraw();
    //pixOrig.background(0);
  
    for(int j=0; j<7; j++){
      for(int i=0; i<28; i++){
        totalBright += pixOrig.get(i,j)/255 ; //set the fill color to a smooth gradation
        if(random(randLikelyhood)<1){ //is the random number less than 1? A bigger variable = less likely
          if(randCounter==0) pixOrig.set(i,j, 255 ); //add a white dot
          if(randCounter==1) pixOrig.set(i,j, 0 ); //add a black dot
        }
        if(random(randTwinkle)<1){ //is the random number less than 1? A bigger variable = less likely
          pixOrig.set(i,j, 255 ); //add a white dot
          if(randCounter==1) totalBright++; //add
        }
        if(random(randTwinkle)<1){ //is the random number less than 1? A bigger variable = less likely
          pixOrig.set(i,j, 0 ); //add a black dot
          if(randCounter==0) totalBright--; //deduct
        }
      }
    }
    pixOrig.endDraw();
    
    if(totalBright > 28*5.5 && randCounter == 0) randCounter = 1; //start dimming
    
    if(totalBright < 28*1.5 && randCounter == 1) {
      randCounter = 0; //go on to the next mode
      modeSelector++;
    }
}

void pixelSnow() { //MODE 2
  //Draw Snow
  if(snowFrameRepeat > snowFrames){
    snowFrameRepeat=0;
    pix.loadPixels();
    
    //Copy each row of pixels down to the next row
    for(int j=6; j>0; j--){ //step through every row but the top one, in reverse
      int wind = round( random(-1,1) ); //set a value to blow the snow to the left, right, or none
      //println(wind);
      for(int i=0; i<28; i++){
        if(!drawRow && j==6){ //only if this is the bottom row and on a frame when the snow isn't drawn on the top row 
          //skip drawing over the last row this frame
        }else{
          //draw this frame
          int targetPix;
          //test boundaries for values
          if( (i+j*pix.width+wind) < 0){
            targetPix = 0;
          }else{
            if( (i+j*pix.width+wind) > 7*pix.width-1){
              targetPix = (i+j*pix.width); 
            }else{
              targetPix = (i+j*pix.width+wind);
            }
          }
            
          pix.pixels[targetPix] = pix.pixels[i+(j-1)*pix.width]; //read pixel values, set fill color to the row above
        }
      }
    }
    
    //Draw the first row of pixels with random values
    int lastRand = 0;
    for(int i=0; i<pix.width; i++){
      int randValue = 0;
      if(drawRow){
        randValue = round(random(.6))*255; //randomly black or white, decimal sets how likely it'll be a white dot
        if(i>0 && lastRand==255){
          randValue = 0; //never have 2 white dots in a row
        }
      }
      pix.pixels[i] = color(randValue); //set each pixel to black or white
      lastRand = randValue;
    }
    
    drawRow = !drawRow; //toggle the variable
  
    pix.updatePixels();
    snowCounter++; //count the number of snow frames so far
  }
  snowFrameRepeat++;
  if(snowCounter == totalSnowFrames) {
    modeSelector = 3; //move on to the next mode
    //println("mode: " + modeSelector);
    snowCounter = 0; //reset the counter
  }
}

void gradation(){ //MODE 3
  if(gradFrameRepeat > gradFrames){
    gradFrameRepeat=0;
    
    //Draw Gradation
    pixOrig.beginDraw();
    pixOrig.background(0);
  
    for(int j=0; j<7; j++){
      for(int i=0; i<28; i++){
        //pixOrig.pixels[i+j*pix.width] = int(128+sin(a)*128.0); //set the fill color to a smooth gradation
        pixOrig.set(i,j, int(128+sin(a)*128.0) ); //set the fill color to a smooth gradation
        a = a + inc;
      }
    }
    pixOrig.endDraw();
    
    //do dithering biz here
      pix.loadPixels();
      pixOrig.loadPixels();
      for(int y=0; y<7; y++){
        for(int x=0; x<28; x++){
          color oldpixel = pixOrig.get(x, y);
          color newpixel = findClosestColor( color(brightness(oldpixel) + random(-100,100)) ); //the wider the range of randomness the "softer" the result. Lower is harder edge
          pix.pixels[x+y*pix.width] = newpixel; //copy colors from one image to the other
        }
      }
      pix.updatePixels();
    
    
    if(gradCounter == totalGradFrames) {
      modeSelector = 1; //move on to the next mode
      //println("mode: " + modeSelector);
      gradCounter = 0; //reset the counter
    }else{
      gradCounter++;
    }
  }
  gradFrameRepeat++;
}

void textScroller(){ //MODE 1
  
  /*
  if(textSelector > 3) {
    textSelector = 1;
    //scrollCounter = 0;
    modeSelector++;
    println("mode: " + modeSelector);
  }*/
  
    //println("drawing text: " + textSelector);
    //println("scroll counter: " + scrollCounter);
    //Draw Text
    pixOrig.beginDraw();
    pixOrig.background(0);
    switch(textSelector){
      case(1):
        pixOrig.image(text1, pixOrig.width-scrollCounter, 0); //draw text starting offscreen to the right
        break;
      case(2):
        pixOrig.image(text2, pixOrig.width-scrollCounter, 0); //draw text starting offscreen to the right
        break;
      case(3):
        pixOrig.image(text3, pixOrig.width-scrollCounter, 0); //draw text starting offscreen to the right
        break;
    }
    pixOrig.endDraw();
    
    switch(textSelector){
      case(1):
        if(scrollCounter > text1.width+pixOrig.width){
          scrollCounter = 0;
          textSelector = 2;
          modeSelector = 2;
          //println("mode: " + modeSelector);
        }
        break;
      case(2):
        if(scrollCounter > text2.width+pixOrig.width){
          scrollCounter = 0;
          textSelector = 3;
          modeSelector = 2;
          //println("mode: " + modeSelector);
        }
        break;
      case(3):
        if(scrollCounter > text3.width+pixOrig.width){
          scrollCounter = 0;
          textSelector = 1;
          modeSelector = 2;
          //println("mode: " + modeSelector);
        }
        break;
    }
  scrollCounter++;
}



void keyPressed(){
  if(key == 'x') mirrorX = ! mirrorX;
  if(key == 'y') mirrorY = ! mirrorY;
}

color findClosestColor(color c) {
  color r;
  // Treshold function
  if (brightness(c) < 128) {
    r = color(0);
  }
  else {
    r = color(255);
  }
  return r;
}

Discussions