Chroma - Advanced Color Manipulation

For those who’d prefer, a full version of the article is available on Medium .

Chroma is a library that offers a range of color manipulation tools. Whether you’re looking to enhance your UI, create dynamic visual effects, or simply explore color theory, Chroma aims to make these tasks easier and more accessible.

Features:

  • color space conversions (RGB, HSL, HSV, LAB, LCH, etc.)
  • color manipulation (brighten/darken, saturate/desaturate)
  • interpolation (in various mode: RGB, HSL, HSV, LAB, LCH, etc.)
  • blending (in various mode: normal, multiply, screen, overlay, darken, lighten, dodge, burn)

Usage

This section will give you an overview to get you started quickly. It won’t cover the entire API of the library.

Constructing Colors

Chroma supports creating colors from various color spaces. The generic color constructor can accept various arguments:

-- RGB colors can be specified as 3 numbers or as an array
local red = chroma(255, 0, 0)
local blue = chroma({0, 0, 255})

-- hex codes can be provided as strings or as numbers
local pink = chroma("#ff3399")
local pink = chroma("#f39")
local pink = chroma(0xff3399)

-- X11 color names are supported (https://en.wikipedia.org/wiki/X11_color_names)
local cyan = chroma("cyan")

-- the color space can be provided as the last argument
local green = chroma(120, 1, 0.75, "hsl") -- hue is between 0-360
local purple = chroma(280, 0.6, 0.4, "hsi") -- hue is between 0-360
-- and you can still pass the numbers in an array
local skyblue = chroma({0.81, 0.08, 225.75}, "oklch")

-- colors can be built from a table where the keys match
-- a supported color space.
local yellow = chroma({ h = 60, s = 1, v = 1 })
local blue = chroma({ c = 1, m = 0.5, y = 0, k = 0.2 })

There are also constructors specific to the different color spaces supported:

-- RGB (Red, Green, Blue)
local red = chroma.rgb(255, 0, 0)
local green = chroma.rgb(0, 255, 0)

-- HSL (Hue, Saturation, Lightness)
local blue = chroma.hsl(240, 1, 0.5)

-- HSV (Hue, Saturation, Value)
local yellow = chroma.hsv(60, 1, 1)

-- LAB (Lightness, A, B)
local labColor = chroma.lab(54, 80, 67)

-- LCH (Lightness, Chroma, Hue)
local lchColor = chroma.lch(54, 105, 40)

-- HCL (Hue, Chroma, Lightness) - Alias for LCH
local hclColor = chroma.hcl(240, 105, 54)

-- HEX (Hexadecimal)
local hexColor = chroma.hex("#FF5733")

-- CMYK (Cyan, Magenta, Yellow, Black)
local cmykColor = chroma.cmyk(0, 1, 1, 0)

-- CSS (CSS color string)
local cssColor = chroma.css("rgba(255, 0, 0, 0.5)")
-- X11 color names are supported (https://en.wikipedia.org/wiki/X11_color_names)
local cornFlowerBlue = chroma.css("cornflowerblue")

-- GL (a variant of RGB, but values are from 0 to 1)
local glColor = chroma.gl(1, 0, 0, 1)

-- HCG (Hue, Chroma, Grayness)
local hcgColor = chroma.hcg(300, 1, 0.5)

-- HSI (Hue, Saturation, Intensity)
local hsiColor = chroma.hsi(120, 1, 0.5)

-- NUM (Numeric color value)
local numColor = chroma.num(0xFF5733)

-- OKLAB
local oklabColor = chroma.oklab(0.62796, 0.22486, 0.12585)

-- OKLCH
local oklchColor = chroma.oklch(0.62796, 0.257, 0.12585)

-- Temperature (Kelvin)
local tempColor = chroma.temperature(6500)

-- Roblox Color3
local chromaFromRoblox = chroma.roblox(Color3.new(1, 0, 0))

If you are not sure if a value can be parsed by Chroma, use the chroma.valid function:

chroma.valid('oof') -- returns false
chroma.valid('red') -- returns true

Color Manipulation

Chroma’s color object provides methods to manipulate colors:

local color = chroma(255, 0, 0)  -- Red

-- Lighten and darken
local lighter = color:brighten(1)
local darker = color:darken(1)

-- Saturate and desaturate
local saturated = color:saturate(1)
local desaturated = color:desaturate(1)

-- Set and get specific channels
color:alpha(0.5)  -- Set alpha
local hue = color:get('hsl.h')  -- Get hue

Color Conversion

Once you have a color, you can easily convert it into another color space. The color object has a method for each color space: cmyk, css, gl, hcg, hex, hsi, hsl, hsv, lab, lch, hcl, name, num, oklab, oklch, rgb, rgba and temperature.

local color = chroma.random()

local cmyk = color:cmyk()
local lch = color:lch()

local cssString = color:css()
local hexString = color:hex()

Interpolating Colors

Chroma offers powerful color interpolation capabilities to transitions between colors. This is particularly useful for generating gradients, creating color schemes, or animating color changes.

Basic Interpolation

The most straightforward way to interpolate between two colors is using mix:

local color1 = chroma.rgb(255, 0, 0)  -- Red
local color2 = chroma.rgb(0, 0, 255)  -- Blue

-- Interpolate halfway between color1 and color2
local interpolated = chroma.mix(color1, color2, 0.5)

-- or use the mix method
local interpolated2 = color1:mix(color2, 0.5)

The third parameter (0.5 in this example) represents the interpolation position, ranging from 0 (100% of color1) to 1 (100% of color2).

Chroma supports various color spaces for interpolation, each producing different results:

-- Available interpolation modes:
-- 'hcg', 'hsi', 'hsl', 'hsv', 'lab', 'lch', 'hcl', 'lrgb'
-- 'num', 'oklab', 'oklch', 'rgb'

local lchInterpolated = chroma.mix(color1, color2, 0.5, 'lch')
-- or using the mix method
local lchInterpolated2 = color1:mix(color2, 0.5, 'lch')

Color Scales

For more complex color transitions, you can use the chroma.scale function to interpolate through multiple colors:

local scale = chroma.scale({
    chroma.rgb(255, 0, 0), 
    chroma.rgb(0, 255, 0), 
    chroma.rgb(0, 0, 255),
})

-- Generate 5 colors along the scale
local colors = scale.colors(5)

-- Get a specific point along the scale
local midpoint = scale(0.5)

By default, the domain is set to [0, 1]. It can easily be re-mapped with the domain function:

local scale = chroma.scale({'yellow', '008ae5'}).domain({0, 100})

local scale(35)

The interpolation mode can also be configured:

local labScale = chroma.scale({ chroma(255, 0, 0), chroma(0, 0, 255) })
	.mode('lab')
	
local lchScale = chroma.scale({ chroma(255, 0, 0), chroma(0, 0, 255) })
	.mode('lch')

ColorBrewer Scales

Chroma includes ColorBrewer scales, which are carefully designed color palettes that are widely used in data visualization and cartography. These scales are particularly useful for creating visually appealing and perceptually accurate color schemes.

ColorBrewer scales are categorized into three main types:

  1. Sequential: Ideal for representing ordered data that progresses from low to high values. These scales use lightness steps, with light colors for low values and dark colors for high values.

    Available names: Blues, BuGn, BuPu, GnBu, Greens, Greys, OrRd, Oranges, PuBu, PuBuGn, PuRd, Purples, RdPu, Reds, YlGn, YlGnBu, YlOrBr, YlOrRd

  2. Diverging: Perfect for data with a meaningful midpoint. These scales use two hues that diverge from a neutral color in the middle.

    Available names: BrBG, PiYG, PRGn, PuOr, RdBu, RdGy, RdYlBu, RdYlGn, Spectral

  3. Qualitative: Designed for categorical data where there’s no inherent ordering. These scales use distinct hues to differentiate between categories.

    Available names: Accent, Dark2, Paired, Pastel1, Pastel2, Set1, Set2, Set3

To use a ColorBrewer scale in your project, simply pass the name of the palette to the chroma.scale function:

-- Create a ColorBrewer scale
local scale = chroma.scale('RdYlBu')

-- Generate colors from the scale
local colors = scale.colors(5)

-- Use a specific color from the scale
local midColor = scale(0.5)

Bezier Interpolation

To get even more control over color transitions, you can use Bezier interpolation:

local bezier = chroma.bezier({
    chroma.rgb(255, 0, 0),
    chroma.rgb(255, 255, 0), 
    chroma.rgb(0, 0, 255),
})

-- Get a specific point along the Bezier curve
local bezierMidpoint = bezier(0.5)

-- bezier.scale will give the same type as chroma.scale, so
-- you can generate 5 colors along the Bezier curve easily:
local bezierColors = bezier.scale.colors(5)

Blending Colors

Chroma supports various color blending modes:

local baseColor = chroma.rgb(255, 0, 0)  -- Red
local blendColor = chroma.rgb(0, 255, 0)  -- Green

-- Available blend modes:
-- 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'dodge', 'burn'
local blendedMultiply = baseColor:blend(blendColor, 'multiply')

Each blending mode produces a different result:

  1. multiply: Multiplies the base color by the blend color. The result is always darker.
  2. screen: Multiplies the inverse of the base and blend colors. The result is always lighter.
  3. overlay: Multiplies or screens the colors, depending on the base color.
  4. darken: Selects the darker of the base and blend colors.
  5. lighten: Selects the lighter of the base and blend colors.
  6. dodge: Brightens the base color to reflect the blend color.
  7. burn: Darkens the base color to reflect the blend color.

Roblox-Specific Features

Chroma-Luau includes two Roblox-specific methods to seamlessly integrate with Roblox’s Color3 objects:

  1. Creating a Chroma color from a Roblox Color3:
local robloxColor = Color3.new(1, 0, 0) -- Red
local chromaColor = chroma(robloxColor)
  1. Converting a Chroma color to a Roblox Color3:
local chromaColor = chroma.rgb(0, 255, 0) -- Green
local robloxColor = chromaColor:roblox()

Installation

For those using npm or yarn, simply add chroma-luau to your project dependencies by running:

npm install chroma-luau
# or
yarn add chroma-luau

You can also grab the Roblox model file attached to the latest release:

  1. Navigate to the GitHub releases
  2. Scroll to the Assets section and download the chroma.rbxm (Roblox model file)
  3. Drag the file into Roblox Studio

For a more exhaustive API description, visit the GitHub documentation page. The chroma-js documentation site is also a good reference and it translates pretty well to Luau.

If you find any issues with the library, open an issue on GitHub.

End Notes

I work part time to dedicate time to the Luau open-source ecosystem. If you appreciate this library or other stuff I built, please consider leaving a tip :sparkling_heart:I have a page on ko-fi where you can contribute or GitHub sponsors are available in any of my projects.

Chroma was translated to Luau from the original JavaScript chroma-js, along with all the automated tests (~2700 tests). Type annotations were added.

This library follows the Sea of Voices Luau Package Standard to ensure compatibility across environments. If you are writing Luau that runs on Lune, Chroma is also compatible.

To learn more:

Useful Links

12 Likes