# Create colour with given lightness value

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.

• "...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. will be back soon Jul 26 '12 at 16:54
• You might be interested in this research. – Silvia Apr 22 '16 at 8:52
• @Silvia Can you explain how the specific research paper is improving the contrast in Mathematica's default conversion to gray from its default colormap? – Léo Léopold Hertz 준영 Sep 22 '16 at 11:13
• @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. – Silvia Sep 22 '16 at 20:16
• @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. – Silvia Sep 23 '16 at 17:11

## 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[#[], gs, r]},
If[0 <= b <= 1, pt = {#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] 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.

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

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][]],
s]},
With[{b2 = b2 /. Solve[grayfunc[h, s2, b2] == gray, b2][]},
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}] 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][]],
s]},
With[{b2 = b2 /. Solve[grayfunc[h, s2, b2] == gray, b2][]},
{s, b,
ColorConvert[Hue[h, s2, b2], "Grayscale"][]/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}] 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. For any color c:

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


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}] So you only need to calculate f[c. 1/2] and then interpolate linearly to the left or to the right.

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"][], 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.,
Graphics /@ ({#, Rectangle[]} & /@ (r = g[c, #] & /@ Table[x /. findBlend[c, p/10], {p, 10}])),
ColorConvert[#, "Grayscale"] & /@ Graphics /@ ({#, Rectangle[]} & /@ r)},
Frame -> All]

• 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 – Dr. belisarius Jul 27 '12 at 14:41

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"]: • Please make your code copyable. – Mr.Wizard Jul 30 '12 at 9:51