Erica

30 Jan 2013

textRainTo implement Text Rain in Processing, I created a few helper classes. First, I create a Character class that draws a character at its current x and y location and detects whether or not the character is free falling or if it landed on a dark spot. It does so by checking the brightness of the pixel directly below it and setting a boolean within the class. I also created a camera class so that I could test the application out with different types of cameras, namely black -and-white and grayscale. I had some issues with thresholding background brightness so I tried mapping each pixel’s brightness to an exponential function to create a bigger differentiation between light and dark values but I still find that I need to adjust the threshold based on the location in which I am running the project.

Text Rain from Erica Lazrus on Vimeo.

Below is my code which can also be downloaded here:

Main text rain class:

import processing.video.*;

Capture camera;
CameraBlackWhite bwCamera; 
CameraGrayscale gsCamera;

color bgColor;

String text;
Character[] characterSet1;
Character[] characterSet2;
Character[] characterSet3;

public void setup() {
  size(640, 480, P2D);
  smooth();

  int threshold = 50;

  gsCamera = new CameraGrayscale(this, threshold);
  gsCamera.startVideo();

  bgColor = color(#ffffff);

  text = "We are synonyms for limbs' loosening of syntax, and yet turn to nothing: It's just talk.";
  characterSet1 = new Character[text.length()];
  characterSet2 = new Character[text.length()];
  characterSet3 = new Character[text.length()];
  for (int i=0; i < text.length(); i++) {
    char c = text.charAt(i);
    color col = color(random(255), random(255), random(255));
    float speed = random(1, 6);
    characterSet1[i] = new Character(c, col, 14, 5 + i*7.25, speed, threshold);
    characterSet2[i] = new Character(c, col, 14, 5 + i*7.25, speed, threshold);
    characterSet3[i] = new Character(c, col, 14, 5 + i*7.25, speed, threshold);

    characterSet1[i].start();
  }
}

public void draw() {
  background(bgColor);
  update();
  render();
}

public void update() {
  gsCamera.update();

  for (int i=0; i < text.length(); i++) {
    characterSet1[i].update();

    if (characterSet1[i].getCurYPos() > height/2) {
      characterSet2[i].start();
    }
    else if (characterSet2[i].getCurYPos() - textAscent() >= height || characterSet2[i].getCurYPos() < 0) {
      characterSet2[i].setCurYPos(0-(textAscent() + textDescent()));
      characterSet2[i].stop();
    }

    characterSet2[i].update();
  }
}

public void render() {
  for (int i=0; i < text.length(); i++) {
    characterSet1[i].render();
    characterSet2[i].render();
    characterSet3[i].render();
  }
}

Abstract camera class:

public abstract class Camera {
  private Capture video;
  private int numPixels;
  
  private int threshold;
  
  public void startVideo() {
    getVideo().start();
  }
  
  public abstract void update();
  
  public abstract void render();
  
  public Capture getVideo() {
    return this.video;
  }
  
  public void setVideo(Capture video) {
    this.video = video;
  }
  
  public int getNumPixels() {
    return this.numPixels;
  }
  
  public void setNumPixels(int numPixels) {
    this.numPixels = numPixels;
  }
  
  public int getThreshold() {
    return this.threshold;
  }
  
  public void setThreshold(int threshold) {
    this.threshold = threshold;
  }
}

Black and white camera class:

public class CameraBlackWhite extends Camera {
  private color BLACK = color(#000000);
  private color WHITE = color(#ffffff);

  public CameraBlackWhite(Text_Rain_2_0 applet, int threshold) {
    setVideo(new Capture(applet, width, height));
    setNumPixels(getVideo().width * getVideo().height);

    setThreshold(threshold);
  }

  public void update() {
    if (getVideo().available()) {
      getVideo().read();
      getVideo().loadPixels();

      loadPixels();

      float pixelBrightness;
      for (int i=0; i < getNumPixels(); i++) {
        int pixelX = i % width;
        int pixelY = i / width;

        pixelBrightness = brightness(getVideo().pixels[i]);
        pixelBrightness = pow(pixelBrightness, 3);
        pixelBrightness = map(pixelBrightness, 0, 16581375, 0, 255);

        if (pixelBrightness > getThreshold()) {
          pixels[(width-1-pixelX) + pixelY*getVideo().width] = WHITE;
        }
        else {
          pixels[(width-1-pixelX) + pixelY*getVideo().width] = BLACK;
        }
      }

      updatePixels();
    }
  }

  public void render() {
  }
}

Grayscale camera class:

public class CameraGrayscale extends Camera {
  private color BLACK = color(#000000);
  private color WHITE = color(#ffffff);

  public CameraGrayscale(Text_Rain_2_0 applet, int threshold) {
    setVideo(new Capture(applet, width, height));
    setNumPixels(getVideo().width * getVideo().height);

    setThreshold(threshold);
  }

  public void update() {
    if (getVideo().available()) {
      getVideo().read();
      getVideo().loadPixels();

      loadPixels();

      float pixelBrightness;
      for (int i=0; i < getNumPixels(); i++) {
        int pixelX = i % width;
        int pixelY = i / width;

        pixelBrightness = brightness(getVideo().pixels[i]);
        pixelBrightness = pow(pixelBrightness, 3);
        pixelBrightness = map(pixelBrightness, 0, 16581375, 0, 255);

        if (pixelBrightness > getThreshold()) {
          pixels[(width-1-pixelX) + pixelY*getVideo().width] = WHITE;
        }
        else {
          pixels[(width-1-pixelX) + pixelY*getVideo().width] = color(pixelBrightness);
        }
      }

      updatePixels();
    }
  }

  public void render() {
  }
}

Character class:

public class Character {
  private char c;
  private color col;
  private int sz;

  private float xPos;
  private float curYPos;
  private float ySpeed;

  private int threshold;
  private boolean falling;

  public Character(char c, color col, int sz, int threshold) {
    setC(c);
    setCol(col);
    setSz(sz);

    setXPos(0);
    setCurYPos(0-(textAscent() + textDescent()));
    setYSpeed(0);

    setThreshold(threshold);
    setFalling(false);
  }
  
  public void start() {
    setFalling(true);
  }
  
  public void stop() {
    setFalling(false);
  }

  public Character(char c, color col, int sz, float xPos, float ySpeed, int threshold) {
    setC(c);
    setCol(col);
    setSz(sz);

    setXPos(xPos);
    setCurYPos(textAscent() + textDescent());
    setYSpeed(ySpeed);

    setThreshold(threshold);
    setFalling(true);
  }

  public void update() {
    if (getCurYPos() < 0 || ceil(getCurYPos() + 1) >= height || isLocationBright((int)getXPos(), (int)getCurYPos() + 1)) {
      setFalling(true);
    }
    else {
      setFalling(false);
    }
    
    if (isFalling()) {
      textSize(getSz());

      float newYPos = getCurYPos() + getYSpeed();
      setCurYPos(newYPos);

      if ((newYPos - textAscent())> height) {
        setCurYPos(0-(textAscent() + textDescent()));
      }
    }
    else {
      setCurYPos(findFirstBrightSpot((int)getXPos(), (int)getCurYPos()));
    }
  }

  public void render() {
    fill(getCol());
    textSize(getSz());
    textAlign(CENTER, BOTTOM);
    text(getC(), getXPos(), getCurYPos());
  }
  
  public boolean isLocationBright(int x, int y) {
    int testValue = get(x, y);
    float testBrightness = brightness(testValue);
    return (testBrightness > getThreshold());
  }
  
  public int findFirstBrightSpot(int x, int y) {
    int yPos;
    for (yPos = y; yPos > 0; yPos--) {
      if (isLocationBright(x, yPos)) break;
    }
    return yPos;
  }

  public char getC() {
    return this.c;
  }

  public void setC(char c) {
    this.c = c;
  }

  public color getCol() {
    return this.col;
  }

  public void setCol(color col) {
    this.col = col;
  }

  public int getSz() {
    return this.sz;
  }

  public void setSz(int sz) {
    this.sz = sz;
  }

  public float getXPos() {
    return xPos;
  }

  public void setXPos(float xPos) {
    this.xPos = xPos;
  }

  public float getCurYPos() {
    return this.curYPos;
  }

  public void setCurYPos(float curYPos) {
    this.curYPos = curYPos;
  }

  public float getYSpeed() {
    return this.ySpeed;
  }

  public void setYSpeed(float ySpeed) {
    this.ySpeed = ySpeed;
  }

  public int getThreshold() {
    return this.threshold;
  }
  
  public void setThreshold(int threshold) {
    this.threshold = threshold;
  }

  public boolean isFalling() {
    return this.falling;
  }

  public void setFalling(boolean falling) {
    this.falling = falling;
  }
}