# Pedantic Semantics – Colorspace conversions

One of the most interesting running debates in my head is how to represent ‘color’ in computer graphics. First of all, some of the best resources available have written by Paul Bourke.  There is one page in paritcular which offers quite a bit related to color spaces and whatnot: Color Spaces.

There is one distinction to make.  There is a difference between a color and a pixel, even if they have similar namings.  A pixel is a representation of a color, that is appropriate for  a particular device.  In computer graphics, pixels are typically an RGB byte pair, like Red: (255, 0, 0).  That is, the pixel takes up 3 bytes of memory, each byte representing a value between 0 and 255.

A colorspace, is a bit different.  You might have the linear color space, also known as RGB, but it will represent the values in a range of 0..1.0, inclusive.  Thus, the same value of red might be represented as RGBColor Red = (1.0, 0, 0).

This is a source of confusion sometimes.  It gets particularly interesting when you want to convert from one form to another.  You could convert straight pixel values, or you could convert colorspace based values.  Really what you want in most cases is to deal with colorspace values, and then just transform to RGB Pixel values when you really need to.  But, storage concerns might prove otherwise, and most of the time we end up converting pixel values instead.

Ahhh, such are the injustices of life.

One of the things that I want to keep straight in my head though is how to do things properly if I actually want to maintain a separation between my colorspace and my pixel space representations.  So, I have a couple of colorspace structures to help me out.

First, I choose as a base colorspace the RGB color space.  Why this one and not one of the sRGB, or CIEXYZ, or what have you?  Mainly because it makes for some easy coding, and I’m only intending to use it for computer graphics and print, so I’m not going to bother myself with others.

There are two color spaces I care about.  RGB, the well known Red, Green, Blue.  Then there’s HSL (HSB), Hue Saturation, Lightness (Brightness).  RGB because it’s standard, and HSL because it’s very common, in environments such as processing.  The RGB color space will be the base.  Here’s how they are defined:

```
ffi.cdef[[
typedef struct {
double r,g,b;
} RGBColor, *PRGBColor;

typedef struct {
double r,g,b,a;
} RGBAColor, *PRGBAColor;

typedef struct {
double h, s, l;
} HSLColor, *PHSLColor;

]]

local RGBColor = ffi.typeof("RGBColor");
local RGBColor_mt = {
__tostring = function(self)
return string.format("%3.4f, %3.4f, %3.4f", self.r, self.g, self.b)
end,

}
local RGBColor = ffi.metatype(RGBColor, RGBColor_mt)

local RGBAColor = ffi.typeof("RGBAColor");
```

Kind of heavy handed to use doubles for the values?  Well, these are colors, not pixels.  You’re probably not going to store them by the millions in a frame buffer.  More typically, you’re going to define a few hundred our thousand of them, and then create pixel values from them by the millions.  So, you want this base representation to have as much precision as possible.  Besides, double matches with the lua ‘number’ type, so there are no gratuitous conversions, as would be true if you stored the values as ‘float’.

Then there’s one convenience function to convert from a typical RGB pixel byte triplet to this RGBColor structure:

```
local RGBToRGBColor = function(r,g,b) return RGBColor(r/255, g/255, b/255) end

snow = RGBToColor(255,255,0);
bisque = RGBToColor(255,228,196);

print("snow: ", snow)
>> snow: 	1.0000, 1.0000, 0.0000
print("bisque", bisque)
>> bisque	1.0000, 0.8941, 0.7686
```

Next, I want to look at the HSL color space. There are a few ways I would like to construct HSL colors. Here are some examples:

```local snow = RGBToColor(255,255,0);
local h1 = HSLColor();      -- default constructor
local h2 = HSLColor(snow)   -- construct from RGBColor value
local h3 = HSLColor(h2)     -- copy constructor, from HSLColor value
local h4 = HSLColor(46, 1.0, 0.59)  -- construct from HSL components
local r4 = h4:ToRGBColor(); -- convert to RGBColor representation
```

This is where metatypes really come in handy. If you were doing this in C++, you’d define a default constructor, and a couple of copy constructors, and a cast operator, and that would be that. In Lua, there’s not all that much operator overloading, so you end up putting these things into one place. So, here’s some of the HSL metatype:

```HSLColor = ffi.typeof("HSLColor")
HSLColor_p = ffi.typeof("PHSLColor")

HSLColor_mt = {
--[[
Calculate HSL from RGB
Hue is in degrees
Lightness is between 0 and 1
Saturation is between 0 and 1
--]]
__new = function(ct, ...)
local nelems = select("#", ...)

-- Default constructor
if nelems == 0 then
return ffi.new(ct)
end

-- element constructor
if nelems == 3 then
return ffi.new(ct, select(1,...), select(2,...), select(3,...));
end

-- Should have only 1 argument now
if nelems ~= 1 then
return nil
end

local c1 = select(1,...)

-- Copy constructor
if ffi.istype(HSLColor, c1) then
return ffi.new(ct, HSLColor)
end

-- Must be Copy from RGBColor
if not ffi.istype(RGBColor, c1) then
return nil
end

local c2 = ffi.new(ct);

local themin = MIN(c1.r,MIN(c1.g,c1.b));
local themax = MAX(c1.r,MAX(c1.g,c1.b));
local delta = themax - themin;
c2.l = (themin + themax) / 2;
c2.s = 0;

if (c2.l > 0 and c2.l  0) then
if (themax == c1.r and themax ~= c1.g) then
c2.h = c2.h + (c1.g - c1.b) / delta;
end
if (themax == c1.g and themax ~= c1.b) then
c2.h = c2.h + (2 + (c1.b - c1.r) / delta);
end
if (themax == c1.b and themax ~= c1.r) then
c2.h = c2.h + (4 + (c1.r - c1.g) / delta);
end
c2.h = c2.h * 60;
end
return(c2);
end,

__tostring = function(self)
return string.format("%3.2f, %3.2f, %3.2f", self.h, self.s, self.l)
end,

__index = {
--[[
Calculate RGB from HSL, reverse of RGB2HSL()
Hue is in degrees
Lightness is between 0 and 1
Saturation is between 0 and 1
--]]
ToRGBColor = function(c1)
while (c1.h  360) do
c1.h = c1.h - 360;
end

local sat = RGBColor();
if (c1.h < 120) then
sat.r = (120 - c1.h) / 60.0;
sat.g = c1.h / 60.0;
sat.b = 0;
elseif (c1.h < 240) then
sat.r = 0;
sat.g = (240 - c1.h) / 60.0;
sat.b = (c1.h - 120) / 60.0;
else
sat.r = (c1.h - 240) / 60.0;
sat.g = 0;
sat.b = (360 - c1.h) / 60.0;
end

sat.r = MIN(sat.r,1);
sat.g = MIN(sat.g,1);
sat.b = MIN(sat.b,1);

local ctmp = RGBColor();
ctmp.r = 2 * c1.s * sat.r + (1 - c1.s);
ctmp.g = 2 * c1.s * sat.g + (1 - c1.s);
ctmp.b = 2 * c1.s * sat.b + (1 - c1.s);

local c2 = RGBColor();
if (c1.l < 0.5) then
c2.r = c1.l * ctmp.r;
c2.g = c1.l * ctmp.g;
c2.b = c1.l * ctmp.b;
else
c2.r = (1 - c1.l) * ctmp.r + 2 * c1.l - 1;
c2.g = (1 - c1.l) * ctmp.g + 2 * c1.l - 1;
c2.b = (1 - c1.l) * ctmp.b + 2 * c1.l - 1;
end

return c2;
end,
}
}
HSLColor = ffi.metatype(HSLColor, HSLColor_mt);
```

The __new() operator deals with all the constructor business. That is, when you’re doing anything like: HSLColor(), the __new metamethod will be called. In this case, I set the parameters to ‘…’, that is the Lua way of saying “variable argument list”. The form: select(“#”,…) is how you says “how many arguments are there?”.

By checking the number of arguments, and the types of the arguments, you can get the various constructors. I find this to be interesting, because you have to resolve any ambiguities explicitly. If you were using the C++ compiler, it would throw up warnings when it saw ambiguities. I find that having to deal with them myself explicitly is probably a good thing as I’ll really have to think about the types that I’ll accept, and what I won’t, and what I will do if I see something I don’t like.

The “ToRGBColor()” function is a simple metamethod, which will be called on any HSL structure. It will create an instance of a RGBColor object. And that’s that.

I don’t get too fancy here, in that I don’t convert to RGB Pixel values. Instead, I leave that to the pixel class, so I don’t introduce a circular dependency.

And that’s about it. Just some simple color handling, as is different from pixel handling. I can add more color spaces, and more conversion routines, such as coming up with grayscale from a color value, or creating RGBColor from a frequency value. The key thing is to keep these all done in color spaces, rather than pixel spaces, then convert to pixel spaces when required. It’s a distinction that might not always make sense for pragmatic programming, but if you want to make such a distinction, you can.