Managing the 16,777,217th Color

By John S.

Here’s a quick poll:  How many of you have figured out by now that 16,777,217 = 2^24+1, and that the "24" refers to the 3 RGB bytes in RGBA32?

An alternate title I considered for this post was "NoColor is a color", which would have been an inside joke for people who go to science fiction conventions.  Considering the audience, I decided to stick with the computer science reference :)

So, what am I talking about?  The fact that, for 24-bit RGB colors, there are actually 2^24+1 possible states, since the absence of color is itself a state.  This in turn causes problems because it’s very difficult to jam 2^24+1 states into 24 bits.  The more subtle problem here is that people often aren’t thinking about the extra state when they write their code, which can lead to "Happy Path" algorithms.  So the question is, "How do we provide an interface to customers that manages the NoColor issue in the best way possible".

Let me give a concrete example.  In our CGM product, we provide the ability to attach "properties" (such as color) to geometric objects – basically we provide SetColor and GetColor methods.  If no color is present, then the object takes on the color of its parent object (or the default color if it is a root object).  The problem we had to deal with was how GetColor should indicate the absence of color.  More precisely, what should the signature of GetRed be, and what should it do when color is absent?

First, the evil answer: int GetRed() returns a “magic (invalid) value” such as -1 when no color is set:

The reason this is evil is that the server (the Color1 class) is intentionally returning an invalid result, and relying on the person writing the client code to remember he needs to check for a special state, then go down a different code branch in that case.  I remember reading somewhere (but can’t find the reference) the "Tiger Trap" principle of interface design: that using the interface correctly should be like a tiger trap – consumers of the interface naturally fall into it.  With this interface choice, the tiger trap is in the direction of using the interface incorrectly.  An insidious variation on this design which might even be worse is to return a valid value for red in the NoColor case.  The reason this might be worse is that an invalid value can be detected by a contract check (in DisplayRedValue() in the example).  A valid (but arbitrary) value is undetectable, and can lead to weird results that are difficult to track down.

Next, the answer from my last post:  create a Nullable<T> template class and have the GetColor query on an object return a Nullable<Color>:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

There are several nice things about this approach:

  • The color class is now responsible for only 2^24 states – the NoColor state is managed by the Nullable class.
  • The Nullable class is designed for exactly this situation, so the approach is intuitive.
  • The tiger trap is now in the correct direction –the compiler won’t let the customer call GetRed() on a Nullable<Color>.

In fact, this is the approach that we use our sample scripting application for CGM – the javascript objects returned by GetColor() wrap a Nullable<Color> object. 

Although the Nullable approach is appropriate in certain circumstances, it still has a drawback: the client still needs to write an if statement when actually getting the RGB values.  This problem is solved by the last approach: pass the RGB values by reference, and don’t change them if the color is unset:

 

 

 

 

 

 

 

 

 

The nice thing about this interface is that it caters to the client’s workflow.  Basically, the client calculates the RGB values that will be used if the color is unset (typically by looking at parent objects and/or the default color).  The client then calls GetRGB – if the color is unset, then the previously calculated colors are left alone, otherwise they’re overridden.  This is the interface that we chose to use for color (and other, similar) properties in our CGM product.

We have found this signature idiom to be really useful in a lot of places.  For example, it’s a natural fit for a chain-of-responsibility pattern:

 

 

 

 

(Yes, I know I could reduce this to a single line, but I’m conservative about these things J )  I’m sure many of you are already using this one in your code, but for those of you that aren’t, I hope you find it useful as well.

One last thing: if anyone out there knows the correct tiger-trap reference could you please post it in a reply?  I’d really like to track it down for proper attribution.

Tags:

Hi John, Thanks for the

Hi John,

Thanks for the comment - sorry you're lost!

I'm not quite sure what you mean by your question about "...why include an interface...", unless by "your code" you mean "an object"? If this is what you mean, then the answer is "to simplify the client code". In more detail it's "Because we don't want to force the client code into writing an if statement." If you look at the client3 code, it's two lines without an if statement. One thing I forgot to mention in the post: the bool that's returned is there so that the client code author can use an if statement if he wants to.

For your second question, I think what you're suggesting is an interface that leads to client code that looks something like this:

ColorProperty const* pColor = object.GetColorProperty();
if(NULL != pColor) {DisplayRedValue(pColor->GetRed());}

This is a workable idea, and in fact it's the way both ACIS deal with this issue internally. You'll notice that it is also very close to the Nullable idea. This has several disadvantages in comparison to the Nullable or client3 interfaces, though:

1) There's no chance of access violating. In the code above, it would be easy for a client to forget to check for a NULL pointer, which leads to an access violation.

2) You avoid exposing internal classes to the client. In the code above, you have to give the client ColorProperty.h, which means he has access to all the methods of ColorProperty. In CGM, we didn't want to expose our property classes to customers - their headers aren't shipped as part of the package. Instead, for querying the properties attached to an object we have an examiner class that has methods like bool GetColor(int&r, int&b, int&g) const;. So if you don't want to give them access to your property class, you can't use its null-ness as a flag, and you have to find another way.

3) Memory ownership: Who owns the pointer returned by GetColorProperty()? Is the client supposed to delete it? If you're not totally consistent in your application, then this question leads to HUGE confusion (and frustration) for customers who need to go digging through the docs for the answer, and even then can't necessarily trust the answer they find. We find it better to avoid putting pointers in the interface, and instead work with value classes and shared_ptrs.

The client3 idiom avoids these issues, which is why it's the one I recommended.

Cheers, John (S. :) )

PS - One last thing I remembered when re-reading the above. We actually got burned on the ACIS side by someone getting this wrong. The implementation of transparency in ACIS is handled using data structures in some libraries we bought as source years (decades?) ago. The problem is that the object that manages transparency also has a color data member, and that there was no way to indicate a "no color" state. Whenever transparency was applied to an object, the object was queried for its color and the color was put into the transparency object. If the object had no color, the default color was put in. This was already a subtle bug - if the default color was changed, partially transparent uncolored objects would keep the old default color. The place this bug actually bit us was when we started doing assembly modeling, where the color can come from other places in the assembly tree. So we suddenly had red cars that, when a wheel was made partially transparent, would end up with a green (the default color) wheel. We had to go into the transparency data object and add a "no color" flag explicitly. We ran into this issue when putting properties into CGM - it's very important to use different property objects for color and transparency (rather than a single RGBA object), since there are two possible "no value" states. Fortunately, we'd already been burned once so we were watching for it this time :) So a major motivation for writing this post was "don't get burned by this common issue that burned us in the past" :)

Hi, I'm totally lost by this

Hi,
I'm totally lost by this post. If your code does not have a color property, why include an interface to read or write the color state? If you need to dynamically add or remove properties to your objects, would the presence of the property in the property management suffice as the extra state? Cheers, John G.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Images can be added to this post.

More information about formatting options

Twitter Facebook LinkedIn YouTube RSS