Generative Art

This sketch generates unique abstract artwork by arranging randomly rotated shapes (circles, squares, and triangles) in a grid. Each shape's color, size, and rotation are controlled by Perlin noise, creating coherent organic patterns. Click the mouse or resize the window to generate new designs with different random seeds.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make all shapes the same type — Replace the random shape selection with a single shape type to see how Perlin noise alone (without random shape variation) creates patterns
  2. Flip the noise scale for size — Invert the relationship between noise and size so high-noise areas get tiny shapes and low-noise areas get huge shapes—reversing the visual structure
  3. Create a hotter color palette — Change the hue range from cool purples (180–300) to warm reds and oranges (0–60) for a completely different visual mood
  4. Double the grid density — Increase both cols and rows to 40 so you get four times as many shapes, creating a finer, more detailed mosaic—requires more processing
  5. Swap noise coordinates for swirled patterns — Switch x and y in the noise call to create a transposed Perlin noise pattern—notice how vertical and horizontal patterns reverse
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an infinite gallery of abstract artworks by combining four powerful p5.js techniques: Perlin noise for organic variation, random seeding for reproducible randomness, transformation matrices (translate and rotate) for positioning shapes, and HSB color mode for intuitive hue control. Every time you click, a brand new design appears instantly, yet each design is mathematically reproducible—the same seed always produces the same artwork.

The code is organized around a single generate() function that is called whenever you click, resize the window, or first load the sketch. By studying it, you will learn how Perlin noise creates natural-looking variety instead of pure chaos, how randomSeed() lets you freeze randomness for reproducibility, and how push()/pop() safely apply transformations to individual shapes without affecting others. This is the foundation of all generative art in p5.js.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen canvas, switches to HSB color mode (which is easier for generating harmonious color schemes), assigns a random seed value, and calls generate() to draw the first artwork.
  2. generate() reseeds the random and noise generators so they produce the same sequence every time that seed is used. It then fills the canvas with a light background color.
  3. The function creates a 20×20 grid by looping through columns and rows. For each cell, it calculates the center position (px, py) where a shape will be drawn.
  4. For each grid cell, Perlin noise is sampled at coordinates (x * 0.1, y * 0.1). This single noise value controls three properties: rotation angle (via TWO_PI multiplication), hue (via map), and size (via multiplication). Because nearby grid cells have nearby noise coordinates, nearby shapes have similar colors and sizes—this creates visual coherence instead of pure randomness.
  5. Using push() and pop(), each shape is translated to its grid position, rotated based on the noise value, and filled with a color mapped from the same noise value. A random shape type (circle, square, or triangle) is chosen, sized proportionally to the cell, and drawn at the origin (0,0) because translate has already positioned it.
  6. When you click the mouse, mousePressed() generates a new seed and calls generate() again, redrawing the entire canvas with a fresh random pattern. The same sequence happens if you resize the window, keeping the artwork responsive.

🎓 Concepts You'll Learn

Perlin noiseRandom seedingHSB color modeTransformation matrices (translate, rotate)Grid generationProcedural artPush/pop context management

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It is the ideal place to initialize the canvas, set drawing modes, and call any setup functions you need.

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 100, 100, 100);
  seed = random(10000);
  generate();
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the artwork responsive to screen size
colorMode(HSB, 360, 100, 100, 100);
Switches from RGB to HSB (Hue, Saturation, Brightness) color mode with ranges 0-360 for hue, 0-100 for saturation, 0-100 for brightness, and 0-100 for alpha. HSB is more intuitive for generating harmonious color schemes
seed = random(10000);
Generates a random seed number between 0 and 10000 and stores it. This seed will be used to freeze the random number generator so the same seed always produces the same artwork
generate();
Calls the generate() function to draw the first generative artwork using the seed

generate()

generate() is the heart of this sketch. It demonstrates Perlin noise for smooth variation, random seeding for reproducibility, and transformation matrices (push/translate/rotate/pop) for positioning and orienting shapes. Every time you click or resize, this function redoes all the math to create a brand new artwork.

🔬 This if-else block chooses which shape to draw. What happens if you delete the second condition (the else if for squares) so only circles and triangles appear? Try removing lines 28–30 entirely—does the pattern look more or less interesting?

      if (shape === 0) {
        ellipse(0, 0, size, size);
      } else if (shape === 1) {
        rectMode(CENTER);
        rect(0, 0, size, size);
      } else {
        triangle(-size/2, size/2, size/2, size/2, 0, -size/2);
      }

🔬 These nested loops create the 20×20 grid. What if you change rows to rows * 2 so you get twice as many horizontal rows? Try changing the condition from y < rows to y < rows * 2—will you get more detail or a stretched mess?

  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
function generate() {
  randomSeed(seed);
  noiseSeed(seed);
  background(220, 10, 95);
  
  let cols = 20;
  let rows = 20;
  let cellW = width / cols;
  let cellH = height / rows;
  
  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
      let px = x * cellW + cellW / 2;
      let py = y * cellH + cellH / 2;
      let n = noise(x * 0.1, y * 0.1);
      
      push();
      translate(px, py);
      rotate(n * TWO_PI);
      
      let hue = map(n, 0, 1, 180, 300);
      fill(hue, 70, 90, 80);
      noStroke();
      
      let shape = floor(random(3));
      let size = cellW * 0.8 * n;
      
      if (shape === 0) {
        ellipse(0, 0, size, size);
      } else if (shape === 1) {
        rectMode(CENTER);
        rect(0, 0, size, size);
      } else {
        triangle(-size/2, size/2, size/2, size/2, 0, -size/2);
      }
      pop();
    }
  }
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

initialization Seed Reset Block randomSeed(seed); noiseSeed(seed);

Locks both the random number generator and Perlin noise generator to produce the same sequence every time this seed is used, enabling reproducible artwork

for-loop Nested Grid Loop for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) {

Iterates through every grid cell from top to bottom, left to right, so each cell gets exactly one shape

calculation Noise Sampling let n = noise(x * 0.1, y * 0.1);

Samples 2D Perlin noise at coordinates scaled by 0.1—nearby cells have similar noise values, creating visual coherence across the grid

conditional Transformation & Drawing push(); translate(px, py); rotate(n * TWO_PI);

Uses push() to save the current transformation state, then translates to the grid cell center and rotates based on the noise value—all shapes are then drawn at origin (0,0) but appear in the correct cell with correct rotation

conditional Shape Selection if (shape === 0) { ellipse(0, 0, size, size); } else if (shape === 1) { rectMode(CENTER); rect(0, 0, size, size); } else { triangle(-size/2, size/2, size/2, size/2, 0, -size/2); }

Draws one of three shapes (circle, square, or triangle) at the origin, with dimensions controlled by the size variable. The shape type is randomly chosen but frozen by randomSeed(), so the same grid always has the same shapes

randomSeed(seed);
Locks the random() function to always produce the same sequence for this seed—critical for reproducibility
noiseSeed(seed);
Locks the noise() function to always produce the same Perlin noise pattern for this seed—ensures the same seed produces identical artwork every time
background(220, 10, 95);
Fills the entire canvas with a light off-white/cream color (hue 220 in HSB is blue-ish, but very low saturation 10 makes it nearly white, and brightness 95 keeps it light)
let cols = 20;
Sets the number of columns in the grid to 20—this controls horizontal density of shapes
let rows = 20;
Sets the number of rows in the grid to 20—this controls vertical density of shapes
let cellW = width / cols;
Divides the canvas width by the number of columns to get the pixel width of each grid cell
let cellH = height / rows;
Divides the canvas height by the number of rows to get the pixel height of each grid cell
let px = x * cellW + cellW / 2;
Calculates the center x-position of the current grid cell by multiplying column index by cell width and adding half a cell width
let py = y * cellH + cellH / 2;
Calculates the center y-position of the current grid cell by multiplying row index by cell height and adding half a cell height
let n = noise(x * 0.1, y * 0.1);
Samples Perlin noise at coordinates (x*0.1, y*0.1)—multiplying by 0.1 zooms out the noise so nearby cells have similar values, creating smooth variation instead of chaotic randomness
push();
Saves the current transformation state (position, rotation, fill color, etc.) so changes only affect the current shape
translate(px, py);
Moves the origin point to the center of the current grid cell—all subsequent drawing operations happen relative to this new origin
rotate(n * TWO_PI);
Rotates the coordinate system by an angle equal to the noise value multiplied by 2π (a full circle). Since noise ranges 0–1, this rotates each shape by a random amount from 0 to 360 degrees
let hue = map(n, 0, 1, 180, 300);
Maps the noise value (0 to 1) to a hue (180 to 300). In HSB, 180 is cyan and 300 is magenta, so this produces a cool purple-cyan color palette
fill(hue, 70, 90, 80);
Sets the fill color using HSB mode: hue varies by noise (180–300), saturation is fixed at 70 (moderately vivid), brightness is 90 (bright), and alpha is 80 (mostly opaque)
noStroke();
Removes the outline from shapes so they appear solid without borders
let shape = floor(random(3));
Randomly chooses a shape type: 0 for circle, 1 for square, 2 for triangle. floor(random(3)) produces 0, 1, or 2 with equal probability
let size = cellW * 0.8 * n;
Calculates shape size as a fraction of the cell width: up to 80% of the cell width, scaled by the noise value n (0–1), so shapes in low-noise areas are tiny and shapes in high-noise areas are large
pop();
Restores the transformation state saved by push()—cancels translate() and rotate() for the next iteration so each shape doesn't affect the next

mousePressed()

mousePressed() is a p5.js event handler that runs every time you click the mouse. Here it is used to trigger a new random seed and regenerate the artwork, making the sketch interactive.

function mousePressed() {
  seed = random(10000);
  generate();
}
Line-by-line explanation (2 lines)
seed = random(10000);
Generates a new random seed between 0 and 10000 and stores it globally
generate();
Calls generate() to redraw the entire canvas using the new seed, creating a completely different artwork

keyPressed()

keyPressed() is a p5.js event handler that runs whenever any key is pressed. Here it allows you to save your favorite generative artworks by pressing 's'.

function keyPressed() {
  if (key === 's') {
    saveCanvas('generative-art', 'png');
  }
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Save Trigger if (key === 's') {

Checks if the 's' key was pressed and triggers a canvas save if true

if (key === 's') {
Checks if the key variable (the most recently pressed key) equals 's'—only the 's' key triggers the save
saveCanvas('generative-art', 'png');
Saves the current canvas as a PNG file named 'generative-art.png' to your downloads folder

windowResized()

windowResized() is a p5.js event handler that fires whenever the browser window is resized. Here it keeps the artwork responsive by resizing the canvas and redrawing at the new dimensions.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  generate();
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the new window dimensions when the user resizes their browser window
generate();
Regenerates and redraws the artwork to fill the newly resized canvas using the same seed, preserving the current design

📦 Key Variables

seed number

Stores a random number that freezes the random and noise generators, allowing the same artwork to be reproduced every time that seed is used

let seed = random(10000);
cols number

The number of columns in the grid of shapes—controls horizontal density

let cols = 20;
rows number

The number of rows in the grid of shapes—controls vertical density

let rows = 20;
cellW number

The pixel width of each grid cell, calculated by dividing canvas width by number of columns

let cellW = width / cols;
cellH number

The pixel height of each grid cell, calculated by dividing canvas height by number of rows

let cellH = height / rows;
px number

The x-coordinate of the center of the current grid cell being drawn

let px = x * cellW + cellW / 2;
py number

The y-coordinate of the center of the current grid cell being drawn

let py = y * cellH + cellH / 2;
n number

The Perlin noise value sampled at the current grid position (ranges 0–1)—controls color, rotation, and size for the current shape

let n = noise(x * 0.1, y * 0.1);
hue number

The hue value (in HSB mode) for the current shape, mapped from the noise value so nearby shapes have similar colors

let hue = map(n, 0, 1, 180, 300);
shape number

A random integer (0, 1, or 2) that determines which shape (circle, square, or triangle) to draw for the current grid cell

let shape = floor(random(3));
size number

The diameter of the current shape, scaled by the noise value so shapes vary in size across the grid

let size = cellW * 0.8 * n;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

FEATURE keyPressed()

Saving is not obvious—users won't know to press 's' without instructions

💡 Add a visual UI element (text or button) that displays 'Press S to save' in the corner of the canvas

PERFORMANCE generate()

With large grids (e.g., 50×50 cells), the nested loop performs 2500 transformations and drawing operations every time you click, causing noticeable lag

💡 Consider caching shapes or using a graphics buffer for very large grids, or allow users to lower cols/rows for faster interaction

STYLE variable naming

Single-letter variable names like 'n', 'px', 'py' are hard to search and understand at a glance

💡 Rename 'n' to 'noiseValue', 'px' to 'cellCenterX', 'py' to 'cellCenterY' for clarity

BUG mousePressed()

The function does not return false, so every mouse click may trigger default browser behavior or conflict with other handlers if they exist

💡 Add 'return false;' at the end of mousePressed() to prevent default behavior: 'function mousePressed() { seed = random(10000); generate(); return false; }'

🔄 Code Flow

Code flow showing setup, generate, mousepressed, keypressed, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> generate[generate] generate --> seedreset[Seed Reset Block] seedreset --> gridloop[Grid Loop] gridloop --> noisSampling[Noise Sampling] noisSampling --> transformblock[Transformation & Drawing] transformblock --> shapeselection[Shape Selection] shapeselection --> gridloop generate --> mousepressed[mousePressed] generate --> keypressed[keyPressed] generate --> windowresized[windowResized] mousepressed --> seedreset keypressed --> saveconditional[Save Trigger] saveconditional --> keypressed click setup href "#fn-setup" click generate href "#fn-generate" click mousepressed href "#fn-mousepressed" click keypressed href "#fn-keypressed" click windowresized href "#fn-windowresized" click seedreset href "#sub-seed-reset" click gridloop href "#sub-grid-loop" click noisSampling href "#sub-noise-sampling" click transformblock href "#sub-transform-block" click shapeselection href "#sub-shape-selection" click saveconditional href "#sub-save-conditional"

❓ Frequently Asked Questions

What kind of visuals does the Generative Art sketch produce?

The sketch creates dynamic, colorful geometric shapes arranged in a grid, utilizing hues from the HSB color model and varying sizes based on Perlin noise.

How can users interact with the Generative Art sketch?

Users can click the mouse to generate a new design with a different seed, or press the 's' key to save the current artwork as a PNG image.

What creative coding concepts are showcased in this generative art sketch?

The sketch demonstrates the use of randomization, noise functions for organic variation, and transformation functions like rotation and translation to create visually appealing patterns.

Preview

Generative Art - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Generative Art - Code flow showing setup, generate, mousepressed, keypressed, windowresized
Code Flow Diagram