Pliant graphical stack color models
The mainstream graphic libraries way of coding colors
Once again, mainstream graphic stack design is largely derived from historical time where they started as hardware abstraction layers.
Standard encodings are:
RGB stands for Red Green Blue, and it encodes the color as a level or red light, a level of green light, and a level of blue light since it was the way to produce color on old computer screens.
Some libraries provide extra more advanced encodings:
CMYK stands for Cyan Magenta Yellow Black, and it encodes the color as an amount of each of the four inks since it's the way to print colors on most printing systems.
Pliant graphic stack way of coding colors
Here are the rules followed by various Pliant color models used in the Pliant graphical stack.
The color model used for specifying a color is provided as a string parameter passed to 'color_gamut' function that returns a data structure with type ColorGamut providing all informations (number of components, of bytes per pixel, etc) about pixel encoding details to various graphical stack functions.
Here is a sample:
var Link:ColorGamut g :> color_gamut "rgb"
The standard names for additive color models are:
First, a color device is mostly composed of a set of inks with color spectrum values describing the color of inks or inks mixtures at various levels.
Then, the name a subtractive color model is the name of the device, followed by a colon sign, then the name of all inks (components) separated by plus signs, and an optional '+transparency' or '+transparencies' if one or one per component transparency channel is to be added.
The first color model has 4 bytes per pixel, the second has 10.
Each color device is encoded as a Pliant PML encoded database file stored in data:/pliant/color/device/ directory. The data structure of the database is defined in module /pliant/graphic/color/database.pli
The URL for accessing the various color devices currently defined in your system is:
Here is an example loading the data:/pliant/graphic/color/epson/R800/semigloss/log PML encoded Pliant database file defining the 6 inks used by an Epson R800 inkjet printer:
var Link:ColorGamut g :> color_gamut "epson/R800/semigloss:cyan+magenta+yellow+black+red+blue"
Also, before using a substractive gamut, you should test if it's a valid one since constructing it might have failed as a result of missing underlying database:
The two basic properties of a gamut is decoding and encoding a pixel:
var ColorGamut g :> color_gamut "rgb"
Decoding means convert each component (encoded in the pixel as an 8 bits unsigned integer ranging from 0 to 255) to a 32 bits floating point value ranging from 0 to 1.
Then, various properties of the color model can be queried through:
var Int i
'dimension' is the dimension of the color model from a mathematical point of view. For RGB or RGBA, it's 3. For CMYK, it's 4.
var Str s
The 'model' value can either be 'color_gamut_additive' or 'color_gamut_substractive'.
Some extra properties of the color model can be obtained through 'query' method:
var Str s
'component_name' is often used to enumerate inks in a subtractive gamut.
The great feature of Pliant graphic stack gamuts is that they provide not too bad (I could have said world best) color conversion.
The basic usage is:
var ColorGamut g :> color_gamut "rgb"
'simulate' finds the CIE XYZ color that will result from the various selected components values in the specified color model.
Common sense would be to convert from one gamut to the other (let's say from RGB to printer CMYK) through calling RGB 'simulate' method, then calling CMYK 'formulate' method. Anyway, it does not always produce the optimum result, and 'formulate' is quite slow, so that direct conversion using several cache levels is generally preferred. Here is how to do it:
var ColorGamut sg :> color_gamut "rgb"
'speedup' builds and returns an object that contains or maps various optimization caches.
- to be added: how the conversion machinery works - yuck -
Handling color spectrum
Starting from here, I'm referring, probably often with wrong wording, to a lot of very technical notions about color. Please search for external documents about various CIE color models if you need step by step introduction to CIE color models.
Color spectrum handling in defined in module /pliant/graphic/color/spectrum.pli through two data types:
Here are some sample usage of ColorSpectrum:
var ColorSpectrum s
'get_measure' will return the sample with the wavelength matching best the specified one. On the other hand, the empty method used at the last line of this sample will return a value computed through interpolating the two measures with wavelengths surrounding the specified one.
And, now some usage of ColorSpectrum32:
var ColorSpectrum32 a := cast s ColorSpectrum32
For most operations such as plus, the implementation is to apply the specified operation on each measure.
'exposure' is an improved gamma function. It is the interpolation of the exponential curve between 0 and some point depending on the parameters.
'unexposure' is the exact opposite of 'exposure', so we have
Single color coding
This document is assuming you understand what sRGB, CIE XYZ, Lab and LCh color models are, if not, please ask Google to point you out Wikipedia articles.
Module /pliant/graphic/color/color.pli provides extra data types and functions to ease some conversions.
Available data types are:
For RGB, the red, green and blue coordinates are assumed to be the ones defined in sRGB standard.
Here is a sample usage of these data types and conversions:
var ColorXYZ c1 ; c1 X := 0.4 ; c1 Y := 0.6 ; c1 Z := 0.7
One very useful provided function is the color distance. The recommended one is implementing standard CMC 86 color distance:
var ColorXYZ c1 c2
'lab_distance' is also provided, but it matches human perceived color distances less accurately.
Then an extra ColorRGB888 data type and easy construction functions are provided in /pliant/graphic/color/rgb888
var ColorRGB888 c := color rgb 100 150 200
The color can also be specified in a cylindric representation of the sRGB color space:
var ColorRGB888 c := color hsl 60 30 80
The first parameter, hue, ranges from 0 to 360 where 0 means red, 60 means yellow, 120 means green, 240 means blue. The second, saturation ranges from 0 to 100 where 0 means grey, 100 means maximum saturation in the sRGB color space, and the third parameter, light, ranges from 0 to 100. What is special about Pliant hsl is that the four main colors, red, yellow, green and blue have light 50 and saturation 100, so the light is not much correlated to CIE Y light.
'color' instuction accepts two more ways to specify an sRGB color:
var ColorRGB888 c := color hexa "FF0000" # red
'ColorRGB888' data type also provide to and from string casting, so you can do:
if ("[dq]FF0000[dq]" parse (var ColorRGB888 c))
if ("[dq]#FF0000[dq]" parse (var ColorRGB888 c))
if ("[dq]color rgb 255 0 0[dq]" parse (var ColorRGB888 c))
if ("[dq]color hsl 0 100 50[dq]" parse (var ColorRGB888 c))
Please notice that to and from string casting is unconsistent. If you do:
var ColorRGB888 c
you get nothing. To get it working, you would have to do:
var ColorRGB888 c
var ColorRGB888 c
Transparent casting between ColorRGB888 and ColorRGB is also provided, so that you can write:
var ColorRGB c := color rgb 100 150 200
Please notice that ColorRGB is using linear encoding with components ranging from 0 to 1, whereas ColorRGB888 is using gamma 2.4 encoding as assumed in sRGB standard and integer components ranging from 0 to 255.
Module /pliant/graphic/color/adjust.pli provides a nice 'color_adjust' function that enables to easily apply changes to a color:
var ColorXYZ c
'exposure' does a bit the same as putting more or less light before taking the photo. Neutral value is 0, -0.25 means roughly half the amount of light, 0.25 twice more light, and 1 or 2 are fairly extreme values.
Assuming that a color component is always encoded on 8 bits in a bad idea.
Handling 1 bit per pixel black and white images as 8 bits images consumes too much memory for my application.
Providing color models that would not fit the byte boundary (such as 1 bit per pixel black and white color model) is just not possible because it would make the overall code too complicated.
As a summary, the solution here is not to extend the color models, but extend the in memory image encodings if necessary.
Some scanners and camera have 10 bits or 12 bits sensors. The 24 bits RGB encoding provided in Pliant graphic stack is not enough since it's only 8 bits per component. Why don't you provide 48 bits RGB (16 bits per component) ?
More than 8 bits per component is mostly of no use. I mean, if you take a 12 bits per component RGB image, then reduce to 8 bits per component using an algorithm with basic dithering capabilities, nobody will see the difference in the end. Even 6 bits per component is probably enough in most situations.
Looking at the code, I see an 'XYZ' gamut with 3 32 bits floating point components. What is it used for ?
I bet it's not used any more. Will probably be removed when I'm sure it's really not used.