Close

Processing on the Pi – Scrolling Text

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:180 Comments

Date: 2/19/2016

Scrolling text looks great on even the lowest resolution displays. The persistence of vision effect is so strong that the words move by and I never notice the pixels. I remembered some really tiny pixelated typefaces including Silkscreen and thought I could easily get that working in Processing. I got the typeface loaded in Processing just fine, but when I scaled it down to just 5 pixels tall the text was very blurry.

I wasn't sure how to fix that problem so I decided to just make some high-res images with text, scale them down, and display the images. It's not ideal, doesn't allow for any adjustment or customization, but worked just fine for the moment.

Below is a video of text scrolling across the flip-dot display:

Here's a video of the reflection off the display onto the ceiling:

And, after an image of the text I used, the code for scrolling text:


// Scroll Text 2
// 
// For LinkSprite V3 and AlfaZeta flip-dot display
// by Michael Shaub 2016

import processing.serial.*;

// 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};
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
boolean drawRow = false; //use to alternate drawing a row of dots or skipping

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;

void setup(){
  size(1400,350);
  
  // List all the available serial ports:
  printArray(Serial.list());
  
  // Open the port you are using at the rate you want:
  myPort = new Serial(this, Serial.list()[0], 57600);

  ellipseMode(CORNER);
  noStroke();
  //frameRate(3);
  
  text1 = loadImage("artatthemca.png"); //PImage for text image
  text2 = loadImage("2.png"); //PImage for text image
  text3 = loadImage("3.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);
  
  switch(textSelector){
    case(1):
      if(scrollCounter > text1.width+pixOrig.width){
        scrollCounter = 0;
        textSelector++;
      }
      break;
    case(2):
      if(scrollCounter > text2.width+pixOrig.width){
        scrollCounter = 0;
        textSelector++;
      }
      break;
    case(3):
      if(scrollCounter > text3.width+pixOrig.width){
        scrollCounter = 0;
        textSelector++;
      }
      break;
  }
  if(textSelector > 3) textSelector = 1;
  
  //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();
  
  
  //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();
  
  //draw on screen
  pix.loadPixels();
  for(int j=0; j<7; j++){
    for(int i=0; i<28; i++){
      if(i < 7) {
        if(mirrorX){
          rowArray[6-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
  }
  printToFlipDots(); //call the flipDot conversion
  
  scrollCounter++;
  delay (frameDelay);
}

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 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++){
    myPort.write(test_2[i]); 
  }
  
  //reset row values for next frame
  row1 = 0;
  row2 = 0;
  row3 = 0;
  row4 = 0;
  row5 = 0;
  row6 = 0;
  row7 = 0;
}

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