Skip to content

Color space conversions and other color science related utilities in Java

License

Notifications You must be signed in to change notification settings

EsotericSoftware/color

Repository files navigation

Color

A Java library for color science with color space conversions, spectral calculations, and color metrics.

Key Features

  • Color spaces: 50+ spaces, bidirectional conversions via unified Color interface
  • Color appearance models: CAM16/CAM02 with uniform color spaces (UCS, LCD, SCD)
  • Spectral color: Spectrum support, CRI, TM-30-20 color fidelity metrics
  • CCT calculations: high accuracy improved Robertson, 1000K to infinity, ±0.1K < 7000K
  • Standard observers: CIE 1931, 1964, 2015, 2 and 10 degrees
  • Practical tools: RGBW/RGBWW LED mixing, gamut management, WCAG compliance
  • Professional spaces: ACES, ITP (HDR), HCT (Material You)
  • No dependencies: Pure Java with floats, straightforward code, easy to port

Supported Color Spaces

Many color spaces are supported including RGB variants (HSV, HSL), CIE spaces (Lab, Luv, XYZ), modern perceptual spaces (Oklab, Oklch), color appearance models (CAM16, CAM02), video formats (YCbCr, YUV), professional spaces (ACES, ITP), and specialized spaces for computer vision and printing. See the full list at com.esotericsoftware.color.space.

All color spaces implement the Color interface, enabling conversion to any other space:

RGBrgb = newRGB(1, 0.5f, 0.25f); Lablab = rgb.Lab(); CAM16cam = lab.CAM16(); Oklabok = cam.Oklab();

This library breaks from Java naming conventions to use capitalization that matches the color space names, making the code clearer and more aligned with color science literature. It also omits from or to prefixes to make for cleaner method names, without needing camel case.

Gradient Interpolation

This image shows color spaces suitable for gradient visualization. Generated by Gradients.java.

Color Appearance Models

CAM16 and CAM02

Both models support customizable viewing conditions and have three uniform color space variants:

// Standard uniform color spaceCAM16UCSucs = rgb.CAM16UCS(); CAM02UCSucs2 = rgb.CAM02UCS(); // Large color differences (industry/design)CAM16LCDlcd = rgb.CAM16LCD(); CAM02LCDlcd2 = rgb.CAM02LCD(); // Small color differences (quality control)CAM16SCDscd = rgb.CAM16SCD(); CAM02SCDscd2 = rgb.CAM02SCD(); // Custom viewing conditionsCAM16.VCvc = CAM16.VC.with( Observer.CIE2.D65, // White point40, // Adapting luminance (cd/m²)50, // Background L* value2, // Surround (0=dark, 1=dim, 2=average)false// Discounting illuminant ); CAM16cam = rgb.CAM16(vc);

Color Temperature & Lighting

Correlated Color Temperature (CCT)

// Colors to CCT K and Duv.CCTcct = rgb.CCT(); // CCT to colors, 1000K to infinity is supported.RGBwarmWhite = newCCT(2700).RGB(); // 2700KRGBdaylight = newCCT(6500, 0.003f).RGB(); // 6500K with Duv offset// Exact Planck's law.XYZxyz = newCCT(5000).PlanckianXYZ();

Improved Robertson Method

An improved Robertson method is used for CCT to and from u'v' supporting [1000K..infinity]:

DuvK rangeMax K error@ KMax Duv error
01,000..2,0000.096984861,014.596440.0001
02,000..7,0000.106445315,353.71340.0001
07,000..20,0001.074218819,944.1760.0001
020,000..60,0002.019531259,854.040.0001
060,000..100,0002.195312594,087.120.0001
-0.05..0.051,000..2,0000.1320.0001
-0.05..0.052,000..7,0000.4260.0001
-0.05..0.057,000..20,0001.4520.0001
-0.05..0.0520,000..60,0002.5860.0001
-0.05..0.0560,000..100,0004.0630.0001

The lookup table for this accuracy is generated by CCTRobertsonLUT and contains only 131 entries: 5 floats each, 655 total. Instead of slope it stores direction, avoiding a couple sqrt calls. Binary and Fibonacci search were benchmarked but slower.

For comparison, an Ohno table would need to be much larger, requires double, and is a lot slower. If more accuracy is needed it would be best to use improved Robertson with a larger LUT.

The bracketing for the above accuracy results in this LUT spacing in u'v' space [1000..100000K+]: The space before the last line is from 100000K to infinity K.

At ~1620K the Planckian locus slope changes from positive to negative, wrecking the line-side check that detects which 2 lines the point is between. The sign changes in the LUT at index 565, so the line-side check is reversed >= 565. Next problem interpolation between 560 (negative slope) and 565 (positive) is adjusted and Duv is flipped >= 565.

The improved Robertson is also used for the inverse conversion, from CCT to u'v'.

RGB + White LEDs

Convert linear RGB or CCT to RGBW or RGBWW, using calibrated white LED colors.

// RGBW (single white channel)RGBWrgbw = linearRgb.RGBW(white); // RGBWW (dual white channels) WWww = newWW(warmWhite, coolWhite); RGBWWrgbww = linearRgb.RGBWW(ww); // Create from color temperatureCCTcct3000 = newCCT(3000); RGBWrgbw = cct3000.LRGB().scl(0.8f).RGBW(white); RGBWWrgbww = cct3000.LRGB().scl(0.8f).RGBWW(ww);

Spectral Color and Light Quality

Spectrum Class

Work with spectral power distributions (380-780nm at 5nm intervals):

// Create spectrum from colorSpectrumspectrum = rgb.spectrum(); // Get spectral valuesfloat[] spd = spectrum.spectrum(); floatat550nm = spectrum.get(550); // Convert to colorXYZxyz = spectrum.XYZ(); RGBrgb = spectrum.RGB(); // Spectral calculationsfloatluminousFlux = spectrum.luminousFlux(); floatwavelength = spectrum.dominantWavelength();

Color Rendering Index (CRI)

CIE 13.3-1995 color rendering index:

// Calculate CRI for a light sourceSpectrumlightSource = newCCT(3000).spectrum(); CRIcri = newCRI(lightSource); floatRa = cri.Ra(); // General CRI (0-100)floatR9 = cri.Ri(9); // Red rendering (important for skin tones)// All 14 test color samplesfor (inti = 1; i <= 14; i++){floatRi = cri.Ri(i)}

TM-30-20 Color Rendition

Modern color quality metrics:

TM30tm30 = newTM30(lightSource); // Fidelity and gamut indicesfloatRf = tm30.Rf(); // Fidelity (0-100)floatRg = tm30.Rg(); // Gamut (typically 80-120)// Detailed hue analysis (16 bins)float[] Rfh = tm30.Rfh(); // Fidelity by huefloat[] Rgh = tm30.Rgh(); // Gamut by hue// Design intent prioritiesfloatpreference = tm30.preference(); floatfidelity = tm30.fidelity(); floatvividness = tm30.vividness();

Color Analysis & Utilities

Color Difference

// Delta E 2000: Perceptual lightness, chromaticity, and hue differencefloatdeltaE = rgb1.deltaE2000(rgb2); floatdeltaE = lab1.deltaE2000(lab2); // With custom weights for L*, C*, H*floatdeltaE = lab1.deltaE2000(lab2, 2f, 1f, 1f); // MacAdam steps: Perceptual chromaticity differencefloatsteps = xy1.MacAdamSteps(xy2); // CAM16-UCS distance (perceptually uniform)floatdistance = cam16ucs1.deltaE(cam16ucs2);

Accessibility & Contrast

// WCAG contrast ratio (1:1 to 21:1)floatratio = foreground.contrastRatio(background); // Check WCAG compliancebooleanmeetsAA = foreground.WCAG_AA(background, largeText); booleanmeetsAAA = foreground.WCAG_AAA(background, largeText);

Color Analysis

// Convert to grayscale (perceptual luminance)floatgray = rgb.grayscale(); // Check if color is achromaticbooleanisGray = rgb.achromatic(); // Get relative luminancefloatluminance = rgb.luminance();

Color Harmonies

RGBbaseColor = newRGB(1, 0.5, 0.25); RGBcomplementary = baseColor.complementary(); RGB[] triadic = baseColor.triadic(); RGB[] analogous = baseColor.analogous(30f); // 30° angleRGB[] splitComp = baseColor.splitComplementary();

Gamut Management

The Gamut class manages color space boundaries:

Define Gamuts

Gamutsrgb = Gamut.sRGB; // Standard RGBGamutp3 = Gamut.DisplayP3; // Display P3Gamutrec2020 = Gamut.Rec2020; // Rec. 2020Gamutfull = Gamut.all; // Full visible spectrumGamuthueA = Gamut.PhilipsHue.A; // Philips Hue Avartriangular = newRGBGamut(red, greenblue, Observer.CIE2.D65); // Customvarpolygonal = newPolygonGamut( // Customnewxy(0.68f, 0.32f), // Red-ishnewxy(0.265f, 0.69f), // Green-ishnewxy(0.15f, 0.06f), // Blue-ishnewxy(0.50f, 0.40f) // Yellow-ish );

Gamut Operations

booleaninGamut = gamut.contains(chromaticity); // Closest point if outside gamutxyclamped = gamut.clamp(chromaticity);

Utility Functions

Many color spaces support component operations, scaling, interpolation, and distance calculations:

// Component operationsfloatL = lab.get(0); // Get by indexLabdarker = lab.add(-10, 0, 0); // Add to componentsRGBscaled = rgb.scl(0.5f); // Scale all componentsfloatdistance = color1.dst(color2); // Euclidean distance// Interpolation in any spaceOklabmixed = oklab1.lerp(oklab2, 0.5f); // 50% blend// Utility functionsimportstaticcom.esotericsoftware.color.Colors.*; floatencoded = sRGB(linear); // Gamma correctionStringhex = hex(rgb); // "FF8040"float[] values = floats(color); // Convert to arrayintdmx = dmx16(0.5f); // DMX lighting control

Spectral Locus

Utilities are provided for working with spectral colors and the visible spectrum boundary.

Wavelength to Chromaticity

// Convert wavelength (380-700nm) to CIE u'v' coordinatesuvcolor550nm = SpectralLocus.uv(550); // Green at 550nmxycolor550nmXY = SpectralLocus.xy(550); // Same in xy space// Get exact spectral colorsuvred = SpectralLocus.uv(700); // Deep reduvblue = SpectralLocus.uv(450); // Blueuvgreen = SpectralLocus.uv(550); // Green

Spectrum Boundary Testing

// Check if a color is within the visible spectrumuvtestColor = rgb.uv(); booleanvisible = SpectralLocus.contains(testColor); // Colors outside the spectral locus are not physically realizablebooleanimpossible = SpectralLocus.contains(newuv(0.8f, 0.8f)); // false

Dominant Wavelength

// Find the dominant wavelength of any coloruvcolor = newRGB(0, 1, 0).uv(); // Green RGBfloatwavelength = SpectralLocus.dominantWavelength(color); // ~550nm// Purple/magenta colors return negative complementary wavelengthsuvmagenta = newRGB(1, 0, 1).uv(); floatpurpleWave = SpectralLocus.dominantWavelength(magenta); // Negative value// Use custom white pointfloatwavelength2 = SpectralLocus.dominantWavelength(color, Observer.CIE2.A);

Excitation Purity

// Measure color saturation (0 = white, 1 = pure spectral color)floatpurity = SpectralLocus.excitationPurity(color); // Gray has low purityuvgray = newRGB(0.5f, 0.5f, 0.5f).uv(); floatgrayPurity = SpectralLocus.excitationPurity(gray); // < 0.1// Saturated colors have high purityuvsaturated = newRGB(1, 0, 0).uv(); floatredPurity = SpectralLocus.excitationPurity(saturated); // > 0.8

Standard Observers

CIE standard observers are provided:

// 2 degree observerXYZd65_2deg = Observer.CIE2_1931.D65; XYZd50_2deg = Observer.CIE2_1931.D50; // 10 degree observerXYZd65_10deg = Observer.CIE10_1964.D65; // 2015XYZd65_2deg_2015 = Observer.CIE2_2015.D65; // Change default from CIE2_1931Observer.Default = Observer.CIE2_2015;

Available Observers: A, C, D50, D55, D65, D75, F2, F7, F11

Chromatic Adaptation Transforms

// Various CAT methods for cone responseLMSlms = xyz.LMS(LMS.CAT.Bradford); LMSlms = xyz.LMS(LMS.CAT.vonKries); LMSlms = xyz.LMS(LMS.CAT.CAT02); LMSlms = xyz.LMS(LMS.CAT.CAT97); LMSlms = xyz.LMS(LMS.CAT.HPE); // Convert backXYZxyz = lms.XYZ(LMS.CAT.Bradford);

Quick Examples

importcom.esotericsoftware.color.space.*; // Basic conversionsRGBrgb = newRGB(0.5f, 0.7f, 0.3f); HSVhsv = rgb.HSV(); Lablab = rgb.Lab(); CAM16cam = lab.CAM16(); // Perceptual color mixingOklabcolor1 = rgb1.Oklab(); Oklabcolor2 = rgb2.Oklab(); Oklabmixed = color1.lerp(color2, 0.5f); // 50% blend// Color temperatureRGBwarmWhite = newCCT(2700).RGB(); // 2700KRGBdaylight = newCCT(6500).RGB(-0.003f); // 6500K with Duv// AccessibilitybooleanmeetsWCAG = foreground.WCAG_AA(background, false); floatcontrast = foreground.contrastRatio(background);

License

This library is released under the BSD 3-Clause License.

About

Color space conversions and other color science related utilities in Java

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages