Coding art

Languages for programming art

A programming language is, after all, just another language. And a language can be spoken in many different ways, with a variety of accents or inflections. [...] But if a language isn’t capable of poetry, it has clearly lost its relevance on the human side of the equation. Matt Pearson

Processing

Processing is a flexible software sketchbook and a language for learning how to code.

Since 2001, Processing has promoted software literacy within the visual arts and visual literacy within technology.

In a survey, students in a college-level introductory computing course taught with Processing said they would be twice as likely to take another computer science class as the students in a class with a more traditional curriculum.

Processing was started by Ben Fry and Casey Reas in 2001. At that time, Fry and Reas were both graduate students at the MIT Media Lab within John Maeda's Aesthetics and Computation research group. Processing grew directly out of Maeda's Design By Numbers project, developed at the Media Lab and released in 1999. The full Processing story is told by Fry and Reas in this Medium article: A Modern Prometheus.

Notably, the Processing approach has also been applied to electronics through the Wiring and Arduino projects. Both projects (proudly) started at the Interaction Design Institute of Ivrea in Italy.

Coding example: Game of Life

The Game of Life is a cellular automaton developed by British mathematician John Conway in the late 1960s. Its purpose is to show how complex life-like behaviors can emerge from simple rules of interaction among many bodies.

The game takes place on an infinite grid of square boxes (cells) called the world according to the following rules:

  1. each cell has 8 neighbors, which are the cells adjacent to it, including those in a diagonal direction

  2. each cell can be in two states: alive or dead

  3. initially, the states of cells are randomly chosen

  4. the states of all cells at a given instant are used to calculate the state of the cells at the next instant. All cells in the world are updated simultaneously as they transition from one instant to the next

  5. The state transition rules depend solely on the number of living neighbors:

    1. a living cell with 2 or 3 living neighbors survives, otherwise it dies (due to isolation or overcrowding)

    2. a dead cell with exactly 3 living neighbors is reborn

Cell[][] cells;
int cellSize = 10;
int numX, numY;

void setup() {
  size(600, 600);
  // fullScreen(); // goes full screen
  frameRate(8); // reduce frame rate to 8 fps
  numX = floor(width/cellSize);
  numY = floor(height/cellSize);
  startup();
}

void draw() {
  background(0);

  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
      cells[x][y].compNextState();
    }
  }

  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
      cells[x][y].drawMe();
    }
  }
}

void startup() {
  // create the cell grid
  cells = new Cell[numX][numY];
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
      cells[x][y] = new Cell(x, y);
    }
  }
  // assign cells' neighbors
  for (int x = 0; x < numX; x++) {
    for (int y = 0; y < numY; y++) {
      int above = y-1;
      int below = y+1;
      int left = x-1;
      int right = x+1;
      if (above < 0) above = numY-1;
      if (below == numY) below = 0;
      if (left < 0) left = numX-1;
      if (right == numX) right = 0;

      cells[x][y].addNeighbour(cells[left][above]);
      cells[x][y].addNeighbour(cells[left][y]);
      cells[x][y].addNeighbour(cells[left][below]);
      cells[x][y].addNeighbour(cells[x][below]);
      cells[x][y].addNeighbour(cells[right][below]);
      cells[x][y].addNeighbour(cells[right][y]);
      cells[x][y].addNeighbour(cells[right][above]);
      cells[x][y].addNeighbour(cells[x][above]);
    }
  }
}

void mousePressed() {
  startup();
}

class Cell {
  float x, y;
  boolean state;
  boolean nextState;
  Cell[] neighbours;

  // create a random world
  Cell(float _x, float _y) {
    x = _x * cellSize;
    y = _y * cellSize;
    if (random(1) > 0.5) {
      nextState = true;
    } else {
      nextState = false;
    }
    state = nextState;
    neighbours = new Cell[0];
  }

  void addNeighbour(Cell cell) {
    neighbours = (Cell[]) append(neighbours, cell);
  }

  void compNextState() {
    // count live neighbors
    int liveCount = 0;
    for (int i=0; i < neighbours.length; i++) {
      if (neighbours[i].state == true) {
        liveCount++;
      }
    }
    // a living cell with 2 or 3 living neighbors survives, otherwise it dies
    if (state == true) {
      if ((liveCount == 2) || (liveCount == 3)) {
        nextState = true;
      } else {
        nextState = false;
      }
      // a dead cell with exactly 3 living neighbors is reborn
    } else {
      if (liveCount == 3) {
        nextState = true;
      } else {
        nextState = false;
      }
    }
  }

  void drawMe() {
    state = nextState;
    noStroke();
    if (state == true) {
      fill(255); // white is alive
    } else {
      fill(0); // black is dead
    }
    rect(x, y, cellSize, cellSize);
  }
}
Play - Tweak Game of Life

Create your personal version of GoL starting from the original code

Learn Processing

p5.ps

p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else.

ps.js is an interpretation of Processing for today’s web. Using the metaphor of a sketch, p5.js has a full set of drawing functionality. However, you’re not limited to your drawing canvas. You can think of your whole browser page as your sketch, including HTML5 objects for text, input, video, webcam, and sound.

Coding example: Game of Life

let cells; // use let to declare functions
let cellSize = 10;
let numX, numY;

function setup() {
  createCanvas(600, 600); // use createCanvas instead of size
  frameRate(8); // reduce frame rate to 8 fps
  numX = floor(width / cellSize);
  numY = floor(height / cellSize);
  startup();
}

function draw() {
  background(0);

  for (let x = 0; x < numX; x++) {
    for (let y = 0; y < numY; y++) {
      cells[x][y].compNextState();
    }
  }

  for (let x = 0; x < numX; x++) {
    for (let y = 0; y < numY; y++) {
      cells[x][y].drawMe();
    }
  }
}

function startup() {
  // create the cell grid
  cells = new Array(numX); // matrices are arrays of arrays
  for (let x = 0; x < numX; x++) {
    cells[x] = new Array(numY); 
    for (let y = 0; y < numY; y++) {
      cells[x][y] = new Cell(x, y);
    }
  }
  // assign cells' neighbors
  for (let x = 0; x < numX; x++) {
    for (let y = 0; y < numY; y++) {
      let above = y - 1;
      let below = y + 1;
      let left = x - 1;
      let right = x + 1;
      if (above < 0) above = numY - 1;
      if (below == numY) below = 0;
      if (left < 0) left = numX - 1;
      if (right == numX) right = 0;

      cells[x][y].addNeighbour(cells[left][above]);
      cells[x][y].addNeighbour(cells[left][y]);
      cells[x][y].addNeighbour(cells[left][below]);
      cells[x][y].addNeighbour(cells[x][below]);
      cells[x][y].addNeighbour(cells[right][below]);
      cells[x][y].addNeighbour(cells[right][y]);
      cells[x][y].addNeighbour(cells[right][above]);
      cells[x][y].addNeighbour(cells[x][above]);
    }
  }
}

function mousePressed() {
  startup();
}

class Cell {
  constructor(_x, _y) { //use constructor() to define the class constructor
    this.x = _x * cellSize; // use this to self-referencing the object
    this.y = _y * cellSize;
    this.nextState = random(1) > 0.5;
    this.state = this.nextState;
    this.neighbours = []; // empty array
  }

  addNeighbour(cell) {
    this.neighbours.push(cell); // add a new cell with push()
  }

  compNextState() {
    let liveCount = 0;
    for (let i = 0; i < this.neighbours.length; i++) {
      if (this.neighbours[i].state) {
        liveCount++;
      }
    }

    if (this.state) {
      this.nextState = liveCount == 2 || liveCount == 3;
    } else {
      this.nextState = liveCount == 3;
    }
  }

  drawMe() {
    this.state = this.nextState;
    noStroke();
    if (this.state) {
      fill(255); // white is alive
    } else {
      fill(0); // black is dead
    }
    rect(this.x, this.y, cellSize, cellSize);
  }
}

Learn p5.js

Play - Tweak Game of Life

If your Processing tweaks were interesting, implement them in p5.js. Then fork the GoL code on OpenProcessing and post your own version in p5.js.

R

R is a free software environment for statistical computing and graphics. R was not born to create art. However, it has been recently (ab)used in this sense, proving that even statistics is capable of poetry!

Typically, an artwork in R is generated as a plot - often a scatterplot - using ggplot2 library, which is a system for declaratively creating graphics: you provide the data, tell ggplot2 how to map variables to aesthetics, what graphical primitives to use, and it takes care of the details.

Coding example

library(ggplot2) # import library for plots
library(magrittr) # import library for pipes

seq(from=-10, to=10, by = 0.05) %>% # create a sequence from -10 to 10 with step 0.05
  expand.grid(x=., y=.) %>% # expand the sequence into a grid
  ggplot(aes(x = (x + sin(y)^2), y = (y + sin(x)))) + # alter the grid using some math
  geom_point(alpha=.1, shape=20, size=1, color="black") + # plot the grid as a scatterplot
  theme_void() # set the void theme for the plot

Learn R (for generative art)

Play - Make art with R
  1. pick one function in aRtsy

  2. investigate how it works

  3. read the corresponding R code on GitHub

  4. generate some art with it

  5. save and post it on Discord

Play - Draw a portrait with R
  1. read the Travelling Salesman Portrait project by Fronkonstin

  2. read the corresponding R code

  3. generate a portrait of someone you love

  4. save and post it on Discord (and send it to your lover!)

Last updated