Colour tables
Colour table files allow the rules that map data values to colours to be stored and shared between applications. The format used here is that used by LandSerf and TreeMappa. It consists of a collection of colour rules each of which specifies a data value to colour mapping. Colour rules can be discrete indicating that only the specified data value should be mapped to a colour, or continuous indicating that any a range of data values can be mapped to colours. Unlike Processing's lerpColor(), colour tables allow you to interpolate across a set of many colours rather than between just two of them. A large number of preset colour tables can be created including all LandSerf and ColorBrewer schemes.
You will need to import the relevant colour classes into your sketch with the line (note the English spelling of 'colour'):
import org.gicentre.utils.colour.*;
There are a number of methods available for creating colour tables, adding colour rules and opening and saving to files. For a full documentation of the methods see the ColourTable API reference.
The following example shows how the more common methods might be used:
import org.gicentre.utils.colour.*; // For colour tables. // Sketch to demonstrate the use of colour tables. A ColourTable object // consists of a set of ColourRules. Each rule maps a numeric value to // a colour. The colour to be associated with any value can be found by // calling the findColour() method of a colour table. If the rules are // continuous, the returned colour is interpolated between the two // colour rules closest to the given value. If rules are discrete, only // exact matches are mapped to a colour. // Version 1.2, 4th November, 2013. // Author Jo Wood. // ------------------ Object variables -------------------- ColourTable cTable1, cTable2, cTable3; // Colour tables to use. // ------------------- Initialisation --------------------- /** Creates the colour tables to display and save as files. */ void setup() { size(500,250); // Create a continuous Brewer colour table (YlOrBr6). cTable1 = new ColourTable(); cTable1.addContinuousColourRule(0.5/6, 255,255,212); cTable1.addContinuousColourRule(1.5/6, 254,227,145); cTable1.addContinuousColourRule(2.5/6, 254,196, 79); cTable1.addContinuousColourRule(3.5/6, 254,153, 41); cTable1.addContinuousColourRule(4.5/6, 217, 95, 14); cTable1.addContinuousColourRule(5.5/6, 153, 52, 4); // Create a preset colour table and save it as a file cTable2 = ColourTable.getPresetColourTable(ColourTable.IMHOF_L3,0,1); ColourTable.writeFile(cTable2,createOutput("imhofLand3.ctb")); // Read in a colour table from a ctb file. cTable3 = ColourTable.readFile(createInput("imhofLand3.ctb")); } // ------------------ Processing draw -------------------- /** Draws the colour tables as horizontal colour bars. */ void draw() { background(255); // Draw the continuous Brewer colour table. float inc = 0.001; for (float i=0; i<1; i+=inc) { fill(cTable1.findColour(i)); stroke(cTable1.findColour(i)); rect(width*i,10,width*inc,50); } // Draw the discrete version of the Brewer colour table. stroke(0,150); inc = 1/6.0; for (float i=0; i<1; i+=inc) { fill(cTable1.findColour(i + 0.5*inc)); rect(width*i,70,width*inc,50,2); } // Draw the preset colour table. inc = 0.001; for (float i=0; i<1; i+=inc) { fill(cTable2.findColour(i)); stroke(cTable2.findColour(i)); rect(width*i,130,width*inc,50); } // Draw the colour table loaded from a file. inc = 0.001; for (float i=0; i<1; i+=inc) { fill(cTable3.findColour(i)); stroke(cTable3.findColour(i)); rect(width*i,190,width*inc,50); } }
Preset colour tables
A large number of preset colour schemes can be created easily by calling the ColourTable.getPresetColourTable() method. This includes all ColorBrewer and LandSerf schemes. ColorBrewer schemes are particularly useful for showing statistical data that are sequentially continuous, diverging or categorical. For a full list of ColorBrewer schemes, see the ColourTable API. The available schemes and their names are shown below, so for example to create a sequential purple-red scheme to be scaled between data values of 0 and 10, you would include a line such as:
myCTable = ColourTable.getPresetColourTable(ColourTable.PU_RD,0,10);
CIELab colour space
CIELab colour space is an approximately perceptually uniform space in that any two pairs of colours a fixed distance apart in CIELab space are equally distinguishable regardless of their location. The class CIELab in the colour package can convert to and from this space using the methods getColour() and getLab(). In the example below, that conversion is done with the line converter.getColour(L,a,b,false). Because not all CIELab values result in a visible colour (so-called 'out-of-gamut' colours), the final parameter of this method allows the option of extrapolating colours that fall outside of gamaut to their nearest visible colour. If false, out-of-gamut colours are returned as null objects, if true, the method guarantees that a visible colour is always returned, regardless of L,a,b values.
Note that in processing, to convert a Java Color object into a valid Processing colour value, call its getRGB() method (see the example sketch below).
import org.gicentre.utils.colour.*; // For giCentre Utils colour import java.awt.Color; // For Java's color class. // Sketch to demonstrate the use of CIELab colour conversion. // Use up and down arrows to change the CIELAB 'L' value. // Version 1.1, 4th November, 2013 // Author Jo Wood. CIELab converter; // Does the colour conversion work. float L; // Current L value for CIELab slice void setup() { size(400,400); converter = new CIELab(); L = 50; } void draw() { background(255); noStroke(); noLoop(); // Transform from colour space to screen space scale(width/200.0,-height/200.0); translate(100,-100); // Draw a slice through CIELab space. float inc = 1.0; for (float a=-100; a <=100; a+=inc) { for (float b=-100; b<=100; b+=inc) { Color colour = converter.getColour(L,a,b,false); if (colour != null) { fill(colour.getRGB()); rect(a,b,inc,inc); } } } // Draw axes stroke(0,50); strokeWeight(0.5); line(0,-100,0,100); line(-100,0,100,0); } void keyPressed() { if (key==CODED) { if ((keyCode==UP) && (L<100)) { L++; println("L="+L); loop(); } else if ((keyCode==DOWN) && (L>0)) { L--; println("L="+L); loop(); } } }