Category Archives: text-rain

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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;
  }
}

Caroline

29 Jan 2013

Screen shot 2013-01-29 at 4.34.17 PM

 

Text Rain is a famous interactive installation by Camille Utterback (1999). letters from a poem about motion and the body would rain down on viewers, resting on anything above a certain darkness threshold. If there is a flat surface for a long enough period words and sentence fragments become legible. Text Rain was revolutionary for it’s time because it was in the first waves of interactive art and was written before there were high level programming tools. I re-wrote text rain in Processing for this assignment.

Code on GitHub: https://github.com/crecord/textRain

Joshua

29 Jan 2013

textRain_screenShot_JLB

To implement text rain I created a class called Letter which contains a position, velocity, char, and some functions to move the letter and check if it is sitting on dark pixels.  To move the letters up I check another pixel above the first, and if it is also dark enough, the letter moves up to that position.  After changing the video to grey scale I simply check the red values (r,g, and b are now all the same) and if they are below a threshold, which i determined experimentally, they cause the letter to stop moving downward, or even move upward.

https://github.com/jlopezbi/textRainImplementation

 

 

import processing.video.*;
Capture video;
int[] backgroundPixels;
int numPixels;
int numLetters = 300;
float threshold = 31;
ArrayList rain;
 
void setup() {
  size(600, 450, P2D);
  smooth();
  video = new Capture(this, 160, 120);
  video.start();
  numPixels = video.width * video.height;
  backgroundPixels = new int[numPixels];
  loadPixels();
 
  rain = new ArrayList();
  for (int i = 0; i&lt; numLetters;i++) {
    genRandLtr();
  }
}
 
void draw() {
  if (video.available()) {
    video.read();
    video.filter(GRAY);
    image(video, 0, 0, width, height);
 
  }
  loadPixels();
  for (int i =0;i&lt;rain.size()-1;i++) {
    Letter l = (Letter) rain.get(i);
    l.updatePos();
    l.display();
    if (l.finished()) {
      rain.remove(i);
      genRandLtr();
    }
  }
  color pix = pixels[mouseY*width+mouseX];
  println(red(pix) +" "+ green(pix)+" "+ blue(pix));
  updatePixels();
}
 
void genRandLtr() {
  PVector pos = new PVector(random(0, width), 30);
  PVector vel = new PVector(0, random(0.5, 1));
 
  int lowerUpper = (int)random(2);
  int ascii;
  if (lowerUpper == 0) {
    ascii = (int) random(65, 90.1);
  }
  else {
    ascii = (int) random(97, 122.1);
  }
 
  char ltr = char(ascii);
  Letter letter = new Letter(pos, vel, ltr);
  rain.add(letter);
}
 
class Letter {
  //GLOBAL VARIABLES
  PVector pos;
  PVector vel;
  char ltr;
  int aboveCheck = 4;
  //CONTRUCTOR
  Letter(PVector _pos, PVector _vel, char _ltr){
    pos = _pos;
    vel = _vel;
    ltr = _ltr;
 
  }
 
  //FUNCTIONS
  void display(){
    textAlign(CENTER,BOTTOM);
    text(ltr, pos.x,pos.y);
    noFill();
    //ellipse(pos.x,pos.y,5,5);
  }
 
  void setRandPos(){
    pos = new PVector(random(0,width), 0);
  }
 
  void updatePos(){
    float xPos = pos.x;
    float yPos = pos.y;
    int indexAhead = int(xPos)+int(yPos)*width;
    int indexAbove = int(xPos)+int(yPos-aboveCheck)*width;
    float aheadCol = red(pixels[indexAhead]);
    float aboveCol = red(pixels[indexAbove]);
    if(aheadCol&gt;threshold){
      pos.set(xPos+vel.x, yPos+vel.y,0);
    } 
    else if(aboveCol&lt;=threshold){      
       pos.set(xPos, yPos-aboveCheck,0);     
     }        
}      
 
boolean finished(){     
   return(pos.y&gt;=height-1 || pos.y&lt;=aboveCheck);
  }  
}

Alan

28 Jan 2013

The solution is using Prof Levin’s way to measure the brightness of each pixel on the screen canvas. If the pixel in the letter’s position is below the threshold lower value, the letter should go back where it is bright enough instead of going down.

The text is read from a ‘captain.txt’ file, repeatedly falling from the top of canvas. When it goes down to the bottom, I reset its y value.

 

// Text Rain (Processing Re-Code "cover version")
// Original by Camille Utterback and Romy Achituv (1999):
// http://camilleutterback.com/projects/text-rain/
// Implemented in Processing 2.0b7 by Han Hua, January 2013
// 
// This assumes that the participant is in front of a light-colored background. 
 
//===================================================================
// The live video camera Capture object:
import processing.video.*;
Capture video;
 
float letterGravity = 2;
int brightnessThreshold = 110;
float initialLetterYPosition = 10;
TextRainLetter poemLetters[];
int nLetters;
 
String poemLines[];
int line_index = 0;
BufferedReader reader;
int m_sec;
int nextAddTime = 5000;
 
//-----------------------------------
void setup() {
  frameRate(30);
 
  size(640,480); 
  video = new Capture (this, width, height);
  video.start();  
 
  poemLines = new String[30];
  reader = createReader("captain.txt");
  readText();
 
  String poemString = poemLines[line_index];
  nLetters = poemString.length();
  poemLetters = new TextRainLetter[nLetters];
  for (int i=0; i&lt;nLetters; i++) {
    char c = poemString.charAt(i);
    float x = random(width * ((float)i/(nLetters+1)) + 1, width * ((float)(i+1)/(nLetters+1)));
    float y = random(initialLetterYPosition, initialLetterYPosition+10);
    poemLetters[i] = new TextRainLetter(c,x,y);
  }
}
 
//-----------------------------------
void draw() {
  if (video.available()) {
    video.read();
    video.loadPixels();
 
    // this translate &amp; scale flips the video left/right. 
    pushMatrix();
 
    // mirror the video
    translate (width,0); 
    scale (-1,1); 
    image (video, 0, 0, width, height); // refresh
    popMatrix();
 
    for (int i=0; i&lt;nLetters; i++) {       poemLetters[i].update();       poemLetters[i].draw();     }   }      m_sec = millis();   if(m_sec &gt; nextAddTime){
    println("1");
    nextAddTime += 3000;
    line_index++;
    if(line_index &gt; poemLines.length-1){
      line_index = 0;
    }
    addNewLetters();
  }
}
 
void readText() {
  String line = null;
   try{
       int i = 0;
       while ((line = reader.readLine()) != null) {
          poemLines[i] = line;
          i++;
          //println(line);
       }
 
   }catch(Exception e)
  {
   e.printStackTrace();
    line = null;
  }
}
 
void addNewLetters(){
  TextRainLetter tempLetters[] = new TextRainLetter[poemLetters.length + poemLines[line_index].length()];
 
  int i=0;
  for(; i &lt; poemLetters.length; i++){
    tempLetters[i] = poemLetters[i];
  }
 
  String s = poemLines[line_index];
  int n = s.length();
  for(int j = 0; j&lt;n; j++, i++){     char c = s.charAt(j);     float x = random(1, width-1);     float y = random(initialLetterYPosition, initialLetterYPosition+30);     tempLetters[i] = new TextRainLetter(c,x,y);   }      poemLetters = tempLetters;   nLetters = poemLetters.length; } //----------------------------------- void keyPressed() {   if (key == CODED) {     if (keyCode == UP) {       brightnessThreshold = min(255, brightnessThreshold+5);       println("brightnessThreshold = " + brightnessThreshold);      } else if (keyCode == DOWN) {       brightnessThreshold = max(0, brightnessThreshold-5);       println("brightnessThreshold = " + brightnessThreshold);     }    }  } //=================================================================== class TextRainLetter {      char  c;   float x;    float y;      TextRainLetter (char cc, float xx, float yy) {     c = cc;     x = xx;     y = yy;   }   //-----------------------------------   void update() {     // Update the position of a TextRainLetter.           // 1. Compute the pixel index corresponding to the (x,y) location of the TextRainLetter.     int flippedX = (int)(width-1-x); // because we have flipped the video left/right.     int index = width*(int)y + flippedX;     index = constrain (index, 0, width*height-1);          // establish a range around the threshold, within which motion is not required.     int thresholdTolerance = 5;     int thresholdLo = brightnessThreshold - thresholdTolerance;     int thresholdHi = brightnessThreshold + thresholdTolerance;          // 2. Fetch the color of the pixel there, and compute its brightness.     float pixelBrightness = brightness(video.pixels[index]);          // 3. If the TextRainLetter is in a bright area, move downwards.     //    If it's in a dark area, move up until we're in a light area.     if (pixelBrightness &gt; thresholdHi) {
      y += letterGravity; //move downward
 
    } else {
      while ((y &gt; initialLetterYPosition) &amp;&amp; (pixelBrightness &lt; thresholdLo)){         y -= letterGravity; // travel upwards intil it's bright again         index = width*(int)y + flippedX;         index = constrain (index, 0, width*height-1);         pixelBrightness = brightness(video.pixels[index]);       }     }          if ((y &gt;= height-1) || (y &lt; initialLetterYPosition)){       y = initialLetterYPosition;     }   }   //-----------------------------------   void draw() {     // Draw the letter. Use a simple black "drop shadow"     // to achieve improved contrast for the typography.           if( y &gt; height-20){
      y = random(initialLetterYPosition, initialLetterYPosition+30);
      x = random(1, width-1);
    }
 
    fill(random(256),random(256),random(256));
    text (""+c, x+1,y+1); 
    text (""+c, x-1,y+1); 
    text (""+c, x+1,y-1); 
    text (""+c, x-1,y-1); 
    fill(255,255,255);
    text (""+c, x,y);
  }
}

The Github Repo: https://github.com/chinesecold/TextRain

Bueno

28 Jan 2013

Screen Recording 2 from Andrew Bueno on Vimeo.

Here it is, my own little tribute to Text Rain. My method was to create a LetterDrop object containing coordinates, a velocity, a color, and a character. These were stored into an array list, and spawned about 500 pixels apart. Each line of rain is from an e e cummings poem – they can eventually catch up to each other if there is a collision. Each letter stops in place if it hits a pixel that is dark enough. I decided to highlight the brightness difference by recoloring pixels into one of two colors, depending on where it fell relative to the brightness threshold.

https://github.com/buenoIsHere/textRain

 

Screen Shot 2013-01-28 at 8.19.40 AM

import processing.video.*;

Capture cam;
float brightnessThresh;
String [] quotes;
ArrayList  letters;
int letterGap;

//Our raindrops!
public class LetterDrop
{ 
  public float velocity;
  public float lx;
  public float ly;
  public char letter;
  public color clr;

  public LetterDrop(float xcoord, float ycoord,  char l, color c)
  {
    lx = xcoord;
    ly = ycoord;
    velocity = .6 + random(0.0, 0.07);  
    letter = l;
    clr = c;
  }
}

void setup() {
  size(640, 480);

  quotes = new String [2];
  quotes[0] = "Humanity i love you because you are perpetually putting the";
  quotes[1] = "secret of life in your pants and forgetting it’s there";

  //SETTING UP WEBCAM CAPTURE
  String[] cameras = Capture.list();

  if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } 
  else {

    // The camera can be initialized directly using an 
    // element from the array returned by list():
    cam = new Capture(this, cameras[0]);
    cam.start();
  }

  noStroke();
  brightnessThresh = 120;

  // CREATE THE FONT
  textFont(createFont("Georgia", 22));
  textAlign(CENTER, BOTTOM);

  letters = new ArrayList();
  spawnLetters();
}

//Helper function for setup. Populates our array of letters.
//Note that I was inspired by ~Kamen and I give credit to him for
//the idea to have the next text lines "waiting" offscreen.
void spawnLetters()
{

   //GRAB A LINE FROM THE POEM
   for (int l = 0; l < quotes.length; l++) 
   {

    String phrase = quotes[l];
    int len = phrase.length();

    //FOR EACH NONSPACE CHAR IN PHRASE, MAKE A NEW LETTER OBJECT
    for (int i=0; i < len; i++) 
    {
      char ch = phrase.charAt(i);

      if (ch != ' ')
      {
        letters.add(new LetterDrop(10 * i + 30, -l * 500 + 20, ch, color(255, 255, 255)));
      }
    }
  } 
}

void draw() {

  update();

  image(cam, 0, 0);
  // The following does the same, and is faster when just drawing the image
  // without any additional resizing, transformations, or tint.
  //set(0, 0, cam);  

  for (int k = 0; k < letters.size(); k++)    {     LetterDrop drop = letters.get(k);     if (drop.ly >= 0) 
    {
      stroke(drop.clr);
      text(drop.letter, drop.lx, drop.ly);
    }
  }

}

void update()
{

  // DRAW CAPTURED WEBCAM IMAGE TO WINDOW  
  if (cam.available() == true) {
    cam.read();
  } 

  loadPixels();

  //RECOLOR IMAGE BASED ON PIXEL BRIGHTNESS
  for(int i = 0; i < cam.height; i++)
  {
    for(int j = 0; j < cam.width; j++)     {         int index = (i * cam.width) + j;                  if(brightness(cam.pixels[index]) > brightnessThresh)
        {
          cam.pixels[index] = 0x5F7485;  

        }
        else
        {
          cam.pixels[index] = 0x4B556C;
        }
    }
  }

  updatePixels();

  //CHECK EACH LETTER FOR COLLISION
  for(int k = 0; k < letters.size(); k ++)   {     LetterDrop drop = letters.get(k);          if(!collision(drop))     {       drop.ly += drop.velocity;     }     else if(collision(drop) && (drop.ly > 15))
    {
      int aboveIndex = floor(drop.lx) + floor(drop.ly-1) * width;
      if(brightness(pixels[aboveIndex]) < brightnessThresh)       {         drop.ly -= 5;       }     }          if(drop.ly > height)
    {
      drop.ly -= height + 500; 
    }   
  }
}

boolean collision(LetterDrop drop)
{

    if(drop.ly > 0)
    {
      int index = floor(drop.lx) + floor(drop.ly) * width;
      color pC = pixels[index];

      if(brightness(pC) < brightnessThresh)
      {
        return true;  
      }
      else
      {
        return false;  
      }
    }
    else
    {
      return false;  
    }
}

Elwin

28 Jan 2013

p1_text
Letters (OOP) will fall down from a random position and stops when it “collides” with an object.
The letters will also trace around an object’s edge through an algorithm that pushes the letter
upwards by checking the brightness of the vertical pixels above it.

Githun link: https://github.com/BlueSpiritbox/p1_textrain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import processing.video.*;
 
PFont f;
 
Capture video;
Letter[] letters;
 
 
int w = 640;
int h = 480;
 
String message = "For Part B of this assignment, you are asked to implement “Text Rain” in Processing. Reimplementing a project like this can be highly instructive, and test the limits of your attention to detail.";
 
 
color thresholdColor;
 
 
void setup() {
  size(w, h);  //window size
 
  f = createFont("Arial", 16, true);
  textFont(f);
 
  letters = new Letter[message.length()];
  for( int i = 0; i < message.length(); i++ ) {
    letters[i] = new Letter( random(0,width), 0, message.charAt(i) );
  }
 
  //Checks which cameras are available
  //  String[] cameras = Capture.list();
  //  if (cameras.length == 0) {
  //    println("There are no cameras available for capture.");
  //    exit();
  //  } else {
  //    println("Available cameras:");
  //    for (int i = 0; i < cameras.length; i++) {
  //      println(cameras[i]);
  //    }
  //  }  
 
  video = new Capture(this, w, h, 30); //initiates capture
  video.start();  //starts capturing video
 
  smooth();
}
 
void draw() {
  if ( video.available() ) {
    video.read();  //reads video input
  }
 
  image(video, 0, 0);  //displays video image
 
  for( int i = 0; i < message.length(); i++ ) {
    letters[i].update();
    letters[i].display();
  }
}
 
 
class Letter {
 
  char letter;
  float x, y;
  int velocity;
  int brightnessThreshold = 150; 
 
  long start;
  long delay;
  float life = 255;
  boolean dying = false;
 
  Letter( float _x, float _y, char _letter ) {  //creates Letter instance
    x = _x;
    y = _y;
    letter = _letter;
    velocity = round(random(1,3));
    life = round(random(100,256));
 
    start = millis();
    delay = round(random(500,10000));  //delay when letter falls at the beginning
  }
 
  void display() {  //displays Letter
    fill(100, life);
    text(letter, x, y);
  }
 
  void update() {  //update letter variables
 
    if( y >= height ) {  //reset if letter at the bottom
      x = random(0,width);  //give random x position
      y = 0;
 
    } else {
      if( brightness(video.pixels[round(x+y*width)]) < brightnessThreshold ) {  //if letter position is dark, stop letter
 
        //algorithm to push letter up to follow the "edge"
        if( y > 0 && y != 0) {  //only push up if y pos is not at 0
          float _y = y;  //temp y
 
          for( int i = int(y); i > 0; i-- ) {  //look at every single pixel above letter
            if( brightness(video.pixels[round(x+i*width)]) > brightnessThreshold ) {  //if it is bright
              _y = i;  //make that new y position
              break;  //stop looking for next pixel
            }
          }
          y = _y;  //update y position
        }       
      } else {  //move letter down
        if ( (millis() - start) > delay) {  //delay
          y += 1;
        }
      } 
    }
  }
 
}

Andy

28 Jan 2013

GitHub: https://github.com/andybiar/TextRain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
 
import processing.video.*;
 
Capture cam;
PFont f;
PImage bg;
final int WIDTH = 640;
final int DARK_THRESHOLD = 120;
Letter[] fallers;
Letter[] droppers;
int dropCount = 0;
final String[] WORDS = {"H","i","m","y","n","a","m","e",
"i","s","A","n","d","y","B","i",
"a","r","a","d","I","l","i","k",
"e","t","o","m","a","k","e","t",
"h","i","n","g","s","f","o","r",
"p","e","o","p","l","e","t","o",
"d","i","s","c","o","v","e","r",
"w","i","t","h","f","u","n","!"};
 
void setup() {
size(WIDTH, 480);
 
String[] cameras = Capture.list();
if (cameras.length == 0) println("No cameras");
else cam = new Capture(TextRain.this, cameras[0]);
cam.start();
 
f = createFont("Verdana", 18, true);
textFont(f);
fill(10);
 
fallers = new Letter[WIDTH/10];
droppers = new Letter[WIDTH/10];
}
 
int nextDrop() {
if (dropCount &gt;= WIDTH/10) return -1;
 
int rand = int(random(WIDTH/10));
while(fallers[rand] != null) {
rand = (rand + 1) % (WIDTH/10);
}
 
return rand;
}
 
void draw() {
// Get background from webcam
if (cam.available() == true) cam.read();
image(cam, 0, 0);
loadPixels();
 
// Drop a letter
if (dropCount &lt; 64 &amp;&amp; (millis() / 1000) &gt; dropCount) {
int nextDrop = nextDrop();
if (nextDrop != -1) {
fallers[nextDrop] = new Letter(WORDS[nextDrop%WORDS.length],
nextDrop*10, 0);
}
dropCount++;
}
 
// Draw letters
for (int i = 0; i &lt; fallers.length; i++) {
Letter n = fallers[i];
if (n != null) text(n.letter, n.x, n.y);
}
 
// Gravity
for (int i = 0; i &lt; fallers.length; i++) {
if (fallers[i] != null) {
Letter a = fallers[i];
if (a.y &gt;= height || a.y &lt; 0) continue;
color c = pixels[WIDTH * a.y + a.x];
float r, g, b;
r = red(c); g = green(c); b = blue(c);
int brightness = int(.2989*r + .5870*g + .1140*b);
if (brightness &gt; DARK_THRESHOLD) fallers[i].y += 1;
}
}
 
// Reset falling letters
for(int i = 0; i &lt; fallers.length; i++) {
if (fallers[i] != null &amp;&amp; fallers[i].y &gt;= height) {
fallers[i].y = 0;
}
}
}
 
public class Letter {
public String letter;
public int x;
public int y;
 
public Letter(String letter, int x, int y) {
this.letter = letter;
this.x = x;
this.y = y;
}
}

I think Text Rain is one of my weaker projects in the upkit intensive, but here it is. I used an object-oriented approach, creating a Letter class which stored it’s x and y position as well as the character to be drawn. Other than that, my implementation is likely pretty vanilla – I used the Capture object to get pixels from the webcam, loaded them into an array, and read points under the letters for darkness above a certain threshold. Definitely a fun starter project! Also, this is the first time in my life I have ever intentionally not shaved my stache, and I think it looks pretty good in this photo:

textrain

Ziyun

28 Jan 2013

 

TextRain_Processing from kaikai on Vimeo.

 

This was a very fun project for me. It’s the first processing sketch I wrote from scratch that does some relatively more complex things other then just drawing.

Meng

28 Jan 2013

 

Text Rain, or Chinese Snow.
Finished in Processing. Having captured the video from the camera, I used brightness to distinguish human/objects and background. The characters will fall down as long the brightness of the pixel postion is bigger than a value.

Screen Shot 2013-01-28 at 5.37.45 AM

Here’s code link: https://github.com/mengs/qiangJingJiu

 

PS:
There will be very English rainy, so I chose one of my favorite Chinese Poem by Li bai.

~Taeyoon

28 Jan 2013

 This was a really fun an learning experience. At first it was a bit frustrating to figure out where to start. However after insightful instructions from Golan, everything was pretty smooth. The code is pretty basic, and it can use much more work. Basicly it’s checking video feed for difference between threshold value and each pixels.If it is darker than the threshold value, the character will jump up (ascend) and if not, it will fall down. I wanted to make the characters rotate when they bounce up, and also to be able to move text from right to left and back, but didn’t get there yet.

Second video has an epic fail near the end.

Github https://github.com/tchoi8/IACD/tree/master/TextRain

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
PFont f;
String message = ":-@ :-> K@))(*#dfghj:-& k:-::::P:-*&**@!!!!~~®-P*#dfghj:-& k:-::::P:-*&*#dfghj:-& k:-::::P:-*&lL";
// An array of Letter objects
Letter[] letters;
//char[] letters; 
import processing.video.*;
 
int numPixels;
Capture video;
 
int m;
int n;
int threshold = 95; 
int index = 0;
 
void setup() {
  size(640, 480, P2D); // Change size to 320 x 240 if too slow at 640 x 480
  strokeWeight(5);
  // Uses the default video input, see the reference if this causes an error
  video = new Capture(this, width, height);
  video.start();  
  numPixels = video.width * video.height;
  noCursor();
  smooth();
 
  f = createFont("Arial", 25, true);
 
  textFont(f);
 
  letters = new Letter[message.length()];
 
  // Initialize Letters at the correct x location
  int x = 8;
  for (int i = 0; i < message.length(); i++) {
    letters[i] = new Letter(x, 200, message.charAt(i)); 
    x += 10;
    //x += textWidth(message.charAt(i));
  }
}
 
void draw() {
 
  if (video.available()) {
    video.read();
 
    //video.filter(GRAY);
    image(video, 0, 0);
    video.loadPixels();
    float pixelBrightness; // Declare variable to store a pixel's color
 
    for (int i = 0; i < letters.length; i++) {       letters[i].display();       if (mousePressed) {         letters[i].goup();         // letters[i].display();       }        else {         letters[i].shake();         letters[i].check();       }     }   //background(51, 0, 0);     updatePixels();         } } class Letter {   char letter;   // The object knows its original "home" location   float homex, homey;   // As well as its current location   float x, y;   Letter (float x_, float y_, char letter_) {     homex = x = x_;     homey = y = y_;     letter = letter_;   }   // Display the letter   void display() {     //fill(0, 102, 153, 204);     fill(223, 22, 22);     textAlign(LEFT);     text(letter, x, y);     //x+=10;   }   // Move the letter randomly   void shake() {     int m, n;     m = int(x);     n= int(y);     int testValue= get(m, n);     float testBrightness = brightness(testValue) ;     if (testBrightness > threshold) {
      x += random(-1, 1);
      y += random(5, 10);
 
      if (y > 400) {
        y = -height; 
        index = (index + 1) % letters.length;
      }
    } 
    else {
      x -= random(-1, 1);
      y -= random(20, 26);
    }
  }
 
  void check() { 
    float q= textWidth(message);
 
    if (y> 460) {
      y = 30;
      index = (index +1)%letters.length;
     fill(323, 22, 22);
      noStroke();
      // rect(14, 34, 45, 45);
    }
    else if (y<30) {
      y = 60;
      index = (index +1)%letters.length;
      fill(223, 22, 22);
      noStroke();
    //  rect(10, 30, 40, 40);
    }
 
    else {
     // fill(123, 22, 22);
      noStroke();
    //  rect(10, 30, 40, 40);
    }
  }    
 
  void goup() {
    x -= random(-2, 2);
    y -= random(10, 15);
  }
 
  // Return the letter home
  void home() {
    x = homex;
    y = homey;
  }
}