Making colorschemes

Making new colorschemes

To make new ColorSchemes, you can quickly build arrays of colors; refer the ColorSchemes.jl documentation. You can also use the ColorSchemeTools function make_colorscheme(), and supply information about the color sequences you want.

The following formats are possible:

Linearly-segmented colors

A linearly-segmented color dictionary looks like this:

cdict = Dict(:red   => ((0.0,  0.0,  0.0),
                        (0.5,  1.0,  1.0),
                        (1.0,  1.0,  1.0)),
             :green => ((0.0,  0.0,  0.0),
                        (0.25, 0.0,  0.0),
                        (0.75, 1.0,  1.0),
                        (1.0,  1.0,  1.0)),
             :blue  => ((0.0,  0.0,  0.0),
                        (0.5,  0.0,  0.0),
                        (1.0,  1.0,  1.0)))

This specifies that red increases from 0 to 1 over the bottom half, green does the same over the middle half, and blue over the top half.

The triplets aren't RGB values... For each channel, the first number in each tuple are points on the 0 to 1 brightness scale, and should gradually increase. The second and third values determine the intensity values at that point.

The change of color between point p1 and p2 is defined by b and c:

:red => (
         ...,
         (p1, a, b),
         (p2, c, d),
         ...
         )

If a and b (or c and d) aren't the same, the color will abruptly jump. Notice that the very first a and the very last d aren't used.

To create a new ColorScheme from a suitable dictionary in this format, run make_colorscheme().

using Colors, ColorSchemes
scheme = make_colorscheme(dict)

By plotting the color components separately it's possible to see how the curves change. This diagram shows both the defined color levels as swatches along the top, and a continuously-sampled image below:

cdict = Dict(:red  => ((0.0,  0.0,  0.0),
                       (0.5,  1.0,  1.0),
                       (1.0,  1.0,  1.0)),
            :green => ((0.0,  0.0,  0.0),
                       (0.25, 0.0,  0.0),
                       (0.75, 1.0,  1.0),
                       (1.0,  1.0,  1.0)),
            :blue =>  ((0.0,  0.0,  0.0),
                       (0.5,  0.0,  0.0),
scheme = make_colorscheme(cdict)

"showing linear segmented colorscheme"

Indexed-list color schemes

The data to define an 'indexed list' color scheme looks like this:

terrain = (
           (0.00, (0.2, 0.2,  0.6)),
           (0.15, (0.0, 0.6,  1.0)),
           (0.25, (0.0, 0.8,  0.4)),
           (0.50, (1.0, 1.0,  0.6)),
           (0.75, (0.5, 0.36, 0.33)),
           (1.00, (1.0, 1.0,  1.0))
          )

The first item of each element is the location between 0 and 1, the second specifies the RGB values at that point.

The make_colorscheme(indexedlist) function makes a new ColorScheme from such an indexed list.

For example:

terrain_data = (
        (0.00, (0.2, 0.2, 0.6)),
        (0.15, (0.0, 0.6, 1.0)),
        (0.25, (0.0, 0.8, 0.4)),
        (0.50, (1.0, 1.0, 0.6)),
        (0.75, (0.5, 0.36, 0.33)),
        (1.00, (1.0, 1.0, 1.0)))
terrain = make_colorscheme(terrain_data, length = 50)

"indexed lists scheme"

Functional color schemes

The colors in a 'functional' color scheme are produced by three functions that calculate the color values at each point on the scheme.

The make_colorscheme() function applies the first supplied function at each point on the colorscheme for the red values, the second function for the green values, and the third for the blue. You can use defined functions or supply anonymous ones.

Values produced by the functions are clamped to 0.0 and 1.0 before they're converted to RGB values.

Examples

The first example returns a smooth black to white gradient, because the identity() function gives back as good as it gets.

fscheme = make_colorscheme(identity, identity, identity)

"functional color schemes"

The next example uses the sin() function on values from 0 to π to control the red, and the cos() function from 0 to π to control the blue. The green channel is flat-lined.

fscheme = make_colorscheme(n -> sin(n*π), n -> 0, n -> cos(n*π))

"functional color schemes"

You can generate stepped gradients by controlling the numbers. Here, each point on the scheme is nudged to the nearest multiple of 0.1.

fscheme = make_colorscheme(
        n -> round(n, digits=1),
        n -> round(n, digits=1),
        n -> round(n, digits=1), length=10)

"functional color schemes"

The next example sinusoidally sends the red channel from black to red and back again.

fscheme = make_colorscheme(n -> sin(n * π), n -> 0, n -> 0)

"functional color schemes"

The next example produces a striped colorscheme as the rippling sine waves continually change phase:

ripple7(n)  = sin(π * 7n)
ripple13(n) = sin(π * 13n)
ripple17(n) = sin(π * 17n)
fscheme = make_colorscheme(ripple7, ripple13, ripple17, length=80)

"functional color schemes"

If you're creating a scheme by generating LCHab colors, your functions should convert values between 0 and 1 to values between 0 and 100 (luminance and chroma) or 0 to 360 (hue).

f1(n) = 180 + 180sin(2π * n)
f2(n) = 50 + 20(0.5 - abs(n - 0.5))
fscheme = make_colorscheme(n -> 50, f2, f1,
    length=80,
    model=:LCHab)

"functional color schemes"

make_colorscheme(dict;
    length=100,
    category="",
    notes="")

Make a new ColorScheme from a dictionary of linear-segment information. Calls get_linear_segment_color(dict, n) with n for every length value between 0 and 1.

make_colorscheme(indexedlist;
    length=100,
    category="",
    notes="")

Make a ColorScheme using an 'indexed list' like this:

gist_rainbow = (
       (0.000, (1.00, 0.00, 0.16)),
       (0.030, (1.00, 0.00, 0.00)),
       (0.215, (1.00, 1.00, 0.00)),
       (0.400, (0.00, 1.00, 0.00)),
       (0.586, (0.00, 1.00, 1.00)),
       (0.770, (0.00, 0.00, 1.00)),
       (0.954, (1.00, 0.00, 1.00)),
       (1.000, (1.00, 0.00, 0.75))
)

make_colorscheme(gist_rainbow)

The first element of each item is the point on the color scheme.

make_colorscheme_new(f1::Function, f2::Function, f3::Function;
    model    = :RGB,
    length   = 100,
    category = "",
    notes    = "functional ColorScheme")

Make a ColorScheme using functions. Each function should take a value between 0 and 1 and return for that color component at each point on the ColorScheme, depending on the color model.

The default color model is :RGB, and the functions should return values in the appropriate range:

  • f1 - [0.0 - 1.0] - red
  • f2 - [0.0 - 1.0] - green
  • f3 - [0.0 - 1.0] - blue

For the :HSV color model:

  • f1 - [0.0 - 360.0] - hue
  • f2 - [0.0 - 1.0] - saturataion
  • f3 - [0.0 - 1.0] - value (brightness)

For the :LCHab color model:

  • f1 - [0.0 - 100.0] - luminance
  • f2 - [0.0 - 100.0] - chroma
  • f3 - [0.0 - 360.0] - hue
get_linear_segment_color(dict, n)

Get the RGB color for value n from a dictionary of linear color segments.

This following is a dictionary where red increases from 0 to 1 over the bottom half, green does the same over the middle half, and blue over the top half:

cdict = Dict(:red  => ((0.0,  0.0,  0.0),
                       (0.5,  1.0,  1.0),
                       (1.0,  1.0,  1.0)),
            :green => ((0.0,  0.0,  0.0),
                       (0.25, 0.0,  0.0),
                       (0.75, 1.0,  1.0),
                       (1.0,  1.0,  1.0)),
            :blue =>  ((0.0,  0.0,  0.0),
                       (0.5,  0.0,  0.0),
                       (1.0,  1.0,  1.0)))

The value of RGB component at every value of n is defined by a set of tuples. In each tuple, the first number is x. Colors are linearly interpolated in bands between consecutive values of x; if the first tuple is given by (Z, A, B) and the second tuple by (X, C, D), the color of a point n between Z and X will be given by (n - Z) / (X - Z) * (C - B) + B.

For example, given an entry like this:

:red  => ((0.0, 0.0, 0.0),
          (0.5, 1.0, 1.0),
          (1.0, 1.0, 1.0))

and if n = 0.75, we return 1.0; 0.75 is between the second and third segments, but we'd already reached 1.0 (segment 2) when n was 0.5.