21
$\begingroup$

Suppose I'm writing a function that takes a color as a parameter; for example:

drawShape[color_] := Graphics[{Style[Disk[], color]}]; 

But if the caller inputs an invalid color, bad things happen:

Bad

So I want to use a pattern to define drawShape only for values that are actually colors. Conceptually,

drawShape[color_Color] := ... 

The problem is that unlike (say) Lists, Integers, Reals, Complexes, or Graphicses, color objects do not share a Color head. That is,

In[1]:= Red // Head Out[1]= RGBColor In[2]:= Hue[0.5] // Head Out[2]= Hue In[3]:= GrayLevel[0.5] // Head Out[3]= GrayLevel In[4]:= CMYKColor[0, 1, 1, 1/2] // Head Out[4]= CMYKColor In[4]:= Opacity[0.5, Purple] // Head Out[4]= Opacity In[5]:= Transparent // Head Out[5]= GrayLevel 

So that won't work. I also don't see any ColorQ function, with which I could write drawShape[color_ ? ColorQ] := ....

How can I write a pattern that matches any valid color object? Is there a more robust way than just testing for each of these heads?

$\endgroup$

2 Answers 2

26
$\begingroup$

Original method

colorQ = Quiet @ Check[Blend @ {#, Red}; True, False] &; colorQ /@ {Red, Hue[0.5], GrayLevel[0.5], CMYKColor[0, 1, 1, 1/2], Opacity[0.5, Purple]} 
{True, True, True, True, True} 
colorQ /@ {17, 1.3, Pi, "not a color", {1, 2, 3}, Hue["bad arg"]} 
{False, False, False, False, False, False} 

You would use: drawShape[color_?colorQ] := . . .


Inspired by kguler's comment this might also be formulated as:

colorQ = Quiet[Head @ Darker @ # =!= Darker] &; 

Or:

colorQ = FreeQ[Quiet @ Darker @ #, Darker] &; 

Edit: Darker works on entire Image and Graphics objects and therefore the two forms immediately above will incorrectly return True in these cases. Blend solution is still valid.


Version 10 update and analysis

In version 10 there is a built-in function for this: ColorQ

ColorQ[color] yields True if color is a valid color directive and False otherwise.

A bit of spelunking reveals that the inner definition of this function is (contexts stripped for clarity):

iColorQ[args_?(ColorDirectiveQ[Head[#1[[1]]]] &), opts_] := NumberQ[Quiet[ToColor[args[[1]], XYZColor][[1]]]] 

This is very similar to my own method, however the inner definition of ColorDirectiveQ omits Opacity:

iColorDirectiveQ[args_, opts_] := TrueQ[Quiet[ MatchQ[args[[1]], GrayLevel | RGBColor | CMYKColor | Hue | XYZColor | LUVColor | LABColor | LCHColor]]] 

This means that the function will return False for e.g. Opacity[0.5, Purple] where mine returns True.

$\endgroup$
5
  • 3
    $\begingroup$ Nice. I particularly like that it can distinguish between Opacity[0.5, Purple] and Opacity[0.5]. $\endgroup$ Commented Nov 30, 2012 at 11:40
  • 3
    $\begingroup$ Neat! (Darker@#;True or Lighter@#;True work as well.) $\endgroup$ Commented Nov 30, 2012 at 14:10
  • $\begingroup$ @kguler Good idea! $\endgroup$ Commented Nov 30, 2012 at 14:15
  • $\begingroup$ @kguler I added a couple of other versions based on your comment. $\endgroup$ Commented Nov 30, 2012 at 17:23
  • $\begingroup$ That's very useful (+1), I should put one of these tests into my answer here as well. $\endgroup$ Commented Nov 30, 2012 at 19:14
10
$\begingroup$

I think that the following catches all color heads.

Clear[test]; test[x : (_CMYKColor | _Hue | _RGBColor)] := "good" test[else_] := "bad" 

Let's try it

test[Red] (* Out: "good" *) test[red] (* Out: "bad" *) 
$\endgroup$
2
  • 2
    $\begingroup$ test[GrayLevel[0.5]] "bad". Obviously you can just test the heads against all you think of, but that's not robust if you forget one or if Wolfram adds another. $\endgroup$ Commented Nov 30, 2012 at 1:38
  • 2
    $\begingroup$ Note that this will fail for malformed or incomplete color directives, e.g. RGBColor[1]. +1 nevertheless, but see my answer for a more robust method. $\endgroup$ Commented Nov 30, 2012 at 2:22

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.