Reinterpreting Gerhard Richter's "4900 Colors" with p5.js and Processing

Gerhard Richter is a titan of modern art. In this post, I explore the logic behind his "Color Chart" series and attempt to recreate its essence using p5.js and Processing.

[Japanese version / 日本語版はこちら]

"4900 Colors" by Gerhard Richter


Richter’s process involves arranging color chips—randomly selected from a fixed palette—into a matrix. But does this simple randomization actually yield interesting results?


I wrote a basic script to test this, and honestly, the initial results were underwhelming.

I realized that the secret lies in the palette: "How does one select the perfect 25 colors?" Without a curated palette, a random grid often feels meaningless.


How can I make a beautiful color palette? It's so difficult to answer this question.


/** 
 * Color chips with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.5.0
 * created 2022.12.08
 */

const margin   = 20;
const chipNum  = 32;
const chipSiz  = 20;
const chipGap  = 0;
const colorNum = 25;
const canvasW  = chipSiz * chipNum + margin * 2;

function setup() {
  createCanvas(canvasW, canvasW);
  colorMode(HSB, 360, 100, 100, 100);

  // set random color palette
  const palette = new Array(colorNum);
  for (let i = 0; i < colorNum; i++) {
    palette[i] = color(random(360), random(40, 60), random(60, 80));
  }

  // draw
  background(0, 0, 90, 100);
  translate(margin, margin);
  stroke(0, 0, 90, 100);
  strokeWeight(chipGap);
  for (let chipY = 0; chipY < chipNum; chipY++) {
    for (let chipX = 0; chipX < chipNum; chipX++) {
      fill(palette[floor(random(colorNum))]);
      rect(
        chipX * chipSiz,
        chipY * chipSiz,
        chipSiz,
        chipSiz
      );
    }
  }
}

 

Introducing Structural Rules

When observing Richter's original work, the eye naturally seeks patterns, tracing similar hues and tonal structures even in a random sequence. This led me to a new idea: What if I inject a predefined structure into the randomness?

My approach uses a recursive-like hierarchy: a "large structure" defined by a pattern, which is then populated by "smaller structures" following that same pattern.


I made a color pattern with the randomly selected 25 colors, like the artwork of Gerhard Richter.

A large structure

I placed the smaller version of the same pattern here, but if it were placed normally, the meaning of the larger structure would be lost.

Place the small patterns on the large structure

So, I placed the semi-transparent smaller patterns.

Place the semi-transparent small patterns on the large structure

 

Implementation: Recursive Grids without Recursion

While the structure is conceptually recursive, I implemented it using a more intuitive nested loop.

To add depth, I applied a BLUR filter to the larger background tiles, allowing the sharper foreground chips to pop while maintaining the overall color structure.

Stuck the smaller color structure on the larger version of the same pattern.


/** 
 * Color patterns with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * p5.js 1.5.0
 * created 2022.12.12
 */

const margin   = 20;
const chipNum  = 5;
const chipSiz  = 24;
const chipGap  = 2;
const colorNum = 25;
const palette  = new Array(colorNum);
const pattern  = new Array(chipNum * chipNum);
const canvasW  = chipSiz * chipNum * chipNum + margin * 2;

function setup() {
  createCanvas(canvasW, canvasW);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();
  noLoop();

  // palette
  for (let i = 0; i < colorNum; i++) {
    let h = random(360);
    let s = random(40, 60);
    let b = random(60, 80);
    palette[i] = color(h, s, b, 100);
  }

  // make pattern
  for (let y = 0; y < chipNum; y++) {
    for (let x = 0; x < chipNum; x++) {
      pattern[x + y * chipNum] = palette[floor(random(colorNum))];
    }
  }

  // draw
  background(0, 0, 90, 100);
  translate(margin, margin);

  // large pattern
  noStroke();
  for (let y = 0; y < chipNum; y++) {
    for (let x = 0; x < chipNum; x++) {
      fill(pattern[x + y * chipNum]); // no transparent
      rect(
        x * chipSiz * chipNum,
        y * chipSiz * chipNum,
        chipSiz * chipNum,
        chipSiz * chipNum
      );
    }
  }
  filter(BLUR, 6);

  // small patterns
  strokeWeight(chipGap);
  stroke(0, 0, 90, 100);
  for (let largeY = 0; largeY < chipNum; largeY++) {
    for (let largeX = 0; largeX < chipNum; largeX++) {
      for (let y = 0; y < chipNum; y++) {
        for (let x = 0; x < chipNum; x++) {
          pattern[x + y * chipNum].setAlpha(50); // transparent
          fill(pattern[x + y * chipNum]);
          rect(
            (largeX * chipNum + x) * chipSiz,
            (largeY * chipNum + y) * chipSiz,
            chipSiz,
            chipSiz
          );
        }
      }
    }
  }
}


/**
 * Color patterns with 25 random colors.
 * ref. Gerhard Richter '4900 Farben'
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * Processing 3.5.3
 * created 2022.12.12
 */

public void setup() {
  size(760, 760);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();

  int margin  = 20;
  int chipNum = 6;
  int chipGap = 2;
  int chipSiz = floor((width - margin * 2) / chipNum / chipNum);

  // palette
  int   colorNum  = 25;
  color palette[] = new color[colorNum];
  for (int i = 0; i < colorNum; i++) {
    float h = random(360.0);
    float s = random(40.0, 60.0);
    float b = random(60.0, 80.0);
    palette[i] = color(h, s, b);
  }

  // make pattern
  color pattern[][] = new color[chipNum][chipNum];
  for (int y = 0; y < chipNum; y++) {
    for (int x = 0; x < chipNum; x++) {
      pattern[x][y] = palette[floor(random(colorNum))];
    }
  }

  // draw
  translate(margin, margin);
  background(0.0, 0.0, 90.0, 100.0);

  // large pattern
  noStroke();
  for (int y = 0; y < chipNum; y++) {
    for (int x = 0; x < chipNum; x++) {
      fill(pattern[x][y], 100.0); // no transparent
      rect(
           x * chipSiz * chipNum,
           y * chipSiz * chipNum,
           chipSiz * chipNum,
           chipSiz * chipNum
           );
    }
  }
  filter(BLUR, 6);

  // small patterns
  strokeWeight(chipGap);
  stroke(0.0, 0.0, 90.0, 100.0);
  for (int largeY = 0; largeY < chipNum; largeY++) {
    for (int largeX = 0; largeX < chipNum; largeX++) {
      for (int y = 0; y < chipNum; y++) {
        for (int x = 0; x < chipNum; x++) {
          fill(pattern[x][y], 50.0); // transparent
          rect(
               (largeX * chipNum + x) * chipSiz,
               (largeY * chipNum + y) * chipSiz,
               chipSiz,
               chipSiz
               );
        }
      }
    }
  }
}

 

Conclusion

Recreating famous masterpieces is more than an exercise; it's a source of inspiration. Deconstructing a master's logic through code often sparks unexpected creative breakthroughs.

Richter once said that randomness is what makes a painting 'objective.' By adding my own rules to his logic, am I creating more objectivity, or less?

Example work of small round color chips

 

Next Post Previous Post
No Comment
Add Comment
comment url