Unicolour

GitHub GitLab NuGet pipeline status tests passed coverage report

Unicolour is the most comprehensive .NET library for working with color:

Written in C# with zero dependencies and supports full cross-platform compatibility.

Targets .NET Standard 2.0 for use in .NET 5.0+, .NET Core 2.0+ and .NET Framework 4.6.1+ applications.

See a live demo in the browser — a color picker for any color space — made with Unicolour.

Contents

  1. 🧭 Overview
  2. 🔆 Installation
  3. Quickstart
  4. 🌈 Features
  5. 💡 Configuration
  6. Examples
  7. 🔮 Datasets

🧭 Overview

A Unicolour encapsulates a single color and its representation across 30+ color spaces. It can be used to mix and compare colors, as well as other useful tools for working with color.

Supported color spaces

RGB · Linear RGB · HSB / HSV · HSL · HWB · HSI · CIEXYZ · CIExyY · WXY · CIELAB · CIELChab · CIELUV · CIELChuv · HSLuv · HPLuv · YPbPr · YCbCr / YUV (digital) · YCgCo · YUV (PAL) · YIQ (NTSC) · YDbDr (SECAM) · TSL · XYB · IPT · ICTCP · Jzazbz · JzCzhz · Oklab · Oklch · Okhsv · Okhsl · Okhwb · CIECAM02 · CAM16 · HCT · CMYK / ICC Profile ?

Unicolour pink = new("#FF1493");
Console.WriteLine(pink.Oklab); // 0.65 +0.26 -0.01

This library was initially written for personal projects since existing libraries had complex APIs, missing features, or inaccurate conversions. The goal of this library is to be accurate, intuitive, and easy to use. Although performance is not a priority, conversions are only calculated once; when first evaluated (either on access or as part of an intermediate conversion step) the result is stored for future use.

Unicolour is extensively tested, including verification of roundtrip conversions, validation using known color values, and 100% line coverage and branch coverage.

🔆 Installation

  1. Install the package from NuGet
    dotnet add package Wacton.Unicolour
    
  2. Import the package
    using Wacton.Unicolour;
    
  3. Use the package
    Unicolour color = new(ColourSpace.Rgb255, 192, 255, 238);
    

⚡ Quickstart

The simplest way to get started is to make a Unicolour and use it to see how the color is represented in a different color space.

var cyan = new Unicolour("#00FFFF");
Console.WriteLine(cyan.Hsl); // 180.0° 100.0% 50.0%

var yellow = new Unicolour(ColourSpace.Rgb255, 255, 255, 0);
Console.WriteLine(yellow.Hex); // #FFFF00

Colors can be mixed or interpolated using any color space.

var red = new Unicolour(ColourSpace.Rgb, 1.0, 0.0, 0.0);
var blue = new Unicolour(ColourSpace.Hsb, 240, 1.0, 1.0);

/* RGB: [1, 0, 0] ⟶ [0, 0, 1] = [0.5, 0, 0.5] */
var purple = red.Mix(blue, ColourSpace.Rgb);
Console.WriteLine(purple.Rgb); // 0.50 0.00 0.50
Console.WriteLine(purple.Hex); // #800080

/* HSL: [0, 1, 0.5] ⟶ [240, 1, 0.5] = [300, 1, 0.5] */
var magenta = red.Mix(blue, ColourSpace.Hsl); 
Console.WriteLine(magenta.Rgb); // 1.00 0.00 1.00
Console.WriteLine(magenta.Hex); // #FF00FF

The difference or distance between colors can be calculated using any delta E metric.

var white = new Unicolour(ColourSpace.Oklab, 1.0, 0.0, 0.0);
var black = new Unicolour(ColourSpace.Oklab, 0.0, 0.0, 0.0);
var difference = white.Difference(black, DeltaE.Ciede2000);
Console.WriteLine(difference); // 100.0000

Other useful color information is available, such as chromaticity coordinates, temperature, and dominant wavelength.

var equalTristimulus = new Unicolour(ColourSpace.Xyz, 0.5, 0.5, 0.5);
Console.WriteLine(equalTristimulus.Chromaticity.Xy); // (0.3333, 0.3333)
Console.WriteLine(equalTristimulus.Chromaticity.Uv); // (0.2105, 0.3158)
Console.WriteLine(equalTristimulus.Temperature); // 5455.5 K (Δuv -0.00442)
Console.WriteLine(equalTristimulus.DominantWavelength); // 596.1

Reference white points (e.g. D65) and the RGB model (e.g. sRGB) can be configured.

🌈 Features

Convert between color spaces

Unicolour calculates all transformations required to convert from one color space to any other, so there is no need to manually chain multiple functions and removes the risk of rounding errors.

Unicolour color = new(ColourSpace.Rgb255, 192, 255, 238);
var (l, c, h) = color.Oklch.Triplet;
Color space Enum Property
RGB (0–255) ColourSpace.Rgb255 .Rgb.Byte255
RGB ColourSpace.Rgb .Rgb
Linear RGB ColourSpace.RgbLinear .RgbLinear
HSB / HSV ColourSpace.Hsb .Hsb
HSL ColourSpace.Hsl .Hsl
HWB ColourSpace.Hwb .Hwb
HSI ColourSpace.Hsi .Hsi
CIEXYZ ColourSpace.Xyz .Xyz
CIExyY ColourSpace.Xyy .Xyy
WXY ColourSpace.Wxy .Wxy
CIELAB ColourSpace.Lab .Lab
CIELChab ColourSpace.Lchab .Lchab
CIELUV ColourSpace.Luv .Luv
CIELChuv ColourSpace.Lchuv .Lchuv
HSLuv ColourSpace.Hsluv .Hsluv
HPLuv ColourSpace.Hpluv .Hpluv
YPbPr ColourSpace.Ypbpr .Ypbpr
YCbCr / YUV (digital) ColourSpace.Ycbcr .Ycbcr
YCgCo ColourSpace.Ycgco .Ycgco
YUV (PAL) ColourSpace.Yuv .Yuv
YIQ (NTSC) ColourSpace.Yiq .Yiq
YDbDr (SECAM) ColourSpace.Ydbdr .Ydbdr
TSL ColourSpace.Tsl .Tsl
XYB ColourSpace.Xyb .Xyb
IPT ColourSpace.Ipt .Ipt
ICTCP ColourSpace.Ictcp .Ictcp
Jzazbz ColourSpace.Jzazbz .Jzazbz
JzCzhz ColourSpace.Jzczhz .Jzczhz
Oklab ColourSpace.Oklab .Oklab
Oklch ColourSpace.Oklch .Oklch
Okhsv ColourSpace.Okhsv .Okhsv
Okhsl ColourSpace.Okhsl .Okhsl
Okhwb ColourSpace.Okhwb .Okhwb
CIECAM02 ColourSpace.Cam02 .Cam02
CAM16 ColourSpace.Cam16 .Cam16
HCT ColourSpace.Hct .Hct
CMYK / ICC Profile ? - .Icc

Mix colors

Two colors can be mixed by interpolating between them in any color space, taking into account cyclic hue, interpolation distance, and alpha premultiplication.

var red = new Unicolour(ColourSpace.Rgb, 1.0, 0.0, 0.0);
var blue = new Unicolour(ColourSpace.Hsb, 240, 1.0, 1.0);
var magenta = red.Mix(blue, ColourSpace.Hsl, 0.5, HueSpan.Decreasing); 
var green = red.Mix(blue, ColourSpace.Hsl, 0.5, HueSpan.Increasing); 
Hue span Enum
Shorter 👈 default HueSpan.Shorter
Longer HueSpan.Longer
Increasing HueSpan.Increasing
Decreasing HueSpan.Decreasing

Compare colors

Two methods of comparing colors are available: contrast and difference. Difference is calculated according to a specific delta E (ΔE) metric.

var red = new Unicolour(ColourSpace.Rgb, 1.0, 0.0, 0.0);
var blue = new Unicolour(ColourSpace.Hsb, 240, 1.0, 1.0);
var contrast = red.Contrast(blue);
var difference = red.Difference(blue, DeltaE.Cie76);
Delta E Enum
ΔE76 (CIE76) DeltaE.Cie76
ΔE94 (CIE94) graphic arts DeltaE.Cie94
ΔE94 (CIE94) textiles DeltaE.Cie94Textiles
ΔE00 (CIEDE2000) DeltaE.Ciede2000
ΔECMC (CMC l:c) 2:1 acceptability DeltaE.CmcAcceptability
ΔECMC (CMC l:c) 1:1 perceptibility DeltaE.CmcPerceptibility
ΔEITP DeltaE.Itp
ΔEz DeltaE.Z
ΔEHyAB DeltaE.Hyab
ΔEOK DeltaE.Ok
ΔECAM02 DeltaE.Cam02
ΔECAM16 DeltaE.Cam16

Map color into display gamut

Colors that cannot be displayed with the configured RGB model can be mapped to the closest in-gamut color. The gamut mapping algorithm conforms to CSS specifications.

var outOfGamut = new Unicolour(ColourSpace.Rgb, -0.51, 1.02, -0.31);
var inGamut = outOfGamut.MapToGamut();

Convert between color and temperature

Correlated color temperature (CCT) and delta UV (∆uv) can be obtained from a color, and can be used to create a color. CCT from 500 K to 1,000,000,000 K is supported but only CCT from 1,000 K to 20,000 K is guaranteed to have high accuracy.

var chromaticity = new Chromaticity(0.3457, 0.3585);
var d50 = new Unicolour(chromaticity);
var (cct, duv) = d50.Temperature;

var temperature = new Temperature(6504, 0.0032);
var d65 = new Unicolour(temperature);
var (x, y) = d65.Chromaticity;

Create color from spectral power distribution

A spectral power distribution (SPD) can be used to create a color. Wavelengths should be provided in either 1 nm or 5 nm intervals, and omitted wavelengths are assumed to have zero spectral power.

var spd = new Spd
{
    { 575, 0.5 }, 
    { 580, 1.0 }, 
    { 585, 0.5 }
};
        
var intenseYellow = new Unicolour(spd);

Get wavelength attributes

The dominant wavelength and excitation purity of a color can be derived using the spectral locus. Wavelengths from 360 nm to 700 nm are supported.

var chromaticity = new Chromaticity(0.1, 0.8);
var hyperGreen = new Unicolour(chromaticity);
var dominantWavelength = hyperGreen.DominantWavelength;
var excitationPurity = hyperGreen.ExcitationPurity;

Detect imaginary colors

Whether a color is imaginary — one that cannot be produced by the eye — can be determined using the spectral locus. They are the colors that lie outside the horseshoe-shaped curve of the CIE xy chromaticity diagram.

var chromaticity = new Chromaticity(0.05, 0.05);
var impossibleBlue = new Unicolour(chromaticity);
var isImaginary = impossibleBlue.IsImaginary;

Simulate color vision deficiency

A new Unicolour can be generated that simulates how a color appears to someone with a particular color vision deficiency (CVD) or color blindness.

var color = new Unicolour(ColourSpace.Rgb255, 192, 255, 238);
var noRed = color.SimulateProtanopia();
Color vision deficiency Method
Protanopia (no red perception) SimulateProtanopia()
Deuteranopia (no green perception) SimulateDeuteranopia()
Tritanopia (no blue perception) SimulateTritanopia()
Achromatopsia (no color perception) SimulateAchromatopsia()

Use ICC profiles for CMYK conversion

Device-dependent color prints of 4 (e.g. FOGRA39 CMYK) or more (e.g. FOGRA55 CMYKOGV) are supported through ICC profiles. If no ICC profile is provided, or if the profile is incompatible, naive conversion for uncalibrated CMYK is used instead.

using Wacton.Unicolour.Icc;

var fogra39 = new IccConfiguration("./Fogra39.icc", Intent.RelativeColorimetric);
var config = new Configuration(iccConfiguration: fogra39);

var navyRgb = new Unicolour(config, ColourSpace.Rgb255, 0, 0, 128);
Console.WriteLine(navyRgb.Icc); // 1.0000 0.8977 0.0001 0.2867 CMYK

var navyCmyk = new Unicolour(config, new Channels(1.0, 1.0, 0.0, 0.5));
Console.WriteLine(navyCmyk.Rgb.Byte255); // 46 37 87

Only ICC profiles that meet the following criteria are supported:

Restriction Criteria Tag signature
Device class Output or ColorSpace prtr or spac
PCS CIELAB or CIEXYZ Lab or XYZ
Transform A2B and B2A A2B0 and B2A0 (A2B1 B2A1 and A2B2 B2A2 are used accordingly if present)

A wider variety of ICC profiles will be supported in future releases. If a problem is encountered using an ICC profile that meets the above criteria, please raise an issue.

Handle invalid values

It is possible for invalid or unreasonable values to be used in calculations, either because conversion formulas have limitations or because a user passes them as arguments. Although these values don’t make sense to use, they should propagate safely and avoid triggering exceptions.

var bad1 = new Unicolour(ColourSpace.Oklab, double.NegativeInfinity, double.NaN, double.Epsilon);
var bad2 = new Unicolour(ColourSpace.Cam16, double.NaN, double.MaxValue, double.MinValue);
var bad3 = bad1.Mix(bad2, ColourSpace.Hct, amount: double.PositiveInfinity);

Sensible defaults, highly configurable

Unicolour uses sRGB as the default RGB model and standard illuminant D65 (2° observer) as the default white point of all color spaces, ensuring consistency and a suitable starting point for simple applications. These can be overridden using the Configuration parameter, and common configurations have been predefined.

var defaultConfig = new Configuration(RgbConfiguration.StandardRgb, XyzConfiguration.D65);
var color = new Unicolour(defaultConfig, ColourSpace.Rgb255, 192, 255, 238);

Zero dependencies, quality controlled

Each line of artisan code is exquisitely handcrafted in small-batch programming sessions. No dependencies are used, so there is no risk of reliance on deprecated, obsolete, or unmaintained packages. Every line of code is tested, and any defect is Unicolour’s responsibility.

💡 Configuration

The Configuration parameter can be used to define the context of the color.

Example configuration with predefined Rec. 2020 RGB & illuminant D50 (2° observer) XYZ:

Configuration config = new(RgbConfiguration.Rec2020, XyzConfiguration.D50);
Unicolour color = new(config, ColourSpace.Rgb255, 204, 64, 132);

Example configuration with manually defined wide-gamut RGB & illuminant C (10° observer) XYZ:

var rgbConfig = new RgbConfiguration(
    chromaticityR: new(0.7347, 0.2653),
    chromaticityG: new(0.1152, 0.8264),
    chromaticityB: new(0.1566, 0.0177),
    whitePoint: Illuminant.D50.GetWhitePoint(Observer.Degree2),
    fromLinear: value => Math.Pow(value, 1 / 2.19921875),
    toLinear: value => Math.Pow(value, 2.19921875)
);

var xyzConfig = new XyzConfiguration(Illuminant.C, Observer.Degree10);

var config = new Configuration(rgbConfig, xyzConfig);
var color = new Unicolour(config, ColourSpace.Rgb255, 202, 97, 143);

A Configuration is composed of sub-configurations. Each sub-configuration is optional and will fall back to a sensible default if not provided.

RgbConfiguration

Defines the RGB model, often used to specify a wider gamut than standard RGB (sRGB).

Predefined Property
sRGB 👈 default .StandardRgb
Display P3 .DisplayP3
Rec. 2020 .Rec2020
A98 .A98
ProPhoto .ProPhoto
ACES 2065-1 .Aces20651
ACEScg .Acescg
ACEScct .Acescct
ACEScc .Acescc
Rec. 601 (625-line) .Rec601Line625
Rec. 601 (525-line) .Rec601Line525
Rec. 709 .Rec709
xvYCC .XvYcc
PAL (Rec. 470) .Pal
PAL-M (Rec. 470) .PalM
PAL 625 (Rec. 1700) .Pal625
PAL 525 (Rec. 1700) .Pal525
NTSC (Rec. 470) .Ntsc
NTSC (SMPTE-C) .NtscSmpteC
NTSC 525 (Rec. 1700) .Ntsc525
SECAM (Rec. 470) .Secam
SECAM 625 (Rec. 1700) .Secam625

XyzConfiguration

Defines the XYZ white point (which is also inherited by color spaces that do not need a specific configuration), as well as the observer to use for temperature calculations.

Predefined Property
D65 (2° observer) 👈 default .D65
D50 (2° observer) .D50

YbrConfiguration

Defines the constants, scaling, and offsets required to convert to YPbPr and YCbCr.

Predefined Property
Rec. 601 👈 default .Rec601
Rec. 709 .Rec709
Rec. 2020 .Rec2020
JPEG .Jpeg

CamConfiguration

Defines the viewing conditions for CAM02 and CAM16, which take into account the surrounding environment to determine how a color is perceived.

Predefined Property
sRGB 👈 default .StandardRgb
HCT .Hct

The predefined sRGB configuration refers to an ambient illumination of 64 lux under a gray world assumption.

IccConfiguration

Defines the ICC profile and rendering intent, typically used for accurate CMYK conversion.

Predefined Property
None 👈 default .None

Unicolour does not embed or distribute ICC profiles. Some commonly used profiles can be found in the ICC profile registry.

IctcpScalar & JzazbzScalar

There is ambiguity and no clear consensus about how XYZ values should be scaled before calculating ICTCP and Jzazbz. These scalars can be changed to match the behaviour of other implementations if needed.

White points

All color spaces are impacted by the reference white point. Unicolour applies different reference white points to different sets of color spaces, as shown in the table below. When a conversion to or from XYZ space involves a change in white point, a chromatic adaptation transform (CAT) is performed using the Bradford method.

White point configuration Affected color spaces
RgbConfiguration RGB · Linear RGB · HSB / HSV · HSL · HWB · HSI · YPbPr · YCbCr / YUV (digital) · YCgCo · YUV (PAL) · YIQ (NTSC) · YDbDr (SECAM) · TSL · XYB
XyzConfiguration CIEXYZ · CIExyY · WXY · CIELAB · CIELChab · CIELUV · CIELChuv · HSLuv · HPLuv
CamConfiguration CIECAM02 · CAM16
None (always D65/2°) IPT · ICTCP · Jzazbz · JzCzhz · Oklab · Oklch · Okhsv · Okhsl · Okhwb · HCT

Convert between configurations

A Unicolour can be converted to a different configuration, in turn enabling conversions between different RGB models, XYZ white points, CAM viewing conditions, etc.

/* pure sRGB green */
var srgbConfig = new Configuration(RgbConfiguration.StandardRgb);
var srgbColor = new Unicolour(srgbConfig, ColourSpace.Rgb, 0, 1, 0);                         
Console.WriteLine(srgbColour.Rgb); // 0.00 1.00 0.00

/* ⟶ Display P3 */
var displayP3Config = new Configuration(RgbConfiguration.DisplayP3);
var displayP3Color = srgbColour.ConvertToConfiguration(displayP3Config); 
Console.WriteLine(displayP3Colour.Rgb); // 0.46 0.99 0.30

/* ⟶ Rec. 2020 */
var rec2020Config = new Configuration(RgbConfiguration.Rec2020);
var rec2020Color = displayP3Colour.ConvertToConfiguration(rec2020Config);
Console.WriteLine(rec2020Colour.Rgb); // 0.57 0.96 0.27

✨ Examples

This repository contains projects showing how Unicolour can be used to create:

  1. Gradient images
  2. Heatmaps of luminance
  3. Diagrams of color data
  4. A colorful console application
  5. A color picker web application
  6. 3D visualizations of color spaces in Unity

Gradients

Example code to create gradient images using 📷 SixLabors.ImageSharp can be seen in the Example.Gradients project.

Gradients generated through different color spaces, created with Unicolour
Gradients generated through each color space
Visualization of temperature from 1,000 K to 13,000 K, created with Unicolour
Visualization of temperature from 1,000 K to 13,000 K
Color spectrum rendered with different color vision deficiencies, created with Unicolour
Color spectrum rendered with different color vision deficiencies
Demonstration of interpolating from red to transparent to blue, with and without premultiplied alpha, created with Unicolour
Demonstration of interpolating from red to transparent to blue, with and without premultiplied alpha
Perceptually uniform colormaps from Unicolour.Datasets, created with Unicolour
Perceptually uniform colormaps from Unicolour.Datasets

Heatmaps

Example code to create heatmaps of luminance using 📷 SixLabors.ImageSharp with images from 🚀 NASA can be seen in the Example.Heatmaps project.

Heatmap of the sun using perceptually uniform colormaps from Unicolour.Datasets, created with Unicolour
Heatmap of the ☀️ sun using perceptually uniform colormaps from Unicolour.Datasets
Heatmap of the moon using perceptually uniform colormaps from Unicolour.Datasets, created with Unicolour
Heatmap of the 🌕 moon using perceptually uniform colormaps from Unicolour.Datasets

Diagrams

Example code to create diagrams of color data using 📈 ScottPlot can be seen in the Example.Diagrams project.

CIE xy chromaticity diagram with sRGB gamut, created with Unicolour
CIE xy chromaticity diagram with sRGB gamut
CIE xy chromaticity diagram with Planckian or blackbody locus, created with Unicolour
CIE xy chromaticity diagram with Planckian or blackbody locus
CIE xy chromaticity diagram with spectral locus plotted at 1 nm intervals, created with Unicolour
CIE xy chromaticity diagram with spectral locus plotted at 1 nm intervals
CIE 1960 color space, created with Unicolour
CIE 1960 color space
CIE 1960 color space with Planckian or blackbody locus, created with Unicolour
CIE 1960 color space with Planckian or blackbody locus

Console

Example code to create a colorful console application using ⌨️ Spectre.Console can be seen in the Example.Console project.

Console application displaying color information from a hex value, created with Unicolour
Console application displaying color information from a hex value

Web

Example code to create a client-side color picker web application using 🕸️ Blazor can be seen in the Example.Web project.

See the live demo!

Web application for picking colors in any color space, created with Unicolour
Web application for picking colors in any color space

Unity

Example code to create 3D visualizations of color spaces using 🎮 Unity can be seen in the Example.Unity project.

Try it out online in Unity Play!

3D visualization of color spaces in Unity, created with Unicolour
3D visualization of color spaces in Unity
3D movement through color spaces in Unity, created with Unicolour
3D movement through color spaces in Unity

🔮 Datasets

Some color datasets have been compiled for convenience and are available as a NuGet package.

Commonly used sets of colors:

Perceptually uniform colormaps / palettes:

Color data used in academic literature:

Example usage:

  1. Install the package from NuGet
    dotnet add package Wacton.Unicolour.Datasets
    
  2. Import the package
    using Wacton.Unicolour.Datasets;
    
  3. Reference the predefined Unicolour
    var pink = Css.DeepPink;
    var green = Xkcd.NastyGreen;
    var mapped = Colourmaps.Viridis.Map(0.5);
    

Wacton.Unicolour is licensed under the MIT License, copyright © 2022-2024 William Acton.

Also available in British 🇬🇧.