Tutorial 12: Color Lookup Tables
In this tutorial we will explore how to use color lookup tables to remap data inside a Jitter matrix. The jit.charmap object is a powerful tool for this. We'll also look at different strategies for generating matrices to use as lookup tables, including the jit.gradient object.
The lookup process
Lookup tables (which are often called transfer functions) are arrays of numbers where an input number is used as an index in the table. The number stored at that index (or address, or position) is then retreived to replace the original number. Any function—a graph where each x value (address) has a corresponding y value (output)—can be used as a lookup table. Max objects such as funbuff, table, and the MSP buffer~ object are common candidates for use as lookup tables. In this tutorial, we'll be using Jitter matrices in much the same way.
The top left of the patch shows a jit.movie object in which you can read two different files. The object is initialized (via loadbang) with the file colorwheel.pct loaded into it. You can also load in the movie rain.mov by clicking on the message box that says . You should feel free to alternate between the two image sources throughout the tutorial.
The bottom of the patch contains a jit.charmap object, which we will use in this tutorial to remap cell values in the image. The object has two inlets, the left one of which is connected to our jit.movie object at the top of the patch. The right inlet is connected to a jit.matrix named . The matrix is one-dimensional, with four planes of char and a width of cells. This is the lookup table that jit.charmap uses to remap the color values of the cells in the lefthand matrix. The receive object (abbreviated r) with the name receives data from elsewhere in the patch and sends it to the jit.matrix. For example, turning on the toggle box at the top of the patch will send a to the jit.matrix, causing it to send its matrix message to both the jit.pwindow and jit.charmap.
The jit.charmap object builds its output matrix by using the values in the input (left) matrix to point to positions in the (right) matrix and copying the value found there. For example, assume that the matrix we send to jit.charmap contains a cell with the values in its four planes. If our lookup table has the value at cell in the first plane, at cell in the second plane, at cell in the third plane, and at cell in the fourth plane, our output cell will contain the values .
Lookup tables for jit.charmap should be one-dimensional matrices of cells with the same number of planes as the matrix you want to remap. This is because the possible range of values for char matrices is 0-255, so 256 numbers are need to cover the full range of the lookup table.
Generating the Lookup Table
The upper-right side of the tutorial patch contains three multislider objects that let you design the transfer functions for planes 1-3 of the lookup table matrix :
The multislider objects (which have 256 integer sliders in the range 0-255) send their lists to the jit.fill objects below them. These objects replace the values currently stored in planes 1-3 (i.e. Red, Green, and Blue) of the matrix with the values from the multislider objects. (See Tutorial 11.) When the matrix has been edited with the new values, the jit.fill objects send out a , which we send to the jit.matrix on the right of the patch that connects to the right inlet of jit.charmap. We're ignoring plane 0 in this tutorial because it only contains Alpha values when we treat 4-plane Jitter matrices as video.
The jit.matrix and jit.fill objects in our patch share the same ( ). As a result, the two objects read from and write to the same matrix, allowing one object (jit.fill) to generate data that the other object (jit.matrix) can read from without having to copy data between two separate matrices. This is similar to how many MSP objects (e.g. peek~, play~, groove~) can share sample data stored in a single buffer~. See Tutorials 11, 16, and 17 for more information on using named matrices.
If you want to reset any of the planes to a y="x" transfer function (i.e. a straight ascending line that leaves all the values unchanged), you can click the button object above the relevant multislider object. The subpatchers called p initialize the multislider objects with an uzi.
Here are some lookup tables and their results:
In the first example, the red and blue transfer functions are approximately inverted while the green is normal. The result is that high values of red and blue in the input image yield low values on the output, and vice versa. This is why the white background of the color wheel now looks green (the cell values ofhave been mapped to ).
The second example has the green plane completely zeroed (the transfer function set to 0 across the entire span of input values). The red and blue planes are also set to 0 up to a threshold, at which point they ramp up suddenly (the red more suddenly than the blue). As a result the majority of the colorwheel is black (especially in the 'green' area). The red plane only becomes visible in very high values (i.e. the magenta in the background of the color wheel).
The third example has the red plane mapped normally. The green plane has a parabolic shape to it, where the extreme values are mapped high and the medium shades are mapped low. The blue plane is normal except for a range in the midtones, where it is zeroed. This nonlinearity is visible as a red 'fault line' across the top and down the right side of the colorwheel.
As you can see, there are infinite ways to remap the cell values of these matrices. We'll now investigate another object, which lets us remap the color values in a more precise manner.
The jit.gradient object
This subpatch shows a method for generating lookup tables using the jit.gradient object:
The jit.gradient object generates single dimension char matrices that transition smoothly between two specified cell values. The and attributes are lists that specify these cell values. For example, a attribute of and an attribute of will generate a gradient that goes from black (at cell in the matrix) to pale green (at the last cell in the matrix). We've given our jit.gradient object the relevant arguments to make it cells wide, so that it can be stored in our jit.matrix when it changes. Note that jit.gradient takes floating point numbers in its attribute lists to specify char values (i.e. a value of in the attribute specifies a char value of ).
The attributes are formatted by taking the RGB list output of the Max swatch objects and converting them to ARGB floats. After the attribute has been sent to the jit.gradient object, the object is given a from the trigger object to cause it to output its matrix into the jit.matrix object on the left of the patch.
The first example shows an inverted image. The start of the lookup table is) and the end of the lookup table is black ( ). As a result, input values of are mapped to , and vice versa (y=255-x).
The second and third examples show duotone gradients that remap the color wheel's spectrum to between red and orange (example 2) and olive and cyan (example 3). Notice how, depending on the original colors at different points in the color wheel, the gradient curve becomes steeper or more gradual.
The third attribute of the jit.gradient object is the attribute, which specifies a curve to follow when morphing between the and values in the matrix. The attribute takes a list of floating point numbers as arguments. These arguments are the amplitudes of different orders of Chebyshev polynomials (see below). These special function curves create different effects when used in lookup tables. The multislider in the tutorial patch that sets the attribute lets you specify the relative amplitude of the first eight Chebyshev polynomial curves, which have the following shapes (if you view them as progressing from black to white):
When you use the jit.gradient object, you can get some very interesting color warping effects even if you leave the and points of the gradient at black and white. Here are some examples with our movie clip rain.mov:attribute in the
The lefthand image shows an unprocessed still image from the rain movie. The middle image shows what happens to the color spectrum when the gradient is generated using a second order Chebyshev polynomial (the darkest area in the image is now in the middle of the color spectrum). The righthand image shows a more complex gradient, where the color spectrum shows numerous peaks and troughs.
You can map cell values in char Jitter matrices using the jit.charmap object. The right inlet of jit.charmap takes a -cell matrix that defines the lookup table (or transfer function) to be applied to the incoming matrix data. You can define the lookup table using several strategies, including using jit.fill to generate the matrix from Max lists, or using the jit.gradient object to generate color gradients between a and cell value according to a curve shape specified by the attribute.
|jit.charmap||Map 256-point input to output|
|jit.fill||Fill a matrix with a list|
|jit.gradient||Generate Chebyshev gradients|
|jit.matrix||The Jitter Matrix!|
|jit.pwindow||Display Jitter data and images|
|jit.movie||Play a QuickTime movie|
|metro||Output a bang message at regular intervals|
|multislider||Display data as sliders or a scrolling display|
|swatch||Choose a color|
|uzi||Send many bang messages|
|vexpr||Evaluate a math expression for a list|