oop « Coding, Sounds and Colors | A blog about algorithmic experiments in music and visual art. Sort of.

Grids: an object oriented approach

posted by on 2016.04.07, under Processing
07:

I have been working recently on a small audio-visual installation based essentially on grid manipulations, inspired by the art of the amazing Casey Reas.
Grids are some of the first interesting repetitive structures one learns to build. For instance, a one parameter function which displays a regular grid of rectangles maximising the surface of the screen used might look like this

void grid(int n){
float stepx = width/n;
float stepy = height/n;

for (int x = 0; x < width; x+=stepx){
   for (int y = 0; y < height; y+=stepy){
    rectMode(CENTER);
    noStroke();
    fill(255);
    rect(x + stepx/2.0, y + stepy/2.0, 20, 20);
    }
  }
}

It will produce a grid of equally spaced white rectangles (so, better set the background to black before in your code). This is all fun and cosy for a couple of milliseconds, but if you are like me, you will look immediately for possibilities of explorations, and honestly the bit of code above doesn’t offer much. Let’s be precise: it *can* offer a lot of ways of tweaking, but they become cumbersome very very quickly. Also, I like to write codes which are conceptually structural and modular, and which allow to separate more clearly the “data holding” part from its representation. I find that this helps a lot the sense of surprise I get from playing with my newly created tool/toy. For instance, all that white is boring: how about we assign a different color to each rectangle? Not randomly though, which would be very cheap. Let’s use an image as a palette, and have each rectangle carry the color of the image pixel at the center of the rectangle. This can be attained by introducing the following line

fill(img.pixels[x + y * width]);

where img is the PImage variable holding your image (which I assume you have resized to the screen width and height, unless you have a fetish for ArrayOutOfBound messages :) ). Nice. But I guess you still can’t see my point, of course. Okay, let’s do the same but with a video. Now the image is changing at each frame, so the function grid() must be called in the animation loop, i.e. inside draw(). Here you can see my point: by proceding in this way, you are doing a certain amount of redundant computations. Indeed, the only thing that you would like to modify in this case is how each node of the grid is represented, and *not* the grid structure itself. So, it helps then to think of a grid as a data holder for the positions of its nodes, which, once we require some extra properties, are directly determined by a single integer n, the number of nodes per row and column. What we draw on the screen is then a representation of this bunch of information. Anytime you have something that behaves like a collection of data, object oriented programming (OOP) is not far from sight. Building objects (or rather classes) is kind of an art: extracting the relevant properties we want in our objects so that further manipulation becomes reactive and enjoyable is not an easy task. This to say that the are some basic rules in object oriented programming, but for artistic reason we can (and should) ignore many standard design patterns, and look at each case separately. Since I like thinking in terms of objects when programming, I feel the following warning is due: do *not* overuse OOP! There are fantastic pieces of algorithmic/generative art which do not use objects at all. The point is that you might run the risk to decompose the problem in its atomic parts, which has a certain intellectual appeal, no doubt, but then get lost when it comes to exploration and tweaking. So, case by case, see what works best, and, most importantly, what puts you in that spot where you can still get surprised. This said, let’s move on.
So, we can model a grid with the following Grid class

class Grid {
  int n;
  float[] posx;
  float[] posy;
  float offsetx;
  float offsety;
  Shape[] shapes = {};
 
  Grid(int _n){
    n = _n;
    int stepx = width/n;
    int stepy = height/n;
    offsetx = stepx/2.0;
    offsety = stepy/2.0;
    posx = new float[n];
    posy = new float[n];
   
    for (int i = 0; i < n; i++){
      posx[i] = i * stepx;
      posy[i] = i * stepy;
    }

    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
        shapes = (Shape[]) append(shapes,new Shape(posx[i], posy[j]));
        }
      }

    }
   
    Grid(int _n, float _offsetx, float _offsety){
    n = _n;
    int stepx = width/n;
    int stepy = height/n;
    offsetx = _offsetx;
    offsety = _offsety;
    posx = new float[n];
    posy = new float[n];
   
    for (int i = 0; i < n; i++){
      posx[i] = i * stepx;
      posy[i] = i * stepy;
    }

    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
        shapes = (Shape[]) append(shapes,new Shape(posx[i], posy[j]));
        }
      }

    }
   
    Grid(int _n, float len, float _offsetx, float _offsety){
    n = _n;
    int stepx = int(len/n);
    int stepy = int(len/n);
    offsetx = _offsetx;
    offsety = stepy;
    posx = new float[n];
    posy = new float[n];
   
    for (int i = 0; i < n; i++){
      posx[i] = i * stepx;
      posy[i] = i * stepy;
    }

    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
        shapes = (Shape[]) append(shapes,new Shape(posx[i], posy[j]));
        }
      }

    }  
   
    void display(){
      pushMatrix();
      translate(offsetx, offsety);

      for (int i = 0; i < shapes.length; i++){
        shapes[i].display(20);
      }

      popMatrix();
    }
   
    void display(PImage _img){
      _img.loadPixels();
      pushMatrix();
      translate(offsetx, offsety);

      for (int i = 0; i < shapes.length; i++){
        shapes[i].setColor(_img.pixels[int(shapes[i].x) + int(shapes[i].y) * _img.width]);
        shapes[i].display(20);
      }

      popMatrix();
    }
   
  }

So, you can see that I have heavily used that you can overload constructors and methods, so to give some default behaviours when we don’t want to bother passing a lot of parameters. An instance of Grid will also carry an array of Shape objects: this is the “representational” informations of our grid. Here’s how the Shape class looks like:

class Shape {
  float x, y;
  color c;
 
  Shape(float _x, float _y){
    x = _x;
    y = _y;
    c = color(255, 255, 255);
  }
 
  Shape(float _x, float _y, color _c){
    x = _x;
    y = _y;
    c = _c;
  }
 
  void display(float w){
    rectMode(CENTER);
    noStroke();
    fill(c, 255);
    rect(x, y, w , w);
    rectMode(CORNER);
  }
 
  void setColor(color _c){
    c = _c;
  }
 
}

If you don’t specify any color, the rectangle will be white.
So, now the previous function grid() is subsumed in the following lines of code

Grid grid = new Grid(20);
grid.display(img);

Apart from the elegance of it, that as I mentioned before should not be the only criterion of judgement, the code now is amenable to vast explorations. For instance, by modifying the display() method of the Shape class we can completely change the appearance of our grid, even so much that it doesn’t look like a grid anymore! Moreover, we don’t have all those redundant computations to be made.
One thing that comes to mind when you have a class is that you can produce many objects from that class with slightly different properties. In this case I have decided to use this expedient in order to explore “fractalization”, which is another (made up?) word for “apply recursion carefully”. Add the following function to your code

void fractalize(int n){
  if (n > 1){
  grid = new Grid(n, x, y);
  grid.display();
  x+= grid.offsetx;
  y+= grid.offsety;
  fractalize(n - 1);
  }
}

To make the most of it, you might want to lower the alpha of the filling for the rectangles. Actually, as I mentioned before, we can start exploring the code and the questions it naturally suggests, like: “why restrict ourselves to rectangles?” Or “why even filling them?” “Can I add some stochastic noise here and there to make everything not so hearthless and rigid?” “What is the meaning of life?”
When you start raising these questions, a vast playground (or/and a deep existential pit) opens up.
Believe it or not, after very few adjustments to the Shape class here’s what I got

1

1

which I find having an interesting balance between structure and randomness. A nice surprise! :)

pagetop