
In this example we are going to combine a simple brownian motion algorithm with image sampling. We will sue the brownian motion to sample pixel in an image and “draw” a shape of that color in the current position.
Brownian motion is a simple algorithm that moves a point randomly through space. We start with an X and Y position and then add randomly to that position in each frame:
float xpos = 0;
float ypose = 0;
void draw(){
xpos = xpos + random (-1,1);
ypose = ypos + random (-1,1);
}
This example uses the simplest form of displaying brownian motion. Without “redrawing” the background each frame, each ellipse from previous positions of the point is displayed. In this case we can’t reference those previous positions because we do not have them stored anywhere. We can’t track their positions, track them over time, etc.
float xpos;
float ypos;
void setup(){
size(500,500);
xpos = width/2;
ypos = height/2;
background(0);
}
void draw(){
noStroke();
fill(255,100);
ellipse(xpos,ypos,5,5);
float ranx = random(-5,5);
float rany = random(-5,5);
if(xpos + ranx > width || xpos + ranx < 0){
ranx = -ranx;
}
if(ypos + rany > height || ypos + rany < 0){
rany = -rany;
}
xpos = xpos + ranx;
ypos = ypos + rany;
}
In this version we are storing the positions in a list and redrawing all of ellipses each frame. We also have a list storing the scale of each point. This allows us to slowly decrease the scale over time leaving a diminishing trail. We are also drawing a cyan line through the points. By keeping track of the positions of each point it opens up so many possibilities for how you display and use the history of the point positions
ArrayList <PVector> position = new ArrayList <PVector>();
FloatList pscale = new FloatList();
void setup(){
size(500,500);
float xpos = width/2;
float ypos = height/2;
position.add(new PVector(xpos,ypos,0));
pscale.append(20);
}
void draw(){
background(0);
for(int i = 0; i < position.size()-1; i++){
PVector p1 = position.get(i);
PVector p2 = position.get(i+1);
float sc = pscale.get(i);
stroke(0,255,255);
line(p1.x,p1.y,p2.x,p2.y);
noStroke(); fill(255,100);
ellipse(p1.x,p1.y,sc,sc);
pscale.set(i,sc*0.995);
}
PVector p = position.get(position.size()-1);
float ranx = random(-5,5);
float rany = random(-5,5);
if(p.x + ranx > width || p.x + ranx < 0){
ranx = -ranx;
}
if(p.y+ rany > height || p.y + rany < 0){
rany = -rany;
}
float newx = p.x + ranx;
float newy= p.y + rany;
position.add(new PVector(newx,newy,0));
pscale.append(20);
}
The example on the left uses the simple brownian example from above. The main addition is sampling the image at each position and then drawing an ellipse with the color received form the sample. Using one brownian motion point it will take a fairly long time to sample a significate amount of pixels form the image.
With this sketch it will take a long time to “draw” the image. One way to speed this up is to add multiple brownian particles. W could do this by copying the code and variables that control the movement of the particle. This is 1. really inefficient and 2. doesn’t really allow us to easily control how many particles we can add.
We can create a class or an object for the particle. In some ways you can think of this as an object that contains its own processing sketch with these parts:
Global Variables
Setup
Draw
In this case we would build a class for the particles and we will call that object “Searcher.”
Global Variables and Setup
class Searcher {
float xpos;
float ypos;
Searcher (float x, float y) {
xpos = x;
ypos = y;
}
Display, which draws the particle
void display() {
noStroke();
color c = image_holder.get(int(xpos),
int(ypos));
fill(c,100);
ellipse(xpos,ypos,5,5);
}
Update which calculates the next position of the particle
void update() {
float ranx = random(-5,5);
float rany = random(-5,5);
if(xpos + ranx > width || xpos + ranx < 0){
ranx = -ranx;
}
if(ypos + rany > height || ypos + rany < 0){
rany = -rany;
}
xpos = xpos + ranx;
ypos = ypos + rany;
}
}
//declare PImage object
PImage img;
//declare PGraphics object to hold image
PGraphics image_holder;
float xpos;
float ypos;
void setup(){
size(800,450);
img = loadImage("Kubrick.jpg");
image_holder = createGraphics(800, 450);
image_holder.beginDraw();
image_holder.image(img, 0, 0);
image_holder.endDraw();
xpos = width/2;
ypos = height/2;
background(0);
}
void draw(){
noStroke();
color c = image_holder.get(int(xpos), int(ypos));
fill(c,100);
ellipse(xpos,ypos,5,5);
float ranx = random(-5,5);
float rany = random(-5,5);
if(xpos + ranx > width || xpos + ranx < 0){
ranx = -ranx;
}
if(ypos + rany > height || ypos + rany < 0){
rany = -rany;
}
xpos = xpos + ranx;
ypos = ypos + rany;
}ArrayList <Searcher> image_searchers = new ArrayList <Searcher>();
By using a class or object, we can create multiple Brownian particles. You an compare the code on the left below (single particle) and the code on the right using a class to define the particle.
ArrayList <Searcher> image_searchers = new ArrayList <Searcher>();
//declare PImage object
PImage img;
//declare PGraphics object to hold image
PGraphics image_holder;
void setup(){
size(800,450);
img = loadImage("Kubrick.jpg");
image_holder = createGraphics(800, 450);
image_holder.beginDraw();
image_holder.image(img, 0, 0);
image_holder.endDraw();
for(int i = 0; i < 10; i++){
float xpos = width/2 + random(-200,200);
float ypos = height/2 + random(-200,200);
image_searchers.add(new Searcher(xpos,ypos));
}
background(0);
}
void draw(){
for(int i = 0; i < image_searchers.size(); i++){
Searcher is = image_searchers.get(i);
is.display();
is.update();
}
}
class Searcher {
float xpos;
float ypos;
Searcher (float x, float y) {
xpos = x;
ypos = y;
}
void display() {
noStroke();
color c = image_holder.get(int(xpos),
int(ypos));
fill(c,100);
ellipse(xpos,ypos,5,5);
}
void update() {
float ranx = random(-5,5);
float rany = random(-5,5);
if(xpos + ranx > width || xpos + ranx < 0){
ranx = -ranx;
}
if(ypos + rany > height || ypos + rany < 0){
rany = -rany;
}
xpos = xpos + ranx;
ypos = ypos + rany;
}
}
The code below uses the example above to keep track of the particle positions in a list along with a class for the particles.
ArrayList <Searcher> image_searchers = new ArrayList <Searcher>();
//declare PImage object
PImage img;
//declare PGraphics object to hold image
PGraphics image_holder;
void setup(){
size(800,450);
img = loadImage("Kubrick.jpg");
image_holder = createGraphics(800, 450);
image_holder.beginDraw();
image_holder.image(img, 0, 0);
image_holder.endDraw();
for(int i = 0; i < 5; i++){
float xpos = width/2 + random(-200,200);
float ypos = height/2 + random(-200,200);
image_searchers.add(new Searcher(xpos,ypos));
}
}
void draw(){
background(0);
for(int i = 0; i < image_searchers.size(); i++){
Searcher is = image_searchers.get(i);
is.display();
is.update();
}
}
class Searcher {
ArrayList <PVector> position = new ArrayList <PVector>();
ArrayList <PVector> pcolor = new ArrayList <PVector>();
FloatList pscale = new FloatList();
Searcher (float x, float y) {
float xpos = x;
float ypos = y;
position.add(new PVector(xpos,ypos,0));
color c = image_holder.get(int(xpos), int(ypos));
pcolor.add(new PVector(red(c),green(c),blue(c)));
pscale.append(15);
}
void display() {
noStroke();
for(int i = 0; i < position.size(); i++){
PVector p = position.get(i);
PVector c = pcolor.get(i);
float sc = pscale.get(i);
fill(c.x,c.y,c.z);
ellipse(p.x,p.y,sc,sc);
if(sc > 3){
pscale.set(i,sc*0.999);
}
}
}
void update() {
PVector p = position.get(position.size()-1);
float ranx = random(-8,8);
float rany = random(-8,8);
if(p.x + ranx > width || p.x + ranx < 0){
ranx = -ranx;
}
if(p.y+ rany > height || p.y + rany < 0){
rany = -rany;
}
float newx = p.x + ranx;
float newy= p.y + rany;
position.add(new PVector(newx,newy,0));
color c = image_holder.get(int(newx), int(newy));
pcolor.add(new PVector(red(c),green(c),blue(c)));
pscale.append(15);
}
}