Golan-TextRain

by Golan Levin @ 12:14 am 12 January 2011

Here is a Processing version of the classic TextRain (1999) by Camille Utterback and Romy Achituv:

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
// Text Rain (Processing cover version)
// Original by Camille Utterback and Romy Achituv (1999):
// http://camilleutterback.com/projects/text-rain/
// Implemented in Processing 1.2.1 by Golan Levin
 
import processing.video.*;
Capture video;
 
float brightnessThreshold = 50;
TextRainLetter poemLetters[];
int nLetters;
 
//-----------------------------------
void setup() {
  size(320,240); 
  video = new Capture(this, width, height);
 
  String poemString = "A poem about bodies";
  nLetters = poemString.length();
  poemLetters = new TextRainLetter[nLetters];
  for (int i=0; i<nLetters; i++) {
    char c = poemString.charAt(i);
    float x = width * (float)(i+1)/(nLetters+1);
    float y = 10;
    poemLetters[i] = new TextRainLetter(c,x,y);
  }
}
 
//-----------------------------------
void draw() {
  if (video.available()) {
    video.read();
    video.loadPixels();
    image (video, 0, 0, width, height); 
    for (int i=0; i<nLetters; i++) {
      poemLetters[i].update();
      poemLetters[i].draw();
    }
  }
}
 
 
 
//===================================================================
class TextRainLetter {
 
  float gravity = 1.5;
  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 particle.
    int index = width*(int)y + (int)x;
    index = constrain (index, 0, width*height-1);
 
    // 2. Fetch the color of the pixel there, and compute its brightness.
    float pixelBrightness = brightness(video.pixels[index]);
 
    // 3. If we're in a bright area, move downwards.
    //    If we're in a dark area, move up until we're in a light area.
    if (pixelBrightness > brightnessThreshold) {
      y += gravity; //move downward
 
    } else {
      while ((y > 0) && (pixelBrightness <= brightnessThreshold)){
        y -= gravity; // travel up intil it's bright 
        index = width*(int)y + (int)x;
        index = constrain (index, 0, width*height-1);
        pixelBrightness = brightness(video.pixels[index]);
      }
    }
 
    if ((y >= height-1) || (y < 0)){
      y = 0;
    }
  }
 
  //-----------------------------------
  void draw() {
    // Draw the letter. Use a simple black "drop shadow"
    // to achieve improved contrast for the typography. 
    fill(0);
    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);
    text (""+c, x,y);
  }
}

Here is a version for OpenFrameworks v.062, based on the MovieCapture example. First, the header (.h) file:

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
#ifndef _TEST_APP
#define _TEST_APP
 
#include "ofMain.h"
 
class TextRainLetter {
public:
	char  c;
	float x; 
	float y;
 
	TextRainLetter (char cc, float xx, float yy);
	float getPixelBrightness (unsigned char *rgbPixels, int baseIndex);
	void update (unsigned char *rgbPixels, int width, int height, float threshold);
	void draw();
};
 
 
class testApp : public ofBaseApp {
 
	public:
 
		void setup();
		void update();
		void draw();
 
		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void windowResized(int w, int h);
 
		ofVideoGrabber vidGrabber;
		int camWidth;
		int camHeight;
 
		float brightnessThreshold;
		vector <TextRainLetter>  poemLetters; 
		int nLetters;
};
 
#endif

And the C++ (.cpp) file:

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
#include "testApp.h"
 
//--------------------------------------------------------------
void testApp::setup(){	 
 
	camWidth  = 320;	
	camHeight = 240;
	vidGrabber.setVerbose(true);
	vidGrabber.initGrabber(camWidth,camHeight);
 
	brightnessThreshold = 50;
 
	string poemString = "A poem about bodies";
	nLetters = poemString.length();
	for (int i=0; i<nLetters; i++) {
		char  c = poemString[i];
		float x = camWidth * (float)(i+1)/(nLetters+1);
		float y = 10;
		poemLetters.push_back( TextRainLetter(c,x,y) );
	}
}
 
//--------------------------------------------------------------
void testApp::update(){
	vidGrabber.grabFrame();
	if (vidGrabber.isFrameNew()){
		unsigned char *videoPixels = vidGrabber.getPixels();
		for (int i=0; i<nLetters; i++) {
			poemLetters[i].update(videoPixels, camWidth, camHeight, brightnessThreshold);
		}
	}
}
 
//--------------------------------------------------------------
void testApp::draw(){
	ofBackground(100,100,100);
	ofSetHexColor(0xffffff);
	vidGrabber.draw(0,0);
 
	for (int i=0; i<nLetters; i++) {
		poemLetters[i].draw();
	}
}
 
//--------------------------------------------------------------
void testApp::keyPressed  (int key){}
void testApp::keyReleased(int key){}
void testApp::mouseMoved(int x, int y ){}
void testApp::mouseDragged(int x, int y, int button){}
void testApp::mousePressed(int x, int y, int button){}
void testApp::mouseReleased(int x, int y, int button){}
void testApp::windowResized(int w, int h){}
 
 
 
//=====================================================================
TextRainLetter::TextRainLetter (char cc, float xx, float yy) {
    // Constructor function
    c = cc;
    x = xx;
    y = yy;
}
 
//-----------------------------------
void TextRainLetter::draw(){
	char drawStr[1];
	sprintf(drawStr, "%c", c);
 
	ofSetColor(0,0,0);
	ofDrawBitmapString(drawStr, x-1,y-1);
	ofDrawBitmapString(drawStr, x+1,y-1);
	ofDrawBitmapString(drawStr, x-1,y+1);
	ofDrawBitmapString(drawStr, x+1,y+1);
 
	ofSetColor(255,255,255);
	ofDrawBitmapString(drawStr, x,y);
}
 
//-----------------------------------
void TextRainLetter::update (unsigned char *rgbPixels, int width, int height, float threshold) {
    int index = 3* (width*(int)y + (int)x);
    index = (int) ofClamp (index, 0, 3*width*height-1);
    float pixelBrightness = getPixelBrightness(rgbPixels, index);
 
    float gravity = 2.0;
    if (pixelBrightness > threshold) {
	y += gravity; 
 
    } else {
	while ((y > 0) && (pixelBrightness <= threshold)){
		y -= gravity; 
		index = 3* (width*(int)y + (int)x);
		index = (int) ofClamp (index, 0, 3*width*height-1);
		pixelBrightness = getPixelBrightness(rgbPixels, index);
	}
    }
 
    if ((y >= height-1) || (y < 10)){
	y = 10;
    }
}
 
//-----------------------------------
float TextRainLetter::getPixelBrightness (unsigned char *rgbPixels, int baseIndex){
	// small utility function
	int r = rgbPixels[baseIndex + 0];
	int g = rgbPixels[baseIndex + 1];
	int b = rgbPixels[baseIndex + 2];
	float pixelBrightness = (r+g+b)/3.0;
	return pixelBrightness;
}

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.
(c) 2023 Interactive Art & Computational Design / Spring 2011 | powered by WordPress with Barecity