Realistic Color Mixing Library Using Kubelka-Munk Theory – Spectral.js

Realistic Color Mixing Library Using Kubelka-Munk Theory – Spectral.js
Realistic Color Mixing Library Using Kubelka-Munk Theory – Spectral.js
Spectral.js is a lightweight JavaScript library that simulates realistic color mixing based on the Kubelka-Munk theory.

It models how physical pigments interact with light, rather than relying on standard RGB or HSL color blending. This produces color combinations that match what you’d see when mixing actual paint.

You work with familiar hex, RGB, and CSS strings, while the library handles the complex spectral math behind the scenes.

Features:

  • Physics-based color mixing: Models pigment interaction using Kubelka-Munk absorption and scattering coefficients.
  • Spectral reflectance simulation: Generates reflectance curves from RGB values across the visible spectrum (380-750 nm in 10 nm steps).
  • Perceptually uniform color spaces: Uses OKLab and OKLCh for accurate gamut mapping and perceptual color comparisons.
  • Tinting strength control: Adjusts the dominance of individual colors in a mixture to prevent overly saturated results.
  • Lazy memoization: Computes color space transformations and spectral data only once, caching values for performance.
  • GLSL shader support: Includes spectral.glsl for GPU-based real-time color mixing in WebGL projects.
  • 64-bit precision: All spectral and colorimetric calculations use double-precision floating point math.

Use Cases:

  • Digital Painting Applications: Developers can create brushes that blend colors naturally like oil or acrylics.
  • Generative Art: Artists can generate palettes that maintain vibrancy. Standard RGB math often desaturates colors during transitions.
  • Data Visualization: It generates gradients that are perceptually uniform. This makes heatmaps and charts easier to read.
  • Print Simulation: The library approximates how physical inks will layer and mix before going to print.

How To Use It:

1. Install Spectral.js via npm or include the script directly in your HTML:

# NPM
$ npm install spectral.js
<script src="spectral.js"></script>

2. Basic color creation. The Color object automatically computes spectral reflectance, XYZ values, OKLab coordinates, and Kubelka-Munk parameters. These calculations are cached internally for performance.

// Create color from hex string
let blue = new spectral.Color('#002185');

// Create color from RGB string
let yellow = new spectral.Color('rgb(252, 210, 0)');

// Create color from RGB array
let red = new spectral.Color([252, 48, 48]);

3. Mixing Colors. The mix() function combines any number of colors using their spectral properties. Each color requires a mixing factor.

let color1 = new spectral.Color('#002185'); // Blue
let color2 = new spectral.Color('#FCD200'); // Yellow

// Mix equal parts blue and yellow
let green = spectral.mix([color1, 0.5], [color2, 0.5]);

console.log(green.toString()); // Outputs: #3D933E

4. The mixing factors are normalized automatically. You can use whole numbers if preferred:

let color1 = new spectral.Color('#FCF046'); // Yellow
let color2 = new spectral.Color('#E53166'); // Magenta
let color3 = new spectral.Color('#3375DA'); // Blue

// Mix using whole number ratios (1:1:1)
let brown = spectral.mix([color1, 1], [color2, 1], [color3, 1]);

console.log(brown.toString()); // Outputs: #8D7964

5. Generating Color Palettes. The palette() function creates smooth transitions between two colors using spectral interpolation.

let start = new spectral.Color('#BB0657'); // Magenta
let end = new spectral.Color('#E0E0B3'); // Beige

// Generate 8-step gradient
let colors = spectral.palette(start, end, 8);

// Convert to hex strings
let hexColors = colors.map(color => color.toString());
console.log(hexColors);
// ['#BB0657', '#C00C5C', '#CD1E6F', '#DD3889', '#E85AA0', '#ED85AF', '#E9B9B4', '#E0E0B3']

6. Create Gradients using the gradient() function.

let teal = new spectral.Color('#005E72');
let cream = new spectral.Color('#EAD9A7');
let mauve = new spectral.Color('#894B54');

// Get color at 75% position
let t = 0.75;
let result = spectral.gradient(t, [teal, 0], [cream, 0.5], [mauve, 1]);

console.log(result.toString()); // Outputs: #C7938C

7. Some colors dominate mixtures more than others. The tintingStrength property can help you control this behavior.

let red = new spectral.Color('#ff0000');
let yellow = new spectral.Color('#ffff00');

// Default mixing - red dominates
let orange1 = spectral.mix([red, 0.5], [yellow, 0.5]);
console.log(orange1.toString()); // Outputs: #FF440F (very red-orange)

// Reduce red's tinting strength to balance the mixture:
let red = new spectral.Color('#ff0000');
let yellow = new spectral.Color('#ffff00');

// Reduce red's dominance
red.tintingStrength = 0.35;

let orange2 = spectral.mix([red, 0.5], [yellow, 0.5]);
console.log(orange2.toString()); // Outputs: #FF8427 (balanced orange)

// Lower tinting strength values (below 1.0) reduce a color's influence. 
// Higher values (above 1.0) increase it.

8. The spectral.glsl file provides GPU-based color mixing for WebGL projects. It supports mixing 2 to 4 colors with optional tinting strength parameters.

// Define colors as RGB vectors
vec3 yellow = vec3(0.9734, 0.8714, 0.0612);
vec3 red = vec3(0.7835, 0.0307, 0.1329);
vec3 blue = vec3(0.0331, 0.1779, 0.7011);

// Mix colors with factors
vec3 color = spectral_mix(
  yellow, 1.0, 1.0 - p.x,    // Yellow with decreasing influence
  red, 0.5, p.x - p.y,       // Red with custom tinting strength
  blue, 1.0, p.y             // Blue with increasing influence
);

9. Available read-only properties for color data:

  • color.R (Array): Spectral reflectance curve from 380 to 750 nm in 10 nm steps (38 values).
  • color.sRGB (Array): Standard RGB color space values [r, g, b] where each component is 0-1.
  • color.lRGB (Array): Linear RGB values before gamma correction.
  • color.XYZ (Array): CIE XYZ color space representation using D65 illuminant.
  • color.OKLab (Array): OKLab perceptual color space coordinates [L, a, b].
  • color.OKLCh (Array): OKLCh cylindrical representation [L, C, h] where h is hue angle.
  • color.KS (Array): Kubelka-Munk absorption/scattering parameters across the spectrum.
  • color.luminance (Number): Perceived brightness (Y value from CIE XYZ).
  • color.tintingStrength (Number): Mixing intensity of this pigment (default: 1.0).

10. API methods.

// Check if color is within displayable sRGB gamut
let inGamut = color.inGamut({ epsilon: 0.00001 });

// Bring out-of-gamut color into displayable range
// Uses chroma reduction in OKLCh space by default
let mapped = color.toGamut({ method: 'map' });

// Alternative: clip RGB values to 0-1 range (faster but less accurate)
let clipped = color.toGamut({ method: 'clip' });

// Convert color to string representation
// Automatically applies gamut mapping if needed
let hexString = color.toString(); // Default: hex format with 'map' method
let rgbString = color.toString({ format: 'rgb' }); // RGB format
let clippedHex = color.toString({ format: 'hex', method: 'clip' }); // Clipping method

FAQs:

Q: Why do my mixed colors look different from standard RGB blending?
A: Spectral.js simulates how physical pigments absorb and scatter light. RGB blending treats color as additive light. When you mix complementary colors (like blue and yellow) in RGB, you get gray. Spectral mixing produces green because it models pigment behavior.

Q: How do I prevent one color from dominating the mixture?
A: Adjust the tintingStrength property on the dominant color. Set it below 1.0 to reduce its influence. Red often dominates yellow in 50/50 mixtures. Setting red.tintingStrength = 0.35 balances the result.

Q: What happens when a mixed color is outside the sRGB gamut?
A: The toString() method automatically applies gamut mapping using OKLCh chroma reduction by default. This brings the color into displayable range while preserving perceived hue and lightness. You can use method: 'clip' for faster processing but less accurate results. Call inGamut() to check if a color needs adjustment.

The post Realistic Color Mixing Library Using Kubelka-Munk Theory – Spectral.js appeared first on CSS Script.


Discover more from RSS Feeds Cloud

Subscribe to get the latest posts sent to your email.

Discover more from RSS Feeds Cloud

Subscribe now to keep reading and get access to the full archive.

Continue reading