Question

I've been trying to figure this out for a while, and it's driving me mad. As most people know, if you draw 10 rectangles next to each other, ranging from white to black in equal steps of HSV brightness, they will not be perceived equal to the eye. Here's an example of that:

rectangles with equal spacing in HSB brightness

And the code in Processing:

void setup()
{
  size(600, 150);
  colorMode(HSB, 360, 100, 100);
  background(0, 0, 100);

  translate(50, 50);

  noStroke();
  for(int i = 0; i < 10; i++)
  {
    fill(0, 0, i * 10);
    rect(i * 50, 0, 50, 50);
  }
}

As you can see, the contrast between some of the darker tiles is perceived much bigger than with some of the white tiles.

Many people have pointed this out. In his book The Art of Color, Josef Albers describes (based on the Weber-Fechner law) that you should instead increase the brightness in exponential steps. It was later proved that Albers did some nasty miscalculations, and the idea of using a constant logarithmic increase in brightness proved true only within very limited bounds. There has been a lot of papers on this, but many of them are very hard for me to read, and most of them ties to physical aspects of the retina.

So my question is:

Given any color, how do I calculate the perceived equal steps in brightness from HSV brightness 0 to 100?

Even better, how do I calculate the perceived equal steps in brightness from any one color to any other color?

I'm generating files for print via code, and I need to do this in Processing. Any example in any language will do though.

Was it helpful?

Solution

For other people looking to do this in Processing, here's the answer. The Toxiclibs TColor class ships with LAB -> RGB conversion, so it wasn't hard. As you can see in the screenshot, the difference is clear.

import toxi.color.*;
import toxi.geom.*;

void setup()
{
  size(600, 250);
  colorMode(RGB, 1, 1, 1);
  background(1);
  noStroke();
  translate(50, 50);

  // RGB: 10 rects where perceived contrast is NOT equal in all squares
  for(float i = 0; i < 10; i++)
  {
    fill(i / 10.0, i / 10.0, i / 10.0);
    rect(i * 50, 0, 50, 50);
  }

  // LAB: 10 rects where perceived contrast IS equal in all squares
  translate(0, 50);

  for(int i = 0; i < 10; i++)
  {
    float[] rgb = TColor.labToRGB(i * 10, 0, 0);
    TColor col = TColor.newRandom().setRGB(rgb);
    fill(col.toARGB());
    rect(i * 50, 0, 50, 50);
  }
}

And here's the output:

enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top