Need help: Grid with increasing number of columns + incremental greyscale gradient

Hi

I’m trying to do something like this in p5:

I’m a total beginner, so I think this should be pretty basic stuff for most :slight_smile:
So far I’ve got this snippet of code:

function setup() {
    createCanvas(windowWidth, windowHeight);
}

function draw() {

    background(0);

    let amountX = windowWidth / 8;
    let amountY = windowHeight / 4;
    let spaceX = width / amountX;
    let spaceY = height / amountY;
  
    for (let y = 0; y < amountY; y++) { 
      for (let x = 0; x < amountX; x++) { 
        let posX = x*spaceX;
        let posY = y*spaceY;
        let w = spaceX;
        let h = spaceY;

        fill(y * 35, y * 35, y * 35);
  
        rect(posX, posY, w, h);
      }
    }
  }
  1. I’m having trouble figuring out how to divide amountX / Y with the width/height to always make the grid be contained within the canvas?

  2. How can I make the next column/row in the loop increment by x2?

  3. How can I set a greyscale gradient that only has a set amount of steps before starting over?

Hope that someone can help. Animation will have to wait until I figure out the basics of this :slight_smile:

Sorry for a bad explanation, I’m a bit tired, so I can’t go into the details; (I made it in processing not p5)
image

Code
void setup() {
  size(600,300);
  noStroke();
}

void draw() {
  background(0);
  float hh = 300;
  for(int i = 0; i < 5; i++) {
    float m = pow(2,i);
    for(int j = 0; j < m; j++) {
      displayGradient(width/m*j,hh*(m-1),width/m,hh,10,color(255,0,0),color(0,0,255));
    }
    hh*=0.5;
  }
}

void displayGradient(float x, float y, float w, float h, float n, color start, color end) {
  rect(x,y,w,h);
  for(int i = 0; i < n; i++) {
    float ww = w/n;
    fill(lerpColor(start,end,map(i,0,n,0,1)));
    rect(x+i*ww,y,ww,h);
  }
}

Essentially, I made a function that creates the singular gradient lerp cell.
After that, just create a function that positions them correctly.

1 Like

Hi abk,

To give you a bit more details, what you need to realize is that you basically have one pattern (the 8 columns gradient) that get repeated several times but at different positions and different sizes.

Based on this, and because we are lazy and want to save writing code, a good solution would be to write a function that draw one gradient at a specific position and size. All you would need to do later, is to call that function with the proper arguments and the function will handle all the math to display the gradient properly.

Now you can go from bottom up (meaning that drawing function up to your setup part) but maybe position and size are not the best arguments to give the function because the logic is different. Because of that the first thing I did was to write the main frame of the code, to help me figure out how I wanted everything to work. I got this:

let colNb = 8; // The number of columns to display per gradient
let rowNb = 4; // The number of rows of gradients to display

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  
  // Draw 4 rows of the gradient
  for (let i = 0; i < rowNb; i++) {
    drawRow(i);
  }
}

As you can see I want my main code to be really simple. All I want to do is decide the cumber of row of gradients I want to draw and call a function drawRow() that will figure out the size of the gradients for that row and the size they should have.

Simple enough, let’s code this:

// Draw one row of gradients
// lvl is the row nb (strating at 0 = fullscreen)
function drawRow(lvl) {
  let patternNb = pow(2, lvl); // The number of gradients to display in the row
  let w = width / patternNb;   // The width of one gradient in that row
  let h = height / patternNb;  // The height of the gradients in that row
  
  // Draw the actual gradients
  for (let i = 0; i < patternNb; i++) {
    drawGradient(w, h, i);
  }
}

As you can see, the piece of code does exactly what I explained above: finding the number of gradients to draw on that row and their size. In addition of course I want to actually draw them so for each gradient I want to draw I’m calling the drawGradient() function.

And as you can see, I’m not giving it the position and size like I was saying in the beginning but the size and the index of that gradient in the row (0 is the most left, then 1 is just right to this one and so on…)

// Draw one gradient given its size and index in the row
function drawGradient(w, h, idx) {
  let colWidth = w / colNb;   // The size of one bar
  let gradStep = 255 / colNb; // The step for the gradient
  
  for (let i = 0; i < colNb; i++) {
    fill(255 - i * gradStep);
    noStroke();
    rect(idx * w + i * colWidth, height - h, colWidth, h);
  }
}

And voilà!

You can change the number of columns per gradient as well as the number of row to display really easily with this.

EDIT:
To avoid gaps between rectangles, you can simply draw wider rectangles. The excess part will be drawn over by the next rectangle.
So instead of

rect(idx * w + i * colWidth, height - h, colWidth, h);

You have

rect(idx * w + i * colWidth, height - h, 1.5 * colWidth, h);
3 Likes

Wow, that was fast. Thanks for the great answers. I’ll play around with the code and see what I can do with it. Thanks again!

1 Like

Thanks for this thorough explanation! Works great.

Tried to make it responsive if the user changes the browser width/height with adding this at the end of the code:

function windowResized() {
  resizeCanvas(windowWidth,windowHeight);
}

But it just deletes the whole thing? Normally this works fine.

The code generating the pattern is in setup(). It will run only once.

If you want your line resize to work, simply move the pattern generation part in the draw function:

function draw() {
    background(0);
  
  // Draw 4 rows of the gradient
  for (let i = 0; i < rowNb; i++) {
    drawRow(i);
  }
}

Alternatively, you can also copy it to your resize function:

function windowResized() {
  resizeCanvas(windowWidth,windowHeight);
    background(0);
  
  // Draw 4 rows of the gradient
  for (let i = 0; i < rowNb; i++) {
    drawRow(i);
  }
}
1 Like

Ah of course. Thanks!

Hi @abk ,

The result you want to get is very well suited for what we call recursion in programming.

You can see that in the image, there’s a pattern (a gradient with rectangles fading from white to black) that is repeated at different scales.

In fact starting from the top to bottom, there’s a main gradient that is divided into two gradients (the same but different position and size) and then those two gradients are divided into two other gradients (which makes 4) then 8 then …

So it’s a process that calls itself : draw a gradient then draw two gradients at the bottom of it with half of it’s size. → repeat this operation n number of times.

You can refer to a previous post I made on the subject :

Here is my code in p5js with recursion :wink:

function gradient(x, y, w, h, nBands, depth) {
  // If we are at the end, stop
  if (depth === 0) return;
  
  const bandWidth = w / nBands;
  
  // Draw rectangles with gradient
  for (let i = 0; i < nBands; i++) {
    // Compute x position and color
    const rectX = x + i * bandWidth;
    const col = color(map(i, 0, nBands - 1, 255, 0));
    
    // Display the rectangle
    fill(col);
    noStroke();
    rect(rectX, y, bandWidth, h);
  }
  
  // Recursive call to next gradients
  const nextHeight = h * 0.7;
  gradient(x, y + h, w / 2, nextHeight, nBands, depth - 1);
  gradient(x + w / 2, y + h, w / 2, nextHeight, nBands, depth - 1);
}

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

function draw() {
  background(0);
  
  // Recursively draw gradients with a depth of 7
  gradient(0, 0, width, height / 3, 8, 7);
  
  noLoop();
}

It’s actually very simple to setup and you don’t need to worry about computing the next positions as it inherently do that for you by passing arguments to the next function call :yum:

Have fun!

2 Likes

Thanks for all the great answers! I figured out how to change the amount of gradients and flip both color and horisontal/vertical orientation, but I can’t seem to figure out how to animate each column and height of each rectangle and still have the whole pattern fill the canvas. Would I need to start over and create each column separately or would I be able to tweak this code somehow?

I’d like to achieve this kind of movement: https://www.instagram.com/p/CMxHVO_hmUa/?utm_source=ig_web_copy_link

1 Like

Hi @abk,

If you want to animate the columns and the gradients, you need to change the ratio of the width / height of the gradients with different values (for now they are equal). You could use random numbers to do that but if you want to animate it you’ll need something dependent on time.

But as you can see in the post you sent, the animation is very smooth and organic. It’s probably using perlin noise to drive the size of the gradients.

You can see an example on the Processing website : NoiseWave \ Examples \ Processing.org

I’ve made a similar animation that uses the same principle :

You can see the code here :

2 Likes

Awesome! Got it to work :sunglasses: Definitely need to dive deeper into noise based animations. Thanks!

1 Like