A few weeks ago, I proudly released the 1.0.0-alpha version of
and updated the
Projects page of this site accordingly,
disregarding my resolution to release existing projects rather than work
on new ones. In all fairness, this project isn’t of utmost importance
and I’ll probably remain its only user, but to quote what I said back
when I released SAW:
It’s not much, mainly because no one is using it besides me, which means I have no issues to deal with, no weird bugs that I can’t seem to reproduce… But it doesn’t mean that releasing it publicly is devoid of meaning.
So, let’s talk about colors, design, C++ and build systems! This post focuses on some technical aspects of the MCL, in no particular order. A (likely) next article will focus on color interpolation fun facts.
Once upon a time…
…there was an interesting article on Hacker News. It talked about procedural color generation, and color interpolation using the CIE LCHab color space. As it happens, I had already written a rudimentary RGB and HSV hybrid color class that I used for such a purpose, but it had several shortcomings, the main one being caching all fields of the two color spaces (R, G, B, H, S, V). As a result:
Colorclass was rather heavy,
- it was hard to extend with other color spaces,
- it wasn’t efficient (frequent recomputation).
I was therefore usually using several color classes at the same time: this one for interpolation, translated into another one that I used with OpenGL code… It was far from being convenient. Inspired by that article, I launched head first into a better color class implementation, starting what would become the MCL.
From the start, I had a few goals that I wanted to achieve. While some of the funny features emerged later, while coding, those were intended from day 1.
A lightweight RGB class
I wanted to have a RGB struct that I could cast to a
double*, or a
unsigned char*, depending on the chosen data
type. This would allow me to have RGB arrays that I could give
directly to OpenGL, amongst other possibilities.
Support for LCHab color space
In addition to the color spaces I already knew (namely RGB, HSV and HSL), I wanted this future library to be able to also handle LCHab values, which were at the heart of the aforementioned article.
There already was a colormap implementation along with the old color class. It would need to be adapted to the new color classes, as this was the main feature I wanted to achieve.
Although this isn’t exactly a requirement, I used the excuse of C++11 to convince myself it would be a good idea to start a brand new project: I hadn’t yet written anything using C++11’s new features.
Algebraic types over inheritance
Due to requirement #1, I could not have different classes for each color space (RGB, HSV…) all inheriting from a common Color base class abstraction. The reason being, of course, the vtable. No inheritance means no vtable, meaning in turn that the size of the class should be the sum of the size of its elements, providing they’re correctly aligned. As the compiler isn’t allowed by the standard to modify the members order, the resulting class should meet the requirement.
It is worth noting, however, that relying on the fact that compilers won’t pad those classes with any kind of metadata is a bit ugly, as there is no rule in the standard that enforces such a layout (those classes being non-POD). Although it behaves correctly on all tested systems and all tested compilers, using that knowledge to do ugly casts (for OpenGL purposes for instance) is a direct step into the uncharted unholy territory of undefined behaviour. Fun stuff. :)
The best solution to have a generic Color class is to use functional
programming tools: in this case,
algebraic data types. As
there is no native support for such types in C++ (unions do not count),
I unleashed the almighty
allows the creation of pseudo “typed unions”.
Compared to a type hierarchy, such a type is at the opposite end of the Expression Problem: while it makes it tedious to add new color spaces, new types (something that is, in this case, unlikely, or at least uncommon), adding new generic functions over color spaces is easy, and can be done without modifying the existing classes. But more importantly, this type allows for easy, polymorphic, pointer-free use of different color spaces instances on the stack.
A naive way of implementing color space conversion would be to have all N * N conversion functions, which would be a hassle to implement. Instead, the MCL splits the different color spaces in three groups.
- Print group: CMYK, CMY.
- Display group: RGB, HSL, HSV.
- Absolute group: XYZ, LAB, LCH.
Each group has a “reference” type (highlighted above), which each type
knows via a
typedef. Converting from a color space A to a color
space B is therefore done by moving along the resulting graph (see
below). This severely limits the total number of functions
needed. Furthermore, adding a new color space would only mean
implementing conversion from and to its reference type.
Those groups were not chosen randomly. They exhibit a nice behavior:
color conversion inside a group does not depend on external parameters
such as a referent white
(except for one very specific exception: XYZ <-> LAB). Conversions
from one group to one of the others do however require such
parameters. They’re all bundled in a class called
All MCL functions that might need at some point to convert from one
color space to another have a
Environment parameter. For convenience,
however, they all have a
Environment-free variant that passes in
Environment::DEFAULT, which specifies that all device independent
color spaces have to considered as being
sRGB with a
illuminant. Users that want to specify another behavior can inject their
Environment instances in all calls, or choose the sinful path of
Environment::DEFAULT, which is mutable.
Most transformation functions can be expressed as
endomorphisms: their type
Color -> Color. As such, they form a
providing that we implement the equivalents of
in this case
While in Haskell this is really straightforward,
in C++, it’s a tiny bit more verbose…
This allows for fun stuff, such as combining transformations, or even
transformations folding: a list of color transformations can be reduced
to a single one. This is also true for any user defined function over
Color instances, as long as it can be implicitly made into an
Endomorphism, which is defined as
std::function<T (T const&)>.
Although this was not part of the requirements, this was aesthetically rather pleasing. It was also the opportunity to find a way to implement Haskell-like typeclasses in C++, for which I found a traits-based solution that I find funny if not particularly useful. I’ll speak more about it in an upcoming article.
Although I’m quite proud of how everything turned out, this library has several shortcomings.
- The build system is my own ugly one, hand-written in 2008…
- The only clamping functions provided for out of gamut colors is really naive.
- I have made no thorough performance study of the code performance (though I suspect it’ll work better with a high inlining limit).
- It severely lacks feedback from real users!
If you want to see more details about some technical aspects of the MCL, the wiki of the project details each major feature of the library.
To summarize: this was a lot of fun. :)