Story of one picture

by tsulej

I’ve got request to explain thought process behind following pictures (click to see two more):

Wool

Wool

I’ll try to recreate process from the scratch showing ready-to-use code progressively.

Inspiration

In addition to my own, a lot of ideas are taken from others’ works. It can be a specific palette, technique, composition, structure or texture. Then I recreate, rework, inspire myself by them to build my own dictionary and toolset. Still learning and searching.

This time it was picture by SuperColony:

217/365

217/365 by SuperColony

Fortunately guys published source code and it was beginning for me.

Analysis

Code is quite simple and easy to understand but the result isn’t obvious. Let’s simplify code a little and draw one step.

Step 1

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  stroke(233, 131, 45);
  makeme();
}

float yoff = 0.0;
void makeme() {
  background(23, 67, 88);
  strokeWeight(2*noise(20,200));
  beginShape();
  float xoff2 = 0;
  for (float x = 0; x <= width; x += 5) {
    float y = map(noise(xoff2, yoff*20), 0, 1, 20, 800);
    vertex(x, y);
    xoff2 += random(0.01,0.05);
  }
  yoff += 0.01;
  vertex(width, height);
  vertex(0, height);
  endShape(CLOSE);
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }

So, this is simply plot of noise function (line 16) using vertex(). If we draw 2000 steps we’ll see expected result. Originally authors chose 2 different colors (with alpha ranging from 5 to 20) and 3 positions of plot sets.

Step 2

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

float yoff = 0.0;
void makeme() {
  background(23, 67, 88);
  for(int iter=0;iter<2000;iter++) {
    stroke(233, 131, 45, random(5,20));
    strokeWeight(2*noise(20,200));
    beginShape();
    float xoff2 = 0;
    for (float x = 0; x <= width; x += 5) {
      float y = map(noise(xoff2, yoff*20.0), 0, 1, 20, 800);
      vertex(x, y);
      xoff2 += random(0.01,0.05);
    }
    yoff += 0.01;
    vertex(width, height);
    vertex(0, height);
    endShape(CLOSE);
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }

Our picture represents traverse of noise field with almost the same x range (lines 15 and 19) and different y each line. We can see that we have some kind of periodicity. It leads to the conclusion that noise function is periodic. And this characteristic makes the effect. If you change line 15th to have xoff2 initialized randomly you can bypass this issue.

float xoff2 = random(1.0);

Step 3

Ok. This is enough to start with my own version.

Step 1 – Circle

Let’s make a circle from above wool-a-like structure, reset colors (will be decided later). To make circle just switch to polar coordinates. Random some attributes (xoff, yoff, ang step) to avoid regularity. Unfortunately nasty thing happens at angle=0.0. It’s due to fact that lines doesn’t join at the same point, traversing noise field I don’t return to the starting position.

Step 4

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=0.0; ang<TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);
      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    endShape(CLOSE);
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 2 – Break the circle

To bypass this issue just make the gap wider. To make it change line 17. Better but sharp edges are not so pleasant.

Step 5

for (float ang=0.0; ang<0.8*TWO_PI; ang+=random(TWO_PI/200.0)) {

Step 3 – Adjustment

To fix it let’s random a little beginning and end of the arc. And provide variable shape_closed to make two options: one with closed circle and other with open.

Step 6b

Step 6a

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
}

boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);
      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 4 – Shift

This time let’s shift and make some distance of two halves. Make it optional (choosen randomly).

Step 7a

Step 7b

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
}

int shiftxy = 0;
boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
  background(20);
  translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 5 – Blocks

I like rectangular view or similar distortions so here I going to divide my picture to squares and inside each square exchange x and y axises. Of course let’s make it optional. Calculating abs() of x or y (line 46 and 47) stops exchanging x/y for negative values. One more issue: when doing blocks all points are moved by square size we need to adjust it (line 25)

Step 8a

Step 8b

 

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  background(20);
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters
  for(int iter=0;iter<3000;iter++) {
    stroke(240, random(5.0,20.0));
    strokeWeight(random(1.0));
    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
      float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 6 – Colors

Usually I have a huge problem with color choice. So I use Colour Lovers or Paletton (or random generated) to create/choose palletes. Here Infinite Skies from Colour Lovers is used. Let’s add one more option with 1% probablity: make filament orange/gold and put it in the middle of the ring. Some variety of main color is added also.

Step 9

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  background(37,49,63);
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters

  for(int iter=0;iter<3000;iter++) {

    boolean gold_line = false;
    if(random(1)>0.01) {
      stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
      strokeWeight(random(1.0));
    } else {
      gold_line = true;
      stroke(253,155,87,random(20,60));
      strokeWeight(random(1.0,1.5));
    }

    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

      float ll,rr;
      if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

      float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
      float x = r * cos(ang);
      float y = r * sin(ang);

      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Step 9 – Background

The last thing remains: background. It’s grid made of lines with variable intensity. Grid is rotated 45 degrees.

Step 10

void setup() {
  size(800,800);
  smooth(8);
  noFill();
  makeme();
}

void makeOptions() {
  shape_closed = random(1)<0.5 ? true : false;
  shiftxy = (int)random(3);
  do_absx = random(1)<0.5 ? true : false;
  do_absy = random(1)<0.5 ? true : false;
  do_blocky = random(1)<0.75 ? true : false;
}

// background grid
void makeBackground() {
  background(37,49,63);
  strokeWeight(1);
  pushMatrix();
  translate(400,400);
  rotate(HALF_PI/2.0);
  for(float x=-600;x<600;x+=random(5)) {
    stroke(90,104,115,random(5,15));
    line(-600,x,600,x);
    line(x,-600,x,600);
  }
  popMatrix();
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
  makeBackground();
  pushMatrix();
  if(do_blocky) translate(360,360); else translate(400,400);
  makeOptions(); // calculate some random parameters

  for(int iter=0;iter<3000;iter++) {

    // gold line
    boolean gold_line = false;
    if(random(1)>0.01) {
      stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
      strokeWeight(random(1.0));
    } else {
      gold_line = true;
      stroke(253,155,87,random(20,60));
      strokeWeight(random(1.0,1.5));
    }

    float xoff = random(1.0);
    beginShape();
    for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

      float ll,rr;
      if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

      float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
      float x = r * cos(ang);
      float y = r * sin(ang);

      // shift
      if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
        x += 20.0;
        y += 20.0;
      } else if(shiftxy > 0.0) {
        x -= 20.0;
        y -= 20.0;
      }

      // rectangles
      if(do_blocky) {
        float sx = (do_absx?abs(x):x) % 40.0;
        float sy = (do_absy?abs(y):y) % 40.0;
        int xx = (int)(x/40.0);
        int yy = (int)(y/40.0);
        x = xx*40.0 + (40.0-sx);
        y = yy*40.0 + (40.0-sy);
      }

      vertex(x, y);
      xoff += random(0.01,0.05);
    }
    yoff += random(0.01,0.05);
    if(shape_closed) endShape(CLOSE); else endShape();
  }
  popMatrix();
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }

Finished

That’s all. There is difference in colors of picture on my Tumblr and here. Reason is different algorithm of choosing color used (line 49). For Tumblr it’s exactly this:

if(random(1)<0.2)
  stroke(255,241,170,random(5,20));
else
  stroke(255,255,250,random(5,20));

If you want more explanation or clarification or another tutorial, let me know writing an e-mail: generateme.blog@gmail.com

Advertisements