Creative coding color study : Mastering Organic Gradients: Using Normal Distribution

The drawing of impression of dawn.

I’ve always been captivated by the ethereal beauty of a sunrise. Its myriad of colors weave together into complex gradients, both delicate and unpredictable. How can we replicate such organic transitions through creative coding?

 

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

 

The Art of Layering: Understanding Blending Modes

Layering translucent colors in p5.js and Processing is a fundamental way to create new, blended hues.


I'll use the method of layering semi-transparent colors to make complex gradients.

Now, the blendMode() function gives various effect on the mixed colors.

The blendMode(SCREEN) makes it brighter as you layer the colors. It behaves like light.

Layering semi-transparent colors with blendMode(SCREEN)

The blendMode(SUBTRACT) makes it darker as you layer the colors. It behaves like paints

Layerking semi-transparent colors with blendMode(SUBTRACT)

blendMode(BLEND) (default blend-mode) is ideal for layering the semi-transparent colors. And I like the difference of mixed colors in the order of layering.

 

Seeking the Perfect Transition: From Linear to Organic

I'll explore how to create smoother color transitions by meticulously controlling transparency.

Level 1: Linear Gradients (The Basics)

It's the example that changes transparency in linear.

Changing transparency in linear
Changing transparency of three colors in linear

Linear transitions often feel a bit jarring, especially where the colors meet in the center.

 

Level 2: Sine Curves (Smoother, but...)

It's the example that changes transparency in the sine curve.

The graph of the sine curve
Changing transparency in the sine curve
Changing transparency of three colors in the sine curve

The sine curve offers a smoother transition than the linear approach, though the edges can still feel somewhat abrupt.

 

Level 3: Normal Distribution (The Gaussian Approach)

Could using a normal distribution make color transitions look more natural? Let's test this by applying a bell curve to our transparency values.

The graph of bell curve
Changing transparency in the bell curve
Changing transparency of three colors in the bell curve

By using a normal distribution (Gaussian curve), we can achieve a much more natural, organic fade. A significant advantage of this method is the ability to fine-tune the gradient using mean and variance.

The graph of three different bell curve
Changing transparency in the three bell curve

 

Applying Gaussian Gradients to Art

It's an example of artwork changing colors in the bell curve.

Gradient of blue

The colors are semi-transparent so the background can be seen through. You can make a composition by the background painting.

I placed the different color rectangles to be vertically arranged this time.

The rectangles to be vertically arranged

The p5.js Example Code


/**
 * Gradation of Blue.
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.5.0
 * created 2022.12.02
 */

function setup() {
  createCanvas(640, 1000);
  colorMode(HSB, 360, 100, 100, 100);
  noSmooth();
  noLoop();
  blendMode(BLEND);

  const yL = 0.7;

  // background
  fill(220, 90, 30, 100);
  rect(0, 0, width, height * yL);
  fill(220, 60, 90, 100);
  rect(0, height * yL, width, height);
  stroke(0, 0, 100, 100);
  line(0, height * yL, width, height * yL);

  // gradation
  gradateY(200, yL, 0.02, 80, createVector(55, 10, 100));
  gradateY(200, 0.1, 0.03, 80, createVector(260, 90, 5));
}

/**
 * gradateY : draw alpha gradation with the probability distribution function on Y-axis.
 * _divNum  : divide number.
 * _mean    : mean value of the probability distribution.
 * _vari    : variance value of the probability distribution.
 * _alpBase : alpha value on the top of the probability distribution curve.
 * _color   : x = hue, y = saturation, z = brightness
 */
function gradateY(_divNum, _mean, _vari, _alpBase, _color) {
  const divW = height / _divNum;
  const topV = 1 / sqrt(TWO_PI * _vari);
  noStroke();
  for (let i = 0; i < _divNum; i++) {
    let divRate = map(i, 0, _divNum, 0, 1);
    let density = exp(-pow(divRate - _mean, 2) / (2 * _vari)) / sqrt(TWO_PI * _vari);
    fill(_color.x, _color.y, _color.z, _alpBase * density / topV);
    rect(0, i * divW, width, divW);
  }
}


I utilized PVector to keep the gradateY() function's parameter list clean and manageable.

 

The Processing Example Code


/**
 * Gradation of Blue.
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * Processing 3.5.3
 * created 2022.12.02
 */

public void setup() {
  size(640, 1000);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  noSmooth();
  noLoop();
  blendMode(BLEND);

  float yL = 0.7;

  // background
  fill(220.0, 90.0, 30.0, 100.0);
  rect(0.0, 0.0, width, height * yL);
  fill(220.0, 60.0, 90.0, 100.0);
  rect(0.0, height * yL, width, height);
  stroke(0, 0, 100, 100);
  line(0, height * yL, width, height * yL);

  // gradation
  gradateY(200, yL, 0.02, 80.0, new PVector(55.0, 10.0, 100.0));
  gradateY(200, 0.1, 0.03, 80.0, new PVector(260.0, 90.0, 5.0));
}

/**
 * gradateY : draw alpha gradation with the probability distribution function on Y-axis.
 * _divNum  : divide number.
 * _mean    : mean value of the probability distribution.
 * _vari    : variance value of the probability distribution.
 * _alpBase : alpha value on the top of the probability distribution curve.
 * _color   : x = hue, y = saturation, z = brightness
 */
public void gradateY(int _divNum, float _mean, float _vari, float _alpBase, PVector _color) {
  float divW = height * 1.0 / _divNum;
  float topV = 1.0 / sqrt(TWO_PI * _vari);
  noStroke();
  for (int i = 0; i < _divNum; i++) {
    float divRate = map(i, 0, _divNum, 0.0, 1.0);
    float density = exp(-pow(divRate - _mean, 2) / (2.0 * _vari)) / sqrt(TWO_PI * _vari);
    fill(_color.x, _color.y, _color.z, _alpBase * density / topV);
    rect(0.0, i * divW, width, divW);
  }
}

 

Final Notes

The title drawing is my impression of the dawn I saw on my morning walk.

While this example focuses on the Y-axis, you can easily adapt the logic to the X-axis, specific areas, or even rotations. I hope you enjoy experimenting with these techniques!

 

Next Post Previous Post
No Comment
Add Comment
comment url