Create generative art circle pattern

254 views Asked by At

I have been teaching myself JavaScript, and I like the idea of code-generated art. I came across this design that is made up of circles changing in size and overlapped by different colors.

How would something like this reference image: a grid of many circles of 3 colours: green, yellow and white. the circle's radii change based on proximity to what appears to be denser/darker perlin noise values

Can this be coded in JavaScript? I'm guessing for loops? But I am not sure how to set them to gradually change sizes throughout the line.

I'm imagining something like this?

let spaceX = 25,
  spaceY = 25,
  dial = 20;

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(220);

  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      ellipse(x, y, diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

2

There are 2 answers

2
Eric Fortis On

Here's the pseudo-code translated to JavaScript with Canvas

const spaceX = 25,
  spaceY = 25,
  dial = 20,
  width = 400,
  height = 400;

const canvas = document.createElement('canvas');
document.body.append(canvas);
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');


function circle(x, y, diam = 10) {
  ctx.beginPath();
  ctx.arc(x, y, diam / 2, 0, 2 * Math.PI);
  ctx.fill();
}

function draw() {
  for (let x = 0; x <= width; x += spaceX)
    for (let y = 0; y <= height; y += spaceY)
      circle(x, y);
}

draw()

0
Paul Wheeler On

The technique you are looking for to achieve the gradually changing sizes is the use of a two dimensional noise function such as Perlin Noise. P5.js provides this as a utility function: noise(). I've previously written a tutorial on using the noise() function over on OpenProcessing.

In this case it looks like you will want to draw the circles in multiple passes with different noise seeds. During each pass you draw yellow or blue circles with varying diameters based on the noise value for the current x, y coordinate (it is a little hard to tell if green circles are also being drawn, or if they green is just the result of mixing colors).

Unfortunately color mixing with pigments is different than color mixing with the RGB model. In RGB, mixing Yellow and Blue doesn't get you Green, it gets you Grey/White. You can enable simple mixing by using blendMode(ADD), but the results aren't great with the original color scheme. One of the interesting color mixes in the RGB model is Red + Green which does get you yellow. So that is what I've gone with in my example. Note: when using blendMode(ADD) it is important not to draw a light background! If you do that can throw off the color blending. Instead leave the canvas background blank and make the HTML element that contains it have your desired background.

let spaceX = 6,
  spaceY = 6;

function setup() {
  createCanvas(400, 400);
  noFill();
  strokeWeight(2);
  ellipseMode(CENTER);
  // comment this out for animation
  noLoop();
}

function draw() {
  clear();
  // Set the seed for the random noise
  noiseSeed(37);
  // Color: red
  stroke(255, 0, 0);
  // Draw circles with max stroke 2 and max diam 8
  pass(3, 8, 0);
  
  // Change the seed for the random noise
  noiseSeed(73);
  // Set the blendMode to add so that when the red and green circles overlapp their color is combined (instead of the second circle replacing the first).
  blendMode(ADD);
  stroke(0, 255, 0);
  pass(2, 10, 1);
}

// note: I've parameterized the stroke weight, diameter, and a position offset
// This makes it possible to tweak the different color circles to get different effects
function pass(weight, diam, offset) {
  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      // Note: the use of millis() as a third parameter makes this animated if you re-run this over time
      let n = noise(x / 200, y / 200, millis() / 10000);
      strokeWeight(n * weight);
      ellipse(x + offset, y + offset, n * diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

Another option for achieving the variation between the circles of the two different colors would be to use a slight offset in the Z dimension input to the noise function.

let spaceX = 6,
  spaceY = 6;

function setup() {
  createCanvas(400, 400);
  noFill();
  strokeWeight(2);
  ellipseMode(CENTER);
  // comment this out for animation
  noLoop();
}

function draw() {
  clear();
  // Set the seed for the random noise
  noiseSeed(37);
  // Color: red
  stroke(255, 0, 0);
  // Draw circles with max stroke 2 and max diam 8
  pass(3, 8, 0, 0);
  
  // Change the seed for the random noise
  // noiseSeed(73);
  // Set the blendMode to add so that when the red and green circles overlapp their color is combined (instead of the second circle replacing the first).
  blendMode(ADD);
  stroke(0, 255, 0);
  pass(2, 10, 1, 0.3);
}

// note: I've parameterized the stroke weight, diameter, and a position offset
// This makes it possible to tweak the different color circles to get different effects
function pass(weight, diam, offset, zoff) {
  // horizontal row
  for (let x = 0; x <= width; x += spaceX) {
    // vertical column
    for (let y = 0; y <= height; y += spaceY) {
      // Note: the use of millis() as a third parameter makes this animated if you re-run this over time
      let n = noise(x / 200, y / 200, zoff + millis() / 10000);
      strokeWeight(n * weight);
      ellipse(x + offset, y + offset, n * diam);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>