chromaticity-color-utilities
Color utilities for Node.js.
Conversion, modification, and color schemes of: RGB (at any bit depth), HSV, HSL, HSI, HSP, CYMK, YIQ, XYZ, xyY, L*a*b*, L*u*v*, Y'PbPr, Y'CbCr, and more.
Table of Contents
- Install
- Usage
- Color Types and Conversions
- RGB: Red, Green, Blue
- HEX: Hexidecimal
- Rec. 709 RGB : HD video standard
- Rec. 2020 RGB : UHD video standard
- HSV: Hue, Saturation, Value
- HSL: Hue, Saturation, Lightness
- HSI: Hue, Saturation, Intensity
- HSP: Hue, Saturation, Perceived Brightness
- CMYK: Cyan, Magenta, Yellow, Black
- YIQ: NTSC Color
- XYZ: CIE XYZ
- xyY: CIE xyY
- Lab: CIELAB / L*a*b*
- Luv: CIELUV / L*u*v*
- YPbPr: Analog video component signals
- YCbCr: Digital video component signals
- NM: Wavelengths of Light
- Kelvin: Color Temperature Approximation
- Color Spaces and Standard Illuminants
- Modifying Colors
- Color Scheme Generation
- Mathematics
- Compiling from Source
- To Do List
Install
npm install --save chromaticity-color-utilities
Usage
Any color can be converted to any other, with only a few caveats. Construction from()
, conversion to()
, and modification modify()
methods can be chained.
Object properties can be accessed directly, e.g. color.r
for the red channel value.
Most colors will retain their arguments as a part of their object properties, such as bitDepth, colorSpace, etc.
const Color = require('chromaticity-color-utilities')
let color1 = Color.from('rgb',[255,128,0]).to('hsv')
let color2 = Color.from('hex','ff3201').to('rec709rgb', { bitRate: 10 })
let color6 = Color.from('hex', 'ff00ff').to('lab',{
colorSpace: 'AdobeRGB',
referenceWhite: 'd50'
})
let color7 = Color.from('hsl',[300,100,50]).to('ypbpr',{kb:0.0722, kr:0.2126})
let color3 = Color.from('rgb',[255,0,0]).modify('blend', {with: Color.from('rgb',[0,255,0])})
let color4 = Color.from('rgb',[255,0,0]).modify('blend', {
with: Color.from('hex','00ff00'),
amount: 0.4
})
let color5 = Color.from('hex','ee5432').modify('blend', {
with: Color.from('rgb',[234, 100, 20, 64]),
amount: 1/3
}).to('hsv')
let scheme1 = Color.from('rgb',[200, 180, 0]).scheme('splitComplement')
let scheme2 = Color.from('hsl',[180, 80, 48]).scheme('tetradic', { angle: 40 })
Color Types and Conversions
For all of the following examples, the same color is used (magenta / 0xFF00FF) to create the color.
Alpha is optional when available. If not defined, it will default to the maximum value for the given bit depth. When converting to a space that does not support alpha, it is ignored. If converting back, alpha will be set to full opacity.
RGB : Red, Green, Blue
All values are between 0 and (2 ** bitDepth) - 1
. With a default bit depth of 8, values are within 0-255. A color with a bit depth of 16 will have values ranging from 0-65535.
** 8-bit color is sometimes referred to as 24-bit or 32-bit (8 bits per channel, with 32-bit including an alpha channel). This package uses the more correct implementation of 32-bit meaning 32 bits per channel, and so generally most use cases would fall between 8 and 16 bit color depth.
** A special note: Adobe uses 15+1 bit depth for 16-bit color, where the last bit is simply added to the first 15 bits.
Color.from('rgb',[r, g, b, a?],{
bitDepth: number
})
.to('rgb',{
bitDepth: number,
round: boolean
})
let color1 = Color.from('rgb',[255, 0, 255])
let color3 = color2.to('rgb')
let color4 = Color.from('rgb',[1023, 0, 1023], { bitDepth: 10 })
HEX : Hexidecimal
May use string or numerical value. Strings are case-insensitive. Short form or long form may be used. # ignored if present.
Color.from('hex',hex)
.to('hex')
let color1 = Color.from('hex', 'ff00ff')
let color1 = Color.from('hex', '#FF00FF')
let color1 = Color.from('hex', 0xFF00FF)
let color3 = color2.to('hex')
Rec. 709 RGB : HD video standard
Limits RGB color to video levels (16 - 235 for 8-bit or 64 to 940 for 10-bit). Input bit depth must be 8 or 10. Conversion to Y'PbPr and Y'CbCr will fail as this module does not yet have gamma adjustment implemented.
This method does not currently support data levels.
RGB values may fall outside limits.
Alpha channel maintains data levels (0 - 255 / 0 - 1023).
Color.from('rec709rgb',[r, g, b, a?], {
round: boolean,
bitDepth: number
})
.to('rec709rgb', {
round: boolean,
bitDepth: number
})
let color1 = Color.from('rec709rgb', [235, 16, 235])
let color1 = Color.from('rec709rgb', [940, 64, 940], { bitDepth: 10 })
let color3 = color2.to('rec709rgb')
let color3 = color2.to('rec709rgb', { bitDepth: 10 })
Rec. 2020 RGB : UHD video standard
Limits RGB color to video levels (64 to 940 for 10-bit or 256 to 3760 for 12-bit). Input bit depth must be 10 or 12. Conversion to Y'PbPr and Y'CbCr will fail as this module does not yet have gamma adjustment implemented.
This method does not currently support data levels.
RGB values may fall outside limits.
Alpha channel maintains data levels (0 - 1023 / 0 - 4096).
Color.from('rec2020rgb',[r, g, b, a?], {
round: boolean,
bitDepth: number
})
.to('rec2020rgb', {
round: boolean,
bitDepth: number
})
let color1 = Color.from('rec2020rgb', [940, 64, 940])
let color1 = Color.from('rec2020rgb', [3760, 256, 3760], { bitDepth: 12 })
let color3 = color2.to('rec2020rgb')
let color3 = color2.to('rec2020rgb', { bitDepth: 10 })
HSV : Hue, Saturation, Value
Hue value is between 0 and 360. Saturation, value, and alpha are between 0 and 100 (as in, percent).
Color.from('hsv',[h, s, v, a?])
.to('hsv',{
round: boolean
})
let color1 = Color.from('hsv',[300, 100, 100])
let color3 = color2.to('hsv')
HSL : Hue, Saturation, Lightness
Hue value is between 0 and 360. Saturation, lightness, and alpha are between 0 and 100 (as in, percent).
Color.from('hsl',[h, s, l, a?])
.to('hsl',{
round: boolean
})
let color1 = Color.from('hsl',[300, 100, 50])
let color3 = color2.to('hsl')
HSI : Hue, Saturation, Intensity
Hue value is between 0 and 360. Saturation, intensity, and alpha are between 0 and 100 (as in, percent).
Color.from('hsi',[h, s, v, a?])
.to('hsi',{
round: boolean
})
let color1 = Color.from('hsi',[300, 100, 67])
let color3 = color2.to('hsi')
HSP : Hue, Saturation, Perceived Brightness
The formula used to generate HSP is similar to the one Photoshop uses when converting images to greyscale.
Hue value is between 0 and 360. Saturation, perceived brightness, and alpha are between 0 and 100 (as in, percent).
When passing PR and PB values, PR + PG + PB must = 1.
By default,
- PR = 0.299
- PG = 0.587
- PB = 0.114
** This formula is not as accurate as most others, but does offer another way of adjusting brightness in an image.
Color.from('hsp',[h, s, p, a?],{
pb: number,
pr: number
})
.to('hsp',{
round: boolean,
pb: number,
pr: number
})
let color1 = Color.from('hsp',[300, 100, 65]).to('rgb')
let color3 = Color.from('rgb',[255, 0, 255]).to('hsp')
CMYK : Cyan, Magenta, Yellow, Black
All values are between 0 and 100 (as in, percent).
** Calculations do not take pigment conversion into account and should not be used to reference printed colors.
Color.from('cmyk',[c, m, y, k])
.to('cmyk',{
round: boolean
})
let color1 = Color.from('cmyk',[0, 100, 0, 0])
let color3 = color2.to('cmyk')
YIQ : NTSC Color
- Y = luma
- I = in-phase
- Q = quadrature
When normalized:
- Y is between 0 and 255
- I and Q are between -128 and 128
When not normalized:
- Y is between 0 and 1
- I is between -0.5957 and 0.5957
- Q is between -0.5226 and 0.5226
Color.from('yiq', [y, i, q], {
normalized: boolean
})
.to('yiq',{
normalize: boolean,
round: boolean
})
let color1 = Color.from('yiq',[105, 59, 128])
let color3 = color2.to('yiq')
let color4 = Color.from('yiq', [0.413, 0.2746, 0.5226], {normalized: false})
XYZ : CIE XYZ
All values are between 0 and 1. XYZ is only defined within the constraints of a color space and reference white point of a standard illuminant. If one is not given, sRGB and D65 are used as the color space and standard illuminant.
- X = mix of three CIE RGB curves chosen to be non-negative
- Y = luminance
- Z = quasi-equal to blue
It is not often useful to convert to XYZ, as XYZ defines real-world light and is typically then converted to a digital representation (most commonly RGB), but the functionality is present nonetheless.
Available Color Spaces and Stardard Illuminants below.
Color.from('xyz', [x, y, z], {
colorSpace: string,
referenceWhite: string
})
.to('xyz',{
colorSpace: string,
referenceWhite: string
})
let color1 = Color.from('xyz',[0.5928939, 0.2848479, 0.969638])
let color3 = color2.to('xyz')
let color4 = Color.from('xyz', [0.7589799, 0.3743439, 0.7643198], {
colorSpace: 'adobergb',
referenceWhite: 'd50'
})
xyY : CIE xyY
Derived from XYZ, x and y are chromaticity values while Y is the tristimulous value of a color.
Available Color Spaces and Stardard Illuminants below.
Color.from('xyy', [x, y, Y], {
colorSpace: string,
referenceWhite: string
})
.to('xyy',{
colorSpace: string,
referenceWhite: string
})
let color1 = Color.from('xyy',[0.3209377411185291, 0.1541902211986945, 0.2848479])
let color3 = color2.to('xyy')
let color4 = Color.from('xyz', [
0.39995913879719036,
0.1972677588141419,
0.3743439
], {
colorSpace: 'adobergb',
referenceWhite: 'd50'
})
Lab : CIELAB / L*a*b*
Derived from XYZ.
- L* = lightness
- a* = position between red and green (negative indicates green, positive red)
- b* = position between blue and yellow (negative indicates blue, positive yellow)
Available Color Spaces and Stardard Illuminants below.
Color.from('lab', [l, a, b], {
colorSpace: string,
referenceWhite: string
})
.to('lab',{
colorSpace: string,
referenceWhite: string
})
let color1 = Color.from('lab',[
60.32421212836874,
98.23431188800397,
-60.82489220885006
])
let color3 = color2.to('lab')
let color4 = Color.from('lab', [
67.60166164169028,
101.30709261827131,
-5.488771094285516
], {
colorSpace: 'adobergb',
referenceWhite: 'd50'
})
Luv : CIELUV / L*u*v*
Derived from XYZ. L* is identical to L* in L*a*b*
Available Color Spaces and Stardard Illuminants below.
Color.from('luv', [l, u, v], {
colorSpace: string,
referenceWhite: string
})
.to('luv',{
colorSpace: string,
referenceWhite: string
})
let color1 = Color.from('luv',[
60.32421212836874,
84.07139572483507,
-108.68333851910185
])
let color3 = color2.to('luv')
let color4 = Color.from('luv', [
67.60166164169028,
124.0201282170453,
-87.3117870588082
], {
colorSpace: 'adobergb',
referenceWhite: 'd50'
})
YPbPr : Analog video component signals
Also written Y'PbPr or YPBPR.
-
Y' = luma and sync (brightness/luminance and syncrhonization)
-
Pb = difference between blue and luma (B - Y)
-
Pr = difference between red and luma (R - Y)
-
Kb = constant defined from target color space, such that Kb + Kr + Kg = 1
-
Kr = constant defined from target color space, such that Kb + Kr + Kg = 1
Kb and Kr constants are not yet included in this package.
Color.from('ypbpr', [y, pb, pr], {
kb: number,
kr: number
})
.to('ypbpr',{
kb: number,
kr: number
})
.to('ycbcr',{
yLower: number,
yUpper: number,
cLower: number,
cUpper: number
})
let color1 = Color.from('ypbpr',[
0.2848,
0.3854278939426601,
0.45415290830581667
])
let color3 = color2.to('ypbpr',{
kb: 0.0722,
kr: 0.2126
})
let color4 = color1.to('ycbcr')
let color5 = color1.to('ycbcr',{
yLower: 0,
yUpper: 255,
cLower: 0,
cUpper: 255
})
YCbCr : Digital video component signals
Also written Y'CbCr, Y Pb/Cb Pr/Cr, YCBCR, or Y'CBCR.
- Y' = luma and sync (brightness/luminance and syncrhonization)
- Cb = difference between blue and luma (B - Y)
- Cr = difference between red and luma (R - Y)
YCbCr conversions require Kb and Kr constants with the exception of converting to YPbPr. These values are not yet included in this package.
- Kb = constant defined from target color space, such that Kb + Kr + Kg = 1
- Kr = constant defined from target color space, such that Kb + Kr + Kg = 1
Upper and lower bounds vary with color space. It's recommended to always supply these values.
Color.from('ycbcr', [y, cb, cr], {
yLower: number,
yUpper: number,
cLower: number,
cUpper: number
)
.to('ycbcr',{
kb: number,
kr: number
})
.to('ypbpr',{
yLower: number,
yUpper: number,
cLower: number,
cUpper: number
})
let color1 = Color.from('ycbcr', [73, 226, 243])
let color3 = color2.to('ycbcr',{
kb: 0.0722,
kr: 0.2126
})
let color4 = color1.to('ypbpr')
let color5 = color1.to('ypbpr',{
yLower: 0,
yUpper: 255,
cLower: 0,
cUpper: 255
})
NM : Wavelengths of light
This is a one-way approximation and is hugely perceptual. There is no .to('nm')
method option.
Color.from('nm', wavelength)
let color1 = Color.from('nm',600).to('rgb')
Kelvin : Color Temperature Approximation
This is a one-way approximatin. There is no .to('kelvin')
method option.
Color.from('kelvin', degrees)
let color1 = Color.from('kelvin',3500).to('rgb')
Color Spaces and Standard Illuminants
For conversion to and from XYZ, xyY, L*a*b*, and L*u*v*, the following color spaces and standard illuminants have XYZ transformation matrices and reference white point vectors available:
Color Space | Standard Illuminants |
---|
sRGB | D65, D50 |
CIE RGB | E, D50 |
Adobe RGB | D65, D50 |
Apple RGB | D65, D50 |
Best RGB | D50 |
Bruce RGB | D65, D50 |
ColorMatch RGB | D50 |
Don RGB 4 | D50 |
ECI RGB v2 | D50 |
Ekta Space PS5 | D50 |
NTSC RGB | C, D50 |
PAL / SECAM RGB | D65, D50 |
ProPhoto RGB | D50 |
SMPTE-C RGB | D65, D50 |
Wide Gamut RGB | D50 |
Color spaces and standard illuminant arguments are case-insensitive. Color space argument ignores any character not alphanumeric. Some common misspellings / words left out are also taken into account. (PAL / SECAM
is equivalent to palsecamrgb
.)
Modifying Colors
Blending Two Colors
When blending two colors, the amount ∈ [0,1] refers to the percentage the second color is blended with the first. In other words, 0 means 0% of the second color and 100% of the first while 1 means 100% of the second color and 0% of the first.
Blending methods include: rgb
, hsv
let color3 = color1.modify('blend', {
with: color2,
amount: number,
method: string
})
let color4 = Color.from('rgb',[255,0,0]).modify('blend', {
with: Color.from('hex','00ff00')
})
let color5 = Color.from('hex','ee5432').modify('blend', {
with: Color.from('rgb',[234, 100, 20, 64]),
amount: 1/3
}).to('hsv')
Darken
Methods available are: hsl
/lightness
, hsp
/perceived
These methods are intended to provide alternative ways of modifying a color versus changing the values directly, which can make more sense.
let color2 = color1.modify('darken', {
amount: number,
method: string
round: boolean
})
let color2 = Color.from('rgb',[255,0,255,200]).modify('darken',{method:'lightness'})
let color2 = Color.from('rgb',[100,0,100]).modify('darken',{method:'hsp'})
Lighten
Methods available are: hsl
/lightness
, hsp
/perceived
These methods are intended to provide alternative ways of modifying a color versus changing the values directly, which can make more sense.
let color2 = color1.modify('lighten', {
amount: number,
method: string
round: boolean
})
let color2 = Color.from('rgb',[255,0,255,200]).modify('lighten',{method:'lightness'})
let color2 = Color.from('rgb',[100,0,100]).modify('lighten',{method:'hsp'})
Saturate
Methods available are: hsv
, hsl
. The input color type does not matter.
These methods are intended to provide alternative ways of modifying a color versus changing the values directly, which can make more sense.
let color2 = color1.modify('saturate', {
amount: number,
method: string
round: boolean
})
let color2 = Color.from('rgb',[128,64,128,200]).modify('saturate','hsl')
let color2 = Color.from('rgb',[128,64,128,200]).modify('saturate','hsv')
Desaturate
Methods available are: hsv
, hsl
. The input color type does not matter.
These methods are intended to provide alternative ways of modifying a color versus changing the values directly, which can make more sense.
let color2 = color1.modify('saturate', {
amount: number,
method: string
round: boolean
})
let color2 = Color.from('rgb',[255,0,255,200]).modify('desaturate','hsl')
let color2 = Color.from('rgb',[255,0,255,200]).modify('desaturate','hsl')
Color Scheme Generation
Schemes can be generated from any color type. All methods return an array of colors, each the same as the input type. (If calling method on a color of type hsl
, all values of the returned array will be of type hsl
.)
.scheme(type: string)
.scheme(type: string, {
angle: number
})
Complementary Schemes
Complementary color scheme generation has a fixed angle of 180°.
.scheme('complement')
let color1 = Color.from('rgb',[255,0,255]).scheme('complement')
Analogous, Triadic, & Split Complement Schemes
These three methods are synonyms with different default angles.
.scheme('analogous', {
angle: number
})
.scheme('triadic', {
angle: number
})
.scheme('splitcomplement', {
angle: number
})
let color1 = Color.from('rgb',[255,0,255]).scheme('analogous')
let color2 = Color.from('rgb',[255,0,255]).scheme('triadic')
let color3 = Color.from('rgb',[255,0,255]).scheme('splitcomplement',{angle: 160})
Tetradic & Square Schemes
These two methods are synonyms, but that the square method has a fixed angle of 90°.
.scheme('tetradic', {
angle: number
})
.scheme('square')
let color1 = Color.from('rgb',[255,0,255]).scheme('tetradic',{angle: 42})
let color2 = Color.from('rgb',[255,0,255]).scheme('square')
Tint Scale
todo / not yet implemented
.scheme('tint',{
length: number,
distance: number
})
Shade Scale
todo / not yet implemented
.scheme('shade',{
length: number,
distance: number
})
Tint & Shade Scale
todo / not yet implemented
.scheme('tintshade',{
length: number,
distance: number
})
Mathematics
The following are the formulae used in the conversion algorithms. For succinctness, consider all values normalized ∈ [0, 1] unless stated otherwise.
Normalizing RGB
to achieve R,G,B ∈ [0, 1]
RGB to HSV
C references chroma
HSV to RGB
RGB to HSL
C references chroma
HSL to RGB
C references chroma
RGB to HSI
C references chroma
HSI to RGB
C references chroma
RGB to HSP
Where P is perceived brightness. This algorithm is similar to the one Photoshop uses when converting images to greyscale.
If no values are passed, the default weight for P is as follows:
HSP to RGB
Where P is perceived brightness. This algorithm is similar to the one Photoshop uses when converting images to greyscale.
If no values are passed, the default weight for P is as follows:
HSV to HSL
HSL to HSV
RGB to CMYK
CMYK to RGB
RGB to YIQ
YIQ to RGB
RGB to XYZ
M = 3x3 RGB to XYZ transformation matrix based on color space and standard illuminant reference white. This transformation matrix is an inverse of the XYZ to RGB transformation matrix.
sRGB
L*
Other color spaces
gamma (γ) based on target color space
XYZ to RGB
M = 3x3 XYZ to RGB transformation matrix based on color space and standard illuminant reference white.
sRGB
L*
Other color spaces
gamma (γ) based on target color space
XYZ to xyY
xyY to XYZ
XYZ to L*a*b*
W is a 1x3 reference white vector based on standard illuminant.
L*a*b* to XYZ
W is a 1x3 reference white vector based on standard illuminant.
XYZ to L*u*v*
W is a 1x3 reference white vector based on standard illuminant
L*u*v* to XYZ
W is a 1x3 reference white vector based on standard illuminant
RGB to YPbPr
Kb and Kr constants defined from target color space
YPbPr to YCbCr
Scaling bounds given by conversion method / target space. Typical bounds might be 0-255 for all values for JPEG target or 16-235 for Y and 16-245 for Cb and Cr for Rec. 709 target.
YCbCr to YPbPr
Y is scaled to 0-1, Cb and Cr are scaled such that Pb and Pr are between -0.5 and 0.5.
YPbPr to RGB
Kb and Kr constants defined from target color space
Compiling from Source
git clone https://github.com/reiniiriarios/chromaticity-color-utilities.git
cd chromaticity-color-utilities
npm install
tsc
To Do List
- Gamma adjustment modification
- Auto-gamma adjustment and conversion for rec709, rec2020, and jpeg to/from ypbpr
- note to self: rec709 does gamma conversion before while rec2020 does gamma conversion after when converting to ypbpr (I think)
- Create color schemes / gradient schemes based on tints and shades
- Generate gradients given two colors
- Generate triangular gradients based on three colors
- Modification methods that retain luma
Review:
http://www.physics.sfasu.edu/astro/color/blackbodyc.txt
References
todo: clean this up
Recommendation ITU-R BT.709-6, Parameter values for the HDTV standards for production and international programme exchange,
https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
Recommendation ITU-R BT.601-7, Studio encoding parameters of digital television for standard 4:3and wide-screen 16:9 aspect ratios,
https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
Computing RGB-to-XYZ and XYZ-to-RGB matrices,
http://www.brucelindbloom.com
Approximate RGB values for Visible Wavelengths,
http://www.physics.sfasu.edu/astro/color/spectra.html
Converting temperature (Kelvin) to RGB,
https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html