View Source Ultraviolet (Ultraviolet v0.1.1)

Ultraviolet is a color manipulation library designed to work like chroma-js. It may not have full parity with chroma-js, but it includes most of the common operations and features.

The first step to get your color into Ultraviolet is to create a Ultraviolet.Color. This can be done through new/1, new/2, or the constructors for each supported color space: hsl/2 for HSL, hsv/2 for HSV, etc.

Getting Started

Here are a few things Ultraviolet can do:

  • read colors from a wide range of inputs
  • analyze and manipulate colors
  • convert colors into a wide range of formats
  • linear, bezier, and custom interpolation in different color spaces

Here's an example of a simple read / manipulate / output chain:

iex>{:ok, color} = Ultraviolet.new("pink");
iex>color |> Ultraviolet.Color.darken!() |> Ultraviolet.Color.saturate!(2) |> Ultraviolet.Color.hex()
"#ff6d93"

Aside from that, Ultraviolet can help you generate nice colors using various methods. These colors can be used, for example, as a color palette for maps or data visualization.

iex>{:ok, scale} = Ultraviolet.scale(["#fafa6e", "#2a4858"], space: :lch);
iex>Enum.map(Ultraviolet.Scale.take(scale, 6), &Ultraviolet.Color.hex/1)
["#fafa6e", "#bed869", "#6aba78", "#00969d", "#1b6c85", "#2a4858"]

The rest of this module's documentation has more examples.

Summary

Functions

Similar to mix/3, but accepts more than two colors. Simple averaging of the R,G,B components and the alpha channel.

Blends two colors using RGB channel-wise blend functions.

Generates the sRGB representation of a Ultraviolet.Color.LCH.

Generates the sRGB representation of a Ultraviolet.Color.HSL.

Generates the sRGB representation of a Ultraviolet.Color.HSV.

Generates the sRGB representation of a Ultraviolet.Color.Lab

Generates the sRGB representation of a Ultraviolet.Color.LCH.

Mixes two colors. the mix ratio is a value between 0 and 1

Creates a new Ultraviolet.Color from the given input.

Creates a new Ultraviolet.Color from the given input and options.

Generates the sRGB representation of an Ultraviolet.Color.OKLab.

Generates the sRGB representation of a Ultraviolet.Color.OKLCH.

Create a new color Scale from "white" to "black" with default options.

Create a new color Scale with the given colors and default options.

Creates a new color Scale with the given colors and options.

Converts a temperature into a color, based on the color temperature scale.

Functions

Link to this function

average(colors, options \\ [])

View Source
@spec average([Ultraviolet.Color.input()], list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Similar to mix/3, but accepts more than two colors. Simple averaging of the R,G,B components and the alpha channel.

Examples

iex> colors = ["ddd", "yellow", "red", "teal"];
iex>{:ok, color} = Ultraviolet.average(colors);
iex>Ultraviolet.Color.hex(color)
"#d3b480"
iex>{:ok, color} = Ultraviolet.average(colors, space: :rgb);
iex>Ultraviolet.Color.hex(color)
"#b79757"
iex>{:ok, color} = Ultraviolet.average(colors, space: :lab);
iex>Ultraviolet.Color.hex(color)
"#d3a96a"
iex>{:ok, color} = Ultraviolet.average(colors, space: :lch);
iex>Ultraviolet.Color.hex(color)
"#ef9e4e"

Also works with alpha channels:

iex>{:ok, color} = Ultraviolet.average(["red", %Ultraviolet.Color{r: 0, g: 0, b: 0, a: 0.0}])
iex>Ultraviolet.Color.hex(color)
"#b4000080"

You can also provide an array of weights to compute a weighted average:

iex> colors = ["ddd", "yellow", "red", "teal"];
iex>{:ok, color} = Ultraviolet.average(colors, space: :lch, weights: [1, 1, 2, 1]);
iex>Ultraviolet.Color.hex(color)
"#f98841"
iex>{:ok, color} = Ultraviolet.average(colors, space: :lch, weights: [1.5, 0.5, 1, 2.3]);
iex>Ultraviolet.Color.hex(color)
"#ae9e52"
Link to this function

blend(color, mask, mode)

View Source

Blends two colors using RGB channel-wise blend functions.

Valid Blend Modes

  • :multiply
  • :darken
  • :lighten
  • :screen
  • :overlay
  • :burn
  • :dodge

Examples

iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :multiply);
iex>Ultraviolet.Color.hex(color)
"#47af22"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :darken);
iex>Ultraviolet.Color.hex(color)
"#4cbb22"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :lighten);
iex>Ultraviolet.Color.hex(color)
"#eeeefc"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :screen);
iex>Ultraviolet.Color.hex(color)
"#f3fafc"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :overlay);
iex>Ultraviolet.Color.hex(color)
"#e7f643"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :burn);
iex>Ultraviolet.Color.hex(color)
"#c6e81f"
iex>{:ok, color} = Ultraviolet.blend("4cbbfc", "eeee22", :dodge);
iex>Ultraviolet.Color.hex(color)
"#ffffff"
Link to this function

hcl(channels, options \\ [])

View Source
@spec hcl(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.LCH.

This is the same as lch/2, but with the channel order of the first argument reversed.

Example

iex>Ultraviolet.hcl({130, 40, 80})
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 1.0}}
Link to this function

hsl(channels, options \\ [])

View Source
@spec hsl(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.HSL.

Examples

iex>Ultraviolet.hsl({330, 0.5, 1})
{:ok, %Ultraviolet.Color{r: 255, g: 255, b: 255, a: 1.0}}
iex>Ultraviolet.hsl([330, 0.5, 1, 0.5], round: 1)
{:ok, %Ultraviolet.Color{r: 255.0, g: 255.0, b: 255.0, a: 0.5}}
Link to this function

hsv(channels, options \\ [])

View Source
@spec hsv(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.HSV.

Examples

iex>Ultraviolet.hsv([330, 0.5, 1])
{:ok, %Ultraviolet.Color{r: 255, g: 255, b: 255, a: 1.0}}
iex>Ultraviolet.hsv({330, 0.5, 1, 0.5}, round: 1)
{:ok, %Ultraviolet.Color{r: 255.0, g: 255.0, b: 255.0, a: 0.5}}
Link to this function

lab(channels, options \\ [])

View Source
@spec lab(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.Lab

Colorspace Options

Examples

iex>Ultraviolet.lab({50, -20, 50})
{:ok, %Ultraviolet.Color{r: 110, g: 127, b: 21, a: 1.0}}
iex>Ultraviolet.lab([80, -20, 50], round: 1)
{:ok, %Ultraviolet.Color{r: 192.3, g: 206.7, b: 101.7, a: 1.0}}
iex>Ultraviolet.lab([80, -20, 50, 0.5], reference: :d50)
{:ok, %Ultraviolet.Color{r: 184, g: 208, b: 100, a: 0.5}}
Link to this function

lch(channels, options \\ [])

View Source
@spec lch(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.LCH.

Colorspace Options

Example

iex>Ultraviolet.lch({80, 40, 130})
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 1.0}}
Link to this function

mix(color, target, options \\ [])

View Source
@spec mix(Ultraviolet.Color.input(), Ultraviolet.Color.input(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Mixes two colors. the mix ratio is a value between 0 and 1

Examples

iex>{:ok, mixed} = Ultraviolet.mix("red", "blue");
iex>Ultraviolet.Color.hex(mixed)
"#b400b4"
iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.25);
iex>Ultraviolet.Color.hex(mixed)
"#dd0080"
iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.75);
iex>Ultraviolet.Color.hex(mixed)
"#8000dd"

The color mixing produces different results based on the color space used for interpolation (default: :lrgb).

iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :rgb);
iex>Ultraviolet.Color.hex(mixed)
"#800080"
iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :hsl);
iex>Ultraviolet.Color.hex(mixed)
"#ff00ff"
iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :lab);
iex>Ultraviolet.Color.hex(mixed)
"#ca0089"
iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :lch);
iex>Ultraviolet.Color.hex(mixed)
"#fa0080"

Available Spaces

  • :lrgb (Linear RGB)
  • :rgb
  • :hsv
  • :hsl
  • :lch and :oklch
  • :hcl
  • :lab and :oklab

Long Hue Interpolation

Hue-based color spaces will take the "short" route between hues by default. If you want your mixture to take the long route instead, set the longer? option to true.

iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :hsv, longer?: true);
iex>Ultraviolet.Color.hex(mixed)
"#00ff00"

Keep in mind this option only applies to color spaces with a Hue channel. If you want to use another color space, longer? will have no effect:

iex>{:ok, mixed} = Ultraviolet.mix("red", "blue", ratio: 0.5, space: :lab, longer?: true);
iex>Ultraviolet.Color.hex(mixed)
"#ca0089"
@spec new(Ultraviolet.Color.input()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Creates a new Ultraviolet.Color from the given input.

Examples

new/1 supports a wide variety of inputs:

Named colors

All named colors as defined by the W3CX11 specification are supported:

iex>Ultraviolet.new("hotpink")
{:ok, %Ultraviolet.Color{r: 255, g: 105, b: 180, a: 1.0}}

Hexadecimal Strings

If there's no matching named color, check for a hexidecimal string. It ignores case, the # sign is optional, and it can recognize the shorter 3-letter format.

iex>Ultraviolet.new("#ff3399")
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new("F39")
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}

Hexadecimal Numbers

Any number between 0 and 16_777_215 will be recognized as a Color:

iex>Ultraviolet.new(0x000000)
{:ok, %Ultraviolet.Color{r: 0, g: 0, b: 0, a: 1.0}}
iex>Ultraviolet.new(0xff3399)
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}

Individual R, G, B, A

You can also pass RGBA channel values individually in an array or tuple, Each channel must be within 0..255.

iex>Ultraviolet.new({0xff, 0x33, 0x99})
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new({255, 51, 153})
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new([255, 51, 153])
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new([255, 51, 153, 0.5])
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 0.5}}
@spec new(Ultraviolet.Color.channels(), [...]) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Creates a new Ultraviolet.Color from the given input and options.

new/2 allows a bit more control over the colors you create. You can pass in colorspace-specific options, the colorspace you want to use, and other transformation-related options.

See the colorspace-specific constructors for more details about what options are available for each colorspace.

This also allows for different datatype options, e.g. simple lists and tuples, for the first argument.

Generic Options

These options are available to new/2 as well as all colorspace-specific constructors:

  • :round: an integer if rounding r, g, and b channel values to N decimal places is desired; if no rounding is desired, pass false. Default: 0

Examples

HSL

iex>Ultraviolet.new({330, 1, 0.6}, space: :hsl)
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new([330, 1, 0.6, 0.5], space: :hsl)
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 0.5}}

HSV / HSB

iex>Ultraviolet.new({330, 0.8, 1}, space: :hsv)
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 1.0}}
iex>Ultraviolet.new([330, 0.8, 1, 0.5], space: :hsv)
{:ok, %Ultraviolet.Color{r: 255, g: 51, b: 153, a: 0.5}}

Lab

iex>Ultraviolet.new({40, -20, 50}, space: :lab)
{:ok, %Ultraviolet.Color{r: 83, g: 102, b: 0, a: 1.0}}
iex>Ultraviolet.new([40, -20, 50, 0.5], space: :lab)
{:ok, %Ultraviolet.Color{r: 83, g: 102, b: 0, a: 0.5}}

LCH / HCL

iex>Ultraviolet.new({80, 40, 130}, space: :lch)
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 1.0}}
iex>Ultraviolet.new({130, 40, 80, 0.5}, space: :hcl)
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 0.5}}
iex>Ultraviolet.new([80, 40, 130], space: :lch)
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 1.0}}
iex>Ultraviolet.new([h: 130, c: 40, l: 80, a: 0.5], space: :hcl)
{:ok, %Ultraviolet.Color{r: 170, g: 210, b: 140, a: 0.5}}

OKLab

iex>Ultraviolet.new([0.4, -0.2, 0.5], space: :oklab)
{:ok, %Ultraviolet.Color{r: 98, g: 68, b: 0, a: 1.0}}
iex>Ultraviolet.new({0.4, -0.2, 0.5, 0.5}, space: :oklab)
{:ok, %Ultraviolet.Color{r: 98, g: 68, b: 0, a: 0.5}}

OKLCH

iex>Ultraviolet.new([0.5, 0.2, 240], space: :oklch)
{:ok, %Ultraviolet.Color{r: 0, g: 105, b: 199, a: 1.0}}
iex>Ultraviolet.new({0.5, 0.2, 240, 0.5}, space: :oklch)
{:ok, %Ultraviolet.Color{r: 0, g: 105, b: 199, a: 0.5}}
Link to this function

oklab(channels, options \\ [])

View Source
@spec oklab(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of an Ultraviolet.Color.OKLab.

Example

iex>Ultraviolet.oklab({0.5, -0.2, 0.5})
{:ok, %Ultraviolet.Color{r: 128, g: 97, b: 0, a: 1.0}}
iex>Ultraviolet.oklab([0.8, -0.2, 0.5])
{:ok, %Ultraviolet.Color{r: 217, g: 197, b: 0, a: 1.0}}
Link to this function

oklch(channels, options \\ [])

View Source
@spec oklch(Ultraviolet.Color.channels(), list()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Generates the sRGB representation of a Ultraviolet.Color.OKLCH.

Example

iex>Ultraviolet.oklch({0.8, 0.12, 60})
{:ok, %Ultraviolet.Color{r: 246, g: 171, b: 107, a: 1.0}}
@spec scale() :: {:ok, Ultraviolet.Scale.t()} | {:error, term()}

Create a new color Scale from "white" to "black" with default options.

See scale/2 for examples.

@spec scale([Ultraviolet.Color.input()] | String.t()) ::
  {:ok, Ultraviolet.Scale.t()} | {:error, term()}

Create a new color Scale with the given colors and default options.

See scale/2 for examples.

@spec scale(colors_or_palette :: [Ultraviolet.Color.input()] | String.t(), list()) ::
  {:ok, Ultraviolet.Scale.t()} | {:error, term()}

Creates a new color Scale with the given colors and options.

Color scales, created with scale/2, map numbers onto a color palette. Because they're basically lazy maps, they have similar access functions as maps, as well as some Enumerable:

To see other access functions, see the Ultraviolet.Scale documentation.

By default, a scale has the domain [0, 1] and a range of "white" to "black":

iex>{:ok, scale} = Ultraviolet.scale();
iex>{:ok, color} = Ultraviolet.Scale.fetch(scale, 0.25);
iex>Ultraviolet.Color.hex(color)
"#bfbfbf"

The first argument is an array of colors. Any color that can be read by Ultraviolet.new/1 works here too. If you pass more than two colors, they will be evenly distributed along the gradient.

iex>{:ok, scale} = Ultraviolet.scale(["yellow", "008ae5"]);
iex>Ultraviolet.Scale.take_keys(scale, [0, 1])
[
  %Ultraviolet.Color{r: 255, g: 255, b: 0},
  %Ultraviolet.Color{r: 0, g: 138, b: 229},
]
iex>{:ok, scale} = Ultraviolet.scale(["yellow", "red", "black"]);
iex>Ultraviolet.Scale.take_keys(scale, [0, 0.5, 1])
[
  %Ultraviolet.Color{r: 255, g: 255, b: 0},
  %Ultraviolet.Color{r: 255, g: 0, b: 0},
  %Ultraviolet.Color{r: 0, g: 0, b: 0},
]

Color Brewer

Ultraviolet includes the definitions from ColorBrewer as well.

iex>{:ok, scale} = Ultraviolet.scale("OrRd");
iex>Enum.map(Ultraviolet.Scale.take(scale, 5), &Ultraviolet.Color.hex/1)
["#fff7ec", "#fdd49e", "#fc8d59", "#d7301f", "#7f0000"]

You can reverse the colors by reversing the domain:

iex>{:ok, scale} = Ultraviolet.scale("YlGnBu", domain: [1, 0]);
iex>Ultraviolet.Scale.fetch(scale, 0.25)
{:ok, %Ultraviolet.Color{r: 34, g: 94, b: 168}}

Color Count

You can include a :count option when creating a ColorBrewer-based scale to retrieve the Color Brewer palette with the given number of colors. The default is 9.

iex>{:ok, scale} = Ultraviolet.scale("YlGnBu", count: 5);
iex> scale.colors
[
  %Ultraviolet.Color{r: 255, g: 255, b: 204, a: 1.0},
  %Ultraviolet.Color{r: 161, g: 218, b: 180, a: 1.0},
  %Ultraviolet.Color{r: 65, g: 182, b: 196, a: 1.0},
  %Ultraviolet.Color{r: 44, g: 127, b: 184, a: 1.0},
  %Ultraviolet.Color{r: 37, g: 52, b: 148, a: 1.0}
]

Options

Scales can be created with a number of options which affect the output colors:

Domain

You can change the input domain to match your use case. The default domain is [0, 1].

iex>{:ok, scale} = Ultraviolet.scale(["yellow", "008ae5"], domain: [0, 100]);
iex>Enum.map(Ultraviolet.Scale.take_keys(scale, [0, 100]), &Ultraviolet.Color.hex/1)
["#ffff00", "#008ae5"]

You can use this option to set the exact positions of each color:

iex>{:ok, scale} = Ultraviolet.scale(
...>  ["yellow", "lightgreen", "008ae5"],
...>  domain: [0, 0.25, 1]
...>);
iex>Enum.map(Ultraviolet.Scale.take_keys(scale, [0, 0.25, 1]), &Ultraviolet.Color.hex/1)
["#ffff00", "#90ee90", "#008ae5"]

Color Space

As with mix/3, the result of color interpolation will depend on the color space in which the channels are interpolated. The default :space is :rgb.

This default is okay, but sometimes, two-color RGB gradients go through a gray "dead zone", which...doesn't look great. Other color spaces can produce better results.

iex>{:ok, scale} = Ultraviolet.scale(["yellow", "navy"]);
iex>{:ok, color} = Ultraviolet.Scale.fetch(scale, 0.6);
iex>Ultraviolet.Color.hex(color)
# this is mostly gray
"#66664d"
iex>{:ok, scale} = Ultraviolet.scale(["yellow", "navy"], space: :lab);
iex>{:ok, color} = Ultraviolet.Scale.fetch(scale, 0.6);
iex>Ultraviolet.Color.hex(color)
# this is better
"#8e6271"

The available values for this option are the same as with mix/3.

Gamma Correction

:gamma can be used to "shift" a scale's center more towards the beginning (:gamma < 1) or the end (:gamma > 1). This option is typically used to "even out" the lightness gradient. The default gamma is 1.

iex>{:ok, _scale} = Ultraviolet.scale(["yellow", "green"], gamma: 0.5);
iex>{:ok, _scale} = Ultraviolet.scale(["yellow", "green"], gamma: 1);
iex>{:ok, _scale} = Ultraviolet.scale(["yellow", "green"], gamma: 2);

Lightness Correction

:correct_lightness? makes sure the lightness range is spread evenly across a color scale. This option is especially useful when working with multi-hue color scales. where simple gamma correction won't help very much. The default value is false, i.e. lightness correction turned off.

iex>{:ok, scale} = Ultraviolet.scale(
...>  ["black", "red", "yellow", "white"]
...>);
iex>{:ok, color} = Ultraviolet.Scale.fetch(scale, 0.67);
iex>Ultraviolet.Color.hex(color)
"#ffff03"
iex>{:ok, corrected_scale} = Ultraviolet.scale(
...>  ["black", "red", "yellow", "white"],
...>  correct_lightness?: true
...>);
iex>{:ok, color} = Ultraviolet.Scale.fetch(corrected_scale, 0.67);
iex>Ultraviolet.Color.hex(color)
"#ff8000"

Padding

:padding reduces the color range by cutting off a fraction of the gradient on both sides. If you pass a single number, the same padding will be applied to both sides. The default padding is 0, i.e. no padding applied.

iex>{:ok, _scale} = Ultraviolet.scale(
...>  ["red", "yellow", "blue"],
...>  padding: 0.15
...>);

Alternatively, you can specify the padding for each side individually by passing a two-number tuple:

iex>{:ok, _scale} = Ultraviolet.scale(
...>  ["red", "yellow", "blue"],
...>  padding: {0.2, 0}
...>);

Classes

If you want the scale to return a distinct set of colors instead of a continuous gradient, you can use the :classes option. Passing an integer will break up the scale into equidistant classes.

iex>{:ok, scale} = Ultraviolet.scale("OrRd", classes: 5);
iex>Enum.map(Ultraviolet.Scale.take_keys(scale, [0.1, 0.15]), &Ultraviolet.Color.hex/1)
["#fff7ec", "#fff7ec"]

You can also define custom class breaks by passing them as an array.

iex>{:ok, scale} = Ultraviolet.scale(
...>  "OrRd",
...>  classes: [0, 0.3, 0.55, 0.85, 1]
...>);
iex>Enum.map(Ultraviolet.Scale.take_keys(scale, [0.15, 0.25]), &Ultraviolet.Color.hex/1)
["#fff7ec", "#fff7ec"]

The default value is 0, meaning a continuous gradient will be used.

Interpolation

By default, the colors retrieved from the scale are the result of linear interpolation. If you want to change this, use the :interpolation option.

This option accepts a unary function (i.e. a function with one argument), which will be called every time a color retrieval function is called on that space. It should accept a number and return a Ultraviolet.Color or an {:ok, Ultraviolet.Color} tuple.

There are also two builtin interpolation options: :linear for linear interpolation (the default) and :bezier for Bezier interpolation. For :bezier interpolation, the :space must be either :lab or :oklab. If no :space option is passed, :lab will be used.

iex>{:ok, scale} = Ultraviolet.scale(["yellow", "red", "black"]);
iex>Enum.map(Ultraviolet.Scale.take(scale, 5), &Ultraviolet.Color.hex/1)
["#ffff00", "#ff8000", "#ff0000", "#800000", "#000000"]
iex>Ultraviolet.scale(
...>  ["yellow", "red", "black"],
...>  interpolation: :bezier,
...>  space: :rgb
...>)
{:error, "bezier interpolation requires either Lab or OKLab colorspace"}
iex>{:ok, bezier_scale} = Ultraviolet.scale(
...>  ["yellow", "red", "black"],
...>  interpolation: :bezier
...>);
iex>Enum.map(Ultraviolet.Scale.take(bezier_scale, 5), &Ultraviolet.Color.hex/1)
["#ffff00", "#f5a900", "#bf5e0b", "#6c280e", "#000000"]

Example: cubehelix

Here's how you might use this option to implement Dave Green's cubehelix scheme:

iex>defmodule CubeHelix do
...>  def interpolate(x, params) do
...>    a = :math.tau() * ((params.start + 120) / 360 + params.rotations * x)
...>    l = :math.pow(lightness(params) + dl(params) * x, params.gamma)
...>    h = hue(params, x)
...>    amp = (h * l * (1 - l)) / 2
...>    cos_a = :math.cos(a)
...>    sin_a = :math.sin(a)
...>    [
...>      l + amp * (-0.14861 * cos_a + 1.78277 * sin_a),
...>      l + amp * (-0.29277 * cos_a - 0.90649 * sin_a),
...>      l + amp * (1.97294 * cos_a)
...>    ]
...>    |> Enum.map(&clamp_byte(&1 * 255))
...>    |> Ultraviolet.Color.new()
...>  end
...>
...>  defp lightness(%{lightness: {l0, _}}), do: l0
...>  defp lightness(%{lightness: l}), do: l
...>
...>  defp dl(%{lightness: {l0, l1}}), do: l1 - l0
...>  defp dl(%{lightness: _}), do: 0
...>
...>  defp hue(%{hue: {h0, h1}}, x), do: h0 + x * (h1 - h0)
...>  defp hue(%{hue: hue}, _x), do: hue
...>
...>  defp clamp_byte(n), do: min(max(n, 0), 255)
...>end;
iex>params = %{start: 300, rotations: -1.5, hue: 1, gamma: 1, lightness: {0, 1}};
iex>{:ok, cubehelix} = Ultraviolet.scale(
...>  ["black", "white"],
...>  domain: [0, 1],
...>  interpolation: fn x -> CubeHelix.interpolate(x, params) end
...>);
iex>Enum.map(Ultraviolet.Scale.take(cubehelix, 5), &Ultraviolet.Color.hex/1)
["#000000", "#16534c", "#a07949", "#c7b3ed", "#ffffff"]

Linear Interpolation: Hue Interpolation Direction

As with mix/3, use the :longer? option when creating a Scale to interpolate along the "longer" route between hues. See mix/3 for more details about how this works.

iex>{:ok, scale} = Ultraviolet.scale(["red", "lime"], space: :hsl, longer?: true);
iex>Ultraviolet.Color.hex(Ultraviolet.Scale.get(scale, 0.5))
"#0000ff"
@spec temperature(non_neg_integer()) ::
  {:ok, Ultraviolet.Color.t()} | {:error, term()}

Converts a temperature into a color, based on the color temperature scale.

Examples

iex>Ultraviolet.temperature(2000)
{:ok, %Ultraviolet.Color{r: 255, g: 139, b: 20, a: 1.0}}
iex>Ultraviolet.temperature(3500)
{:ok, %Ultraviolet.Color{r: 255, g: 195, b: 138, a: 1.0}}
iex>Ultraviolet.temperature(6500)
{:ok, %Ultraviolet.Color{r: 255, g: 250, b: 254, a: 1.0}}