0

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.

Ronze
  • 1,544
  • 2
  • 18
  • 33
  • 1
    Perception of color cannot be understood on the HSB color space since its a device dependent color space. You need to familiarize yourself with CIE XYZ and CIE L*a*b* color spaces. CIE L*a*b* helps you with perception. You will have to look at conversion from CIE L*a*b* to HSB and calculate your brightness accordingly. And for printing you have to look then at HSB to CYMK calculations. – Nico May 11 '13 at 18:34
  • Yes, I figured that was the case. I have a color library that easily allows me to use CIE LAB. So if I do the same in LAB, changing L with a constant value, I'll get the correct output? – Ronze May 11 '13 at 20:22
  • As far as I understand it, you would convert L*a*b* into XYZ which is a linear color space. Then you would change the value of Y and convert that to L* by using the formula: L* = 116 * f(Y/Y0) - 16, where f(Y/Y0) could be (Y/Y0)^1/3 or 1/3*(29/6)^2*(Y/Y0)+4/29. The formula for f you choose would depend on whether Y/Y0 is greater than or less than (6/29)^3, if its greater, you choose the former formula, otherwise the latter. Y0 is usually taken at 100 or thereabouts. You can find values for Y0 on the internet depending on your needs. I have personally always used 100 for it. Note: Y0=Y nought. – Nico May 11 '13 at 21:13
  • Thanks so much. I posted the answer underneath. – Ronze May 12 '13 at 02:36

1 Answers1

1

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

Ronze
  • 1,544
  • 2
  • 18
  • 33
  • +1. I guess my way was pretty long winded. I was assuming you had to work with HSB colors at the end (which you still can with RSB -> HSB conversion). Anyway, glad you found the solution. – Nico May 12 '13 at 02:45
  • 1
    I actually think that the library itself is doing exactly what you described: Converting into XYZ space, and then to RGB in some way. The TColor class has a toHSV() function, so it would be almost the same in HSB colorMode. – Ronze May 12 '13 at 03:02
  • I get the impression that the [fromCIEXYZ](http://docs.oracle.com/javase/7/docs/api/java/awt/color/ColorSpace.html#fromCIEXYZ%28float[]%29) method of the ColorSpace class will do the same thing without the need for a third-party dependency. – VGR May 12 '13 at 14:14