Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

How can I create a colour of my chosen hue with a given fixed lightness level?

For example, how can I create a reddish colour, col, so that ColorConvert[col, "GrayScale"] will return precisely GrayLevel[0.6]?

Does Mathematica have something built-in to assist in this, or do I need to read up on how colours are converted between various representations? Note that the b value in Hue[h, s, b] does not correspond to the GrayLevel value. I need to have precise control over what certain colours I use will look like when converted to greyscale.

share
3  
"...do I need to read up on how colours are converted between various representations?" - this one, I think. Converting RGB and HSB colors to grayscale isn't too straightforward. –  J. M. Jul 26 '12 at 16:54

4 Answers 4

up vote 16 down vote accepted

Conversion Formula

ColorConvert uses the following formula for "Grayscale" conversion:

$ \mathrm{Grayscale} = 0.299 R + 0.587 G + 0.114 B$

where $R$, $G$, and $B$ are normalized.

Interactive Example

The following manipulate example will help you finding (and confirming) the conversion based on a fixed $\mathrm{Grayscale}$ and $R$ values.

f[g_, gs_, r_] := (gs - .299 r - .587 g)/.114;

Manipulate[
 Column[{
   Show[
    Graphics[{Polygon[{{0, 0}, {1, 0}, {1, 1}, {0, 1}},
       VertexColors -> {RGBColor[r, 0, 0], RGBColor[r, 1, 0], 
         RGBColor[r, 1, 1], RGBColor[r, 0, 1]}]},
     PlotRange -> {{0, 1}, {0, 1}}, PlotRangeClipping -> True, 
     AspectRatio -> 1,
     ImageSize -> 400,
     Frame -> True, FrameLabel -> {"Green", "Blue"},
     LabelStyle -> {FontFamily -> "Arial", FontSize -> 14}],
    Plot[f[g, gs, r], {g, 0, 1}],
    Graphics[{
      Locator[
       Dynamic[pt, (With[{b = f[#[[1]], gs, r]}, 
           If[0 <= b <= 1, pt = {#1[[1]], b}, {.5, .5}]]) &]]
      }]
    ],
   "",
   Row[{"ColorConvert[RGBColor[", Prepend[pt, r], 
     ",\"Grayscale\"]==\n", 
     ColorConvert[RGBColor[Prepend[pt, r]], "Grayscale"]}]
   }],
 {{pt, {.5, .5}}, ControlType -> None},
 {{gs, .5, "Grayscale"}, 0, 1, Appearance -> "Labeled"},
 {{r, 1, "Red"}, 0, 1, Appearance -> "Labeled"},
 SaveDefinitions -> True]

grayscale

The blue line indicates the colors which has the exact grayscale value. If you move the locator, it tries to snap in to the line and show you RGB value as well as converted graysale value (which should be the same as the slider value).

Discussion

belisarius asked, "Then what is the yellowest color whose grayscale value is .3?".

There are several problems. First, what is "yellow"-ish colors? Are these colors close to {1,1,0} in RGB space? Then the solution is the point on the plane 0.299 x + 0.587 y + 0.114 z = 0.3 whose distance is the shortest from {1,1,0} which can be easily solvable. But you will quickly realize that it is not really "yellow". For instance, {1,0,0} and {0,1,0} are both exactly the same distance from the yellow, but none perceive them as yellowish. Then, is it a point with the proportion of R and G is 1:1? It is better but not entirely. Red and green have difference luminescence (thus different weights in grayscale conversion). Now, should we consider the weight? Well, then how about gamma and gamut? Then Euclidean distance becomes useless... I wouldn't even start the fact that human color perception is not linear but more of quadratic space... And then there is a whole story about chromaticity and visible spectrum (then different "yellow" there).

Getting the closest color for some color can be surprisingly hard.

share
    
Ok, But how do I find the most "yellowish" color with grayscale .3? –  belisarius Jul 26 '12 at 19:01

Just a starting point: Buried in the documentation for GrayLevel is this example function:

RGBToGray[RGBColor[r_, g_, b_]] := GrayLevel[.299 r + .587 g + .144 b]

So each grayscale tone desiredtone corresponds to a family of colors satisfying

desiredtone == .299 r + .587 g + .144 b

Update: Unfortunately, this RGBToGray function does not use the same mapping as ColorConvert[#,"Grayscale"]:

enter image description here

share
3  
Please make your code copyable. –  Mr.Wizard Jul 30 '12 at 9:51

Mathematica graphics

For any color c:

f[c_, x_?NumericQ] := ColorConvert[Blend[{White, c, Black}, x], "Grayscale"][[1]]

Find a similar hue (ie lighter or darker) color for gray tone .3

findBlend[myColor_, Grayness_] := FindRoot[f[myColor, x] == Grayness, {x, 1/2}];
blend = findBlend[Blue, .3]

Test it

col = Quiet[Blend[{White, c, Black}, x] /. c -> Blue /. blend]
(* RGBColor[0.209932, 0.209932, 1.] *)
ColorConvert[col, "Grayscale"]
(*GrayLevel[0.3]*)

Testing the same hue:

{ColorConvert[col, "HSB"], ColorConvert[Blue,"HSB"]}
(* {Hue[0.666667,0.790068,1.],
    Hue[0.666667,1.,1.]
   }
*)

Edit

This could be made more efficient if needed by eliminating the FindRoot[] part since:

Plot[f[Blue, x], {x, 0, 1}]

Mathematica graphics

So you only need to calculate f[c. 1/2] and then interpolate linearly to the left or to the right.

Edit2 Answering @Sza's comment

Dirty code for the table at the beginning of this answer

c = Blue;
f[u_, x_?NumericQ] :=  
   Piecewise[{{ColorConvert[Blend[{White, u, Black}, x], "Grayscale"][[1]], 0 <= x <= 1}}];
findBlend[myColor_, Grayness_?NumericQ] := FindRoot[f[myColor, x] == Grayness, {x, 1/2}];
g[u_, x_?NumericQ] := Blend[{White, u, Black}, x];
GraphicsGrid[
 {Range[10]/10.,
  Graphics /@ ({#, Rectangle[]} & /@ (r = g[c, #] & /@ Table[x /. findBlend[c, p/10], {p, 10}])),
  ColorConvert[#, "Grayscale"] & /@ Graphics /@ ({#, Rectangle[]} & /@ r)},
 Frame -> All]
share
3  
Very nicely done. Here's a standalone version of that: findGrayColor[color_, desiredTone_] := Blend[{White, color, Black}, Interpolation[ Transpose[{{1, First[ColorConvert[Blend[{White, color, Black}, 0.5], "Grayscale"]], 0}, {0, 0.5, 1}}], InterpolationOrder -> 1][ desiredTone]] –  ArgentoSapiens Jul 26 '12 at 18:37
    
How did you create the colour table at the top? The code you gave works nicely, but the table at the top does not have blues of the same greyness as the greys below. –  Szabolcs Jul 27 '12 at 6:58
    
Hmmm ... –  Szabolcs Jul 27 '12 at 7:04
    
@Szabolcs posted some code –  belisarius Jul 27 '12 at 14:41

I once saw a formula

$$ \text{gray} = 0.3 \times \text{red} + 0.59 \times \text{green} + 0.11 \times \text{blue} $$

in the document for LaTeX package xcolor (section 6.3.1), wich points to a reference by Adobe: PostScript Language Reference Manual (page 474)

It is similar to that mentioned in Yu-Sung Chang's answer, wish the document could be a useful supplement.

Edit

As about how to do it for chosen hue, I wrote this (row for different saturation, column for different brightness):

Clear[grayfunc]
grayfunc[h_, s_, b_] := Piecewise[{
   {b - (7 b s)/10 + (177 b h s)/50, 0 <= h < 1/6},
   {b + (19 b s)/100 - (9 b h s)/5, 1/6 <= h < 2/6},
   {b - (63 b s)/100 + (33 b h s)/50, 2/6 <= h < 3/6},
   {b + (147 b s)/100 - (177 b h s)/50, 3/6 <= h < 4/6},
   {b - (209 b s)/100 + (9 b h s)/5, 4/6 <= h < 5/6},
   {b - (b s)/25 - (33 b h s)/50, 5/6 <= h <= 1}
   }, 0]

Manipulate[
 Row[{ColorConvert[Rasterize[#, ImageSize -> 200], "Grayscale"], #}] &[
  Graphics[Table[{
     With[{s2 = If[gray >= grayfunc[h, 1, 1],
         Min[s, s2 /. Solve[grayfunc[h, s2, 1] == gray, s2][[1]]],
         s]},
      With[{b2 = b2 /. Solve[grayfunc[h, s2, b2] == gray, b2][[1]]},
       Hue[h, s2, b2]
       ]],
     Disk[{s, b}, .05]}, {s, 0, 1, .1}, {b, 0, 1, .1}], 
   ImageSize -> 200]
  ],
 {{gray, .6}, 0, 1},
 {{h, 1/6}, 0, 1}]

Mathematica graphics

And the relative error vs saturation and brightness:

Manipulate[
 Table[With[{s2 = If[gray >= grayfunc[h, 1, 1],
        Min[s, s2 /. Solve[grayfunc[h, s2, 1] == gray, s2][[1]]],
        s]},
     With[{b2 = b2 /. Solve[grayfunc[h, s2, b2] == gray, b2][[1]]},
      {s, b,
       ColorConvert[Hue[h, s2, b2], "Grayscale"][[1]]/gray - 1
         // Abs // Log[10, #] &
       }
      ]],
    {s, 0, 1, .1}, {b, 0, 1, .1}]
   // Flatten[#, 1] &
  // ListPlot3D[#, PlotRange -> All, ClippingStyle -> None,
    AxesLabel -> Join[
      Style[#, 20, Bold] & /@ {s, b},
      {Rotate[
        Style[
         "relative error ( \!\(\*SubscriptBox[\(log\), \\(10\)]\)-ed )",
         15, Bold], \[Pi]/2]
      }]] &,
 {{gray, .6}, 0, 1},
 {{h, 1/6}, 0, 1}]

Mathematica graphics

As the result shown, the grayscale is mainly related to brightness, consistent with our intuition.

The formula for grayfunc is derived by converting HSB to RGB using the formulae in section 6.3.4 of the document for LaTeX package xcolor, then converting RGB to grayscale using the formula above.

share

This site is currently not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .