Spectra Walkthrough

This notebook provides basic documentation of the spectra Python library, which aims to simplify the process of creating color scales and converting colors from one "color space" to another.

In [1]:
import spectra

Note: To visually display the colors we create, let's define and use this swatches function:

In [2]:
from IPython.display import HTML
In [3]:
swatch_template = """
<div style="float: left;">
    <div style="width: 50px; height: 50px; background: {0};"></div>
    <div>{0}</div>
</div>
"""
swatch_outer = """
<div style='width: 500px; overflow: auto; font-size: 10px; 
    font-weight: bold; text-align: center; line-height: 1.5;'>{0}</div>
"""
In [4]:
def swatches(colors):
    hexes = (c.hexcode.upper() for c in colors)
    html =  swatch_outer.format("".join(map(swatch_template.format, hexes)))
    return HTML(html)

Creating Colors

The easiest way to create a color is to use these shortcuts, one for each "color space" spectra supports:

  • spectra.rgb(r, g, b)
  • spectra.hsl(h, s, l)
  • spectra.hsv(h, s, v)
  • spectra.lab(l, a, b)
  • spectra.lch(l, c, h)
  • spectra.cmy(c, m, y)
  • spectra.cmyk(c, m, y, k)
  • spectra.xyz(x, y, z)

You can also pass a WC3 color name (e.g., "papayawhip") or hexcode (e.g., "#fefefe") to spectra.html(color), which will create the corresponding rgb color.

For example:

In [5]:
swatches([ spectra.html("tomato") ])
Out[5]:
#FF6347
In [6]:
swatches([ spectra.rgb(1, 0.39, 0.28) ])
Out[6]:
#FF6347

Getting Color Values

Instances of spectra.Color have four main properties:

  • .values: An array representation of the color's values in its own color space, e.g. (L, a, b) for an lab color.
  • .hexcode: The hex encoding of this color, e.g. #ffffff for rgb(255, 255, 255)/html("white").
  • .rgb: The (r, g, b) values for this color in the rgb color space; these are allowed to go out of gamut.
  • .clamped_rgb: The "clamped" (r, g, b) values for this color in the rgb color space.

Note on .rgb and .rgb_clamped: Spectra follows colormath's convention:

RGB spaces tend to have a smaller gamut than some of the CIE color spaces. When converting to RGB, this can cause some of the coordinates to end up being out of the acceptable range (0.0-1.0 or 1-255, depending on whether your RGB color is upscaled). [...] Rather than clamp these for you, we leave them as-is.

In [7]:
tomato = spectra.lab(62.28, 57.67, 46.29)
In [8]:
tomato.values
Out[8]:
(62.28, 57.67, 46.29)
In [9]:
tomato.rgb
Out[9]:
(0.9816143351072876, 0.38953937767066, 0.28259874509294824)
In [10]:
tomato.clamped_rgb
Out[10]:
(0.9816143351072876, 0.38953937767066, 0.28259874509294824)
In [11]:
tomato.hexcode
Out[11]:
'#fa6348'

Converting Colors

Any spectra.Color can be converted to any supported color space, using the .to(colorspace) method. E.g.,:

In [12]:
tomato.to("lch").values
Out[12]:
(62.28, 73.94993576738251, 38.75297991115577)

Color Operations

The following spectra.Color methods return new colors:

  • .blend(other_color, ratio=0.5)
  • .brighten(amount=10)
  • .darken(amount=10)
  • .saturate(amount=10)
  • .desaturate(amount=10)

The parameter for .brighten/.darken is a positive/negative linear adjustment to the L(ightness) value of the color's Lab representation. (Spectra converts the color to Lab, makes the change, and then converts back to the original color space.)

Likewise, the parameter for .saturate/.desaturate is a positive/negative linear adjustment to the c(hroma) value of the color's Lch representation.

In [13]:
yellow = spectra.html("yellow").to("lab")
In [14]:
tomato.blend(yellow, 0.25).hexcode
Out[14]:
'#fd9042'
In [15]:
swatches([
    tomato,
    tomato.blend(yellow, 0.25),
    tomato.blend(yellow, 0.75),
    yellow
])
Out[15]:
#FA6348
#FD9042
#FADD24
#F3FF00
In [16]:
swatches([
    tomato.brighten(30),
    tomato,
    tomato.darken(30)
])
Out[16]:
#FFB894
#FA6348
#9D0003
In [17]:
swatches([
    tomato.saturate(40),
    tomato,
    tomato.desaturate(40)
])
Out[17]:
#FF0F19
#FA6348
#CA8472

Color Scales

Color scales translate numbers into colors, based on a set of colors and a domain (default: 0->1).

In [18]:
start = spectra.html("#21313E")
end = spectra.html("#EFEE69")
swatches([ start, end ])
Out[18]:
#21313E
#EFEE69
In [19]:
scale = spectra.scale([ start, end ])
In [20]:
scale(0.5)
Out[20]:
<spectra.core.Color at 0x1099ab650>
In [21]:
scale(0.5).hexcode
Out[21]:
'#889054'
In [22]:
swatches([
    scale(0),
    scale(0.5),
    scale(1)
])
Out[22]:
#21313E
#889054
#EFEE69

To set a custom domain, call .domain([ start_num, end_num ]):

In [23]:
ten_twenty_scale = scale.domain([ 10, 20 ])
In [24]:
swatches([
    ten_twenty_scale(10),
    ten_twenty_scale(15),
    ten_twenty_scale(20)
])
Out[24]:
#21313E
#889054
#EFEE69

The .range(count) method produces an evenly-spaced list of colors:

In [25]:
my_range = ten_twenty_scale.range(10)
In [26]:
[ x.hexcode for x in my_range ]
Out[26]:
['#21313e',
 '#384643',
 '#4f5b48',
 '#66704c',
 '#7d8551',
 '#939a56',
 '#aaaf5b',
 '#c1c45f',
 '#d8d964',
 '#efee69']
In [27]:
swatches(my_range)
Out[27]:
#21313E
#384643
#4F5B48
#66704C
#7D8551
#939A56
#AAAF5B
#C1C45F
#D8D964
#EFEE69

spectra.range(colors, count) provides a shortcut to the same results:

In [28]:
swatches(spectra.range([ start, end ], 10))
Out[28]:
#21313E
#384643
#4F5B48
#66704C
#7D8551
#939A56
#AAAF5B
#C1C45F
#D8D964
#EFEE69

You can also pass plain hexcode or web-color strings to range and scale:

In [29]:
swatches(spectra.range([ "#21313E", "#EFEE69" ], 10))
Out[29]:
#21313E
#384643
#4F5B48
#66704C
#7D8551
#939A56
#AAAF5B
#C1C45F
#D8D964
#EFEE69

The colors produced by scales and ranges depend on the color space you're using. You can change the color space by calling .colorspace(space). To wit:

In [30]:
ranges_html = ""
for space in sorted(spectra.COLOR_SPACES.keys()):
    converted_scale = scale.colorspace(space)
    ranges_html += "<div style='margin-top: 0.5em;'>" + space + "</div>"
    ranges_html += swatches(converted_scale.range(10)).data
HTML(ranges_html)
Out[30]:
cmy
#21313E
#384643
#4F5B48
#66704C
#7D8551
#939A56
#AAAF5B
#C1C45F
#D8D964
#EFEE69
cmyk
#21313E
#30424D
#405559
#536862
#687C6A
#7F916E
#98A771
#B3BE71
#D0D56E
#EFEE69
hsl
#21313E
#274C53
#2C6A64
#2F8364
#319D57
#31B93C
#51D233
#8ADD43
#C0E755
#EFEE69
hsv
#21313E
#2B4B52
#346561
#3D7963
#458D5F
#4DA054
#67B455
#8EC85C
#BBDB63
#EFEE69
lab
#24313E
#394344
#4E564A
#626A4F
#777F54
#8C9459
#A2AA5D
#B8C161
#CFD864
#E6EF67
lch
#24313E
#244557
#1A5C6C
#04737B
#008B83
#22A284
#4DB97F
#7ACE76
#ADE06C
#E6EF67
rgb
#21313E
#384643
#4F5B48
#66704C
#7D8551
#939A56
#AAAF5B
#C1C45F
#D8D964
#EFEE69
xyz
#1A3148
#556351
#738159
#899860
#9BAB66
#ABBC6C
#BACB72
#C7D977
#D3E57C
#DEF181

(Credit to Gregor Aisch for that example.)

Polylinear Scales

You can construct a scale along any number of colors. Constructing a scale along three colors can be handy for divergent color schemes. For example, here's a scale that goes from red -> gray -> green instead of directly from red -> green:

In [31]:
red, gray, green = [ spectra.html(x).to("lab") for x in ("red", "#CCC", "green") ]
polylinear_scale = spectra.scale([ red, gray, green ])
swatches(polylinear_scale.range(9))
Out[31]:
#FA0007
#F85B3C
#F1866B
#E3AA9A
#CCCCCC
#A2BA9C
#76A86D
#46953E
#008100

Note: If you want to customize a polylinear scale's domain, the domain must be the same length as the scale itself. For example:

In [32]:
polylinear_negpos = polylinear_scale.domain([ -1, 0, 1 ])
In [33]:
swatches([
    polylinear_negpos(-0.75),
    polylinear_negpos(0.2),
    polylinear_negpos(1)
])
Out[33]:
#F85B3C
#AABEA5
#008100

Feedback / Questions

Much appreciated! Please open an issue on the spectra GitHub page.