27
$\begingroup$

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.

$\endgroup$
6
  • 3
    $\begingroup$ "...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. $\endgroup$ Commented Jul 26, 2012 at 16:54
  • 1
    $\begingroup$ You might be interested in this research. $\endgroup$
    – Silvia
    Commented Apr 22, 2016 at 8:52
  • $\begingroup$ @Silvia Can you explain how the specific research paper is improving the contrast in Mathematica's default conversion to gray from its default colormap? $\endgroup$ Commented Sep 22, 2016 at 11:13
  • 1
    $\begingroup$ @Masi Good question. I compared the ImageHistogram of their result with that of Mathematica 11's result given by ColorConvert[theColorPNG,"Grayscale"], they look quite the same. I'm not an expert in related field, but I guess Mathematica is already powered by similar algorithm. $\endgroup$
    – Silvia
    Commented Sep 22, 2016 at 20:16
  • 1
    $\begingroup$ @Masi I don't remember which is which :( But when I said "quite the same", I meant to say they are similar to each other comparing with the histogram of brightness/illumination channel results from color spaces like XYZ, LAB, etc. $\endgroup$
    – Silvia
    Commented Sep 23, 2016 at 17:11

4 Answers 4

20
$\begingroup$

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.

$\endgroup$
1
  • 2
    $\begingroup$ Ok, But how do I find the most "yellowish" color with grayscale .3? $\endgroup$ Commented Jul 26, 2012 at 19:01
14
$\begingroup$

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.

$\endgroup$
14
$\begingroup$

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]
$\endgroup$
4
  • 4
    $\begingroup$ 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]] $\endgroup$ Commented Jul 26, 2012 at 18:37
  • $\begingroup$ 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. $\endgroup$
    – Szabolcs
    Commented Jul 27, 2012 at 6:58
  • $\begingroup$ Hmmm ... $\endgroup$
    – Szabolcs
    Commented Jul 27, 2012 at 7:04
  • $\begingroup$ @Szabolcs posted some code $\endgroup$ Commented Jul 27, 2012 at 14:41
9
$\begingroup$

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

$\endgroup$
1
  • 4
    $\begingroup$ Please make your code copyable. $\endgroup$
    – Mr.Wizard
    Commented Jul 30, 2012 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.