---
date: Jun 12, 2016
tags:
- C#
language:
English
---
This post is more than 7 years old. If this is a technical post, the post will most likely not working, but feel free to try it and see if it works.
Recently, i am doing a research on a project to convert RGB to HSB, which leads me to an awesome article on CodeProject from 9 years ago. Hence, I sort the article a little bit to help myself understand more of color agorithm.
"Color is the visual perceptual property corresponding in humans to the categories called red, yellow, white, etc. Color derives from the spectrum of light (distribution of light energy versus wavelength) interacting in the eye with the spectral sensitivities of the light receptors. Color categories and physical specifications of color are also associated with objects, materials, light sources, etc., based on their physical properties such as light absorption, reflection, or emission spectra.
Colorimetry is the science that describes colors in numbers, or provides a physical color match using a variety of measurement instruments. Colorimetry is used in chemistry, and in industries such as color printing, textile manufacturing, paint manufacturing and in the food industry.
Then, how can we display colors as numbers? the answer: color models.
Color Models
RGB (Red Green Blue)
The RGB (Red, Green, Blue) color model is the most known, and the most used every day. It defines a color space in terms of three components:
Red, which ranges from 0-255
Green, which ranges from 0-255
Blue, which ranges from 0-255
The RGB color model is an additive one. In other words, Red, Green and Blue values (known as the three primary colors) are combined to reproduce other colors.
For example, the color “Red” can be represented as [R=255, G=0, B=0], “Violet” as [R=238, G=130, B=238], etc.
Its common graphic representation is the following image:
RGB
In .NET, the Color structure use this model to provide color support through R, G and B properties.
///<summary> /// RGB structure. ///</summary> publicstruct RGB { ///<summary> /// Gets an empty RGB structure; ///</summary> publicstaticreadonly RGB Empty = new RGB(); privateint red; privateint green; privateint blue; publicstaticbooloperator ==(RGB item1, RGB item2) { return ( item1.Red == item2.Red && item1.Green == item2.Green && item1.Blue == item2.Blue ); } publicstaticbooloperator !=(RGB item1, RGB item2) { return ( item1.Red != item2.Red || item1.Green != item2.Green || item1.Blue != item2.Blue ); } ///<summary> /// Gets or sets red value. ///</summary> publicint Red { get { return red; } set { red = (value>255)? 255 : ((value<0)?0 : value); } } ///<summary> /// Gets or sets red value. ///</summary> publicint Green { get { return green; } set { green = (value>255)? 255 : ((value<0)?0 : value); } } ///<summary> /// Gets or sets red value. ///</summary> publicint Blue { get { return blue; } set { blue = (value>255)? 255 : ((value<0)?0 : value); } } publicRGB(int R, int G, int B) { this.red = (R>255)? 255 : ((R<0)?0 : R); this.green = (G>255)? 255 : ((G<0)?0 : G); this.blue = (B>255)? 255 : ((B<0)?0 : B); } publicoverrideboolEquals(Object obj) { if(obj==null || GetType()!=obj.GetType()) returnfalse; return (this == (RGB)obj); } publicoverrideintGetHashCode() { return Red.GetHashCode() ^ Green.GetHashCode() ^ Blue.GetHashCode(); } }
HSB color space
The HSB (Hue, Saturation, Brightness) color model defines a color space in terms of three constituent components:
Hue : the color type (such as red, blue, or yellow).
Ranges from 0 to 360° in most applications. (each value corresponds to one color : 0 is red, 45 is a shade of orange and 55 is a shade of yellow).
Saturation : the intensity of the color.
Ranges from 0 to 100% (0 means no color, that is a shade of grey between black and white; 100 means intense color).
Also sometimes called the “purity” by analogy to the colorimetric quantities excitation purity.
Brightness (or Value) : the brightness of the color.
Ranges from 0 to 100% (0 is always black; depending on the saturation, 100 may be white or a more or less saturated color).
Its common graphic representation is the following image:
HSB Cone
The HSB model is also known as HSV (Hue, Saturation, Value) model. The HSV model was created in 1978 by Alvy Ray Smith. It is a nonlinear transformation of the RGB color space. In other words, color is not defined as a simple combination (addition/substraction) of primary colors but as a mathematical transformation.
Note:HSV and HSB are the same, but HSL is different.
The HSL color space, also called HLS or HSI, stands for:
Hue : the color type (such as red, blue, or yellow).
Ranges from 0 to 360° in most applications (each value corresponds to one color : 0 is red, 45 is a shade of orange and 55 is a shade of yellow).
Saturation : variation of the color depending on the lightness.
Ranges from 0 to 100% (from the center of the black&white axis).
Lightness (also Luminance or Luminosity or Intensity).
Ranges from 0 to 100% (from black to white). Its common graphic representation is the following image:
The HSL cone; Wikipedia image.
HSL is similar to HSB. The main difference is that HSL is symmetrical to lightness and darkness. This means that:
In HSL, the Saturation component always goes from fully saturated color to the equivalent gray (in HSB, with B at maximum, it goes from saturated color to white).
In HSL, the Lightness always spans the entire range from black through the chosen hue to white (in HSB, the B component only goes half that way, from black to the chosen hue).
For my part, HSL offers a more accurate (even if it’s not absolute) color approximation than HSB. All this said, a HSL structure can be:
The CMYK color space, also known as CMJN, stands for:
Cyan.
Ranges from 0 to 100% in most applications.
Magenta.
Ranges from 0 to 100% in most applications.
Yellow.
Ranges from 0 to 100% in most applications.
blacK.
Ranges from 0 to 100% in most applications.
It is a subtractive color model used in color printing.CMYK works on an optical illusion that is based on light absorption.
The principle is to superimpose three images; one for cyan, one for magenta and one for yellow; which will reproduce colors. Its common graphic representation is the following image:
CMYK
Like the RGB color model, CMYK is a combination of primary colors (cyan, magenta, yellow and black). It is, probably, the only thing they have in common.
CMYK suffers from a lack of color shades that causes holes in the color spectrum it can reproduce. That’s why there are often differencies when someone convert a color between CMYK to RGB.
Why using this model? Why black is used? you can tell me… Well it’s only for practical purpose. Wikipedia said:
To improve print quality and reduce moiré patterns,
Text is typically printed in black and includes fine detail (such as serifs); so to reproduce text - using three inks would require an extremely precise alignment for each three components image.
A combination of cyan, magenta, and yellow pigments don’t produce (or rarely) pure black.
Mixing all three color inks together to make black can make the paper rather wet when not using dry toner, which is an issue in high speed printing where the paper must dry extremely rapidly to avoid marking the next sheet, and poor quality paper such as newsprint may break if it becomes too wet.
Using a unit amount of black ink rather than three unit amounts of the process color inks can lead to significant cost savings (black ink is often cheaper).
Let’s come back to our reality. A CMYK structure can be:
///<summary> /// Structure to define CMYK. ///</summary> publicstruct CMYK { ///<summary> /// Gets an empty CMYK structure; ///</summary> publicreadonlystatic CMYK Empty = new CMYK(); privatedouble c; privatedouble m; privatedouble y; privatedouble k; publicstaticbooloperator ==(CMYK item1, CMYK item2) { return ( item1.Cyan == item2.Cyan && item1.Magenta == item2.Magenta && item1.Yellow == item2.Yellow && item1.Black == item2.Black ); } publicstaticbooloperator !=(CMYK item1, CMYK item2) { return ( item1.Cyan != item2.Cyan || item1.Magenta != item2.Magenta || item1.Yellow != item2.Yellow || item1.Black != item2.Black ); } publicdouble Cyan { get { return c; } set { c = value; c = (c>1)? 1 : ((c<0)? 0 : c); } } publicdouble Magenta { get { return m; } set { m = value; m = (m>1)? 1 : ((m<0)? 0 : m); } } publicdouble Yellow { get { return y; } set { y = value; y = (y>1)? 1 : ((y<0)? 0 : y); } } publicdouble Black { get { return k; } set { k = value; k = (k>1)? 1 : ((k<0)? 0 : k); } } ///<summary> /// Creates an instance of a CMYK structure. ///</summary> publicCMYK(double c, double m, double y, double k) { this.c = c; this.m = m; this.y = y; this.k = k; } publicoverrideboolEquals(Object obj) { if(obj==null || GetType()!=obj.GetType()) returnfalse; return (this == (CMYK)obj); } publicoverrideintGetHashCode() { return Cyan.GetHashCode() ^ Magenta.GetHashCode() ^ Yellow.GetHashCode() ^ Black.GetHashCode(); }
}
YUV color space
The YUV model defines a color space in terms of one luma and two chrominance components. The YUV color model is used in the PAL, NTSC, and SECAM composite color video standards.
YUV models human perception of color more closely than the standard RGB model used in computer graphics hardware.
The YUV color space stands for:
Y, the luma component, or the brightness.
Ranges from 0 to 100% in most applications.
U and V are the chrominance components (blue-luminance and red-luminance differences components).
Expressed as factors depending on the YUV version you want to use.
///<summary> /// Structure to define YUV. ///</summary> publicstruct YUV { ///<summary> /// Gets an empty YUV structure. ///</summary> publicstaticreadonly YUV Empty = new YUV(); privatedouble y; privatedouble u; privatedouble v; publicstaticbooloperator ==(YUV item1, YUV item2) { return ( item1.Y == item2.Y && item1.U == item2.U && item1.V == item2.V ); } publicstaticbooloperator !=(YUV item1, YUV item2) { return ( item1.Y != item2.Y || item1.U != item2.U || item1.V != item2.V ); } publicdouble Y { get { return y; } set { y = value; y = (y>1)? 1 : ((y<0)? 0 : y); } } publicdouble U { get { return u; } set { u = value; u = (u>0.436)? 0.436 : ((u<-0.436)? -0.436 : u); } } publicdouble V { get { return v; } set { v = value; v = (v>0.615)? 0.615 : ((v<-0.615)? -0.615 : v); } } ///<summary> /// Creates an instance of a YUV structure. ///</summary> publicYUV(double y, double u, double v) { this.y = (y>1)? 1 : ((y<0)? 0 : y); this.u = (u>0.436)? 0.436 : ((u<-0.436)? -0.436 : u); this.v = (v>0.615)? 0.615 : ((v<-0.615)? -0.615 : v); } publicoverrideboolEquals(Object obj) { if(obj==null || GetType()!=obj.GetType()) returnfalse; return (this == (YUV)obj); } publicoverrideintGetHashCode() { return Y.GetHashCode() ^ U.GetHashCode() ^ V.GetHashCode(); }
}
CIE XYZ color space
In opposition to the previous models, the CIE XYZ model defines an absolute color space. It is also known as the CIE 1931 XYZ color space and stands for:
X, which can be compared to red
Ranges from 0 to 0.9505
Y, which can be compared to green
Ranges from 0 to 1.0
Z, which can be compared to blue
Ranges from 0 to 1.089
Before trying to explain why I include this color space in this article, you have to know that it’s one of the first standards created by the International Commission on Illumination (CIE) in 1931. It is based on direct measurements of the human eye, and serves as the basis from which many other color spaces are defined.
I have made a quick research to include Cie L*ab color model in this article, and I find that a conversion to an absolute color space is required before converting to L*ab. The model used in the conversion principle is Cie XYZ. So, I’ve included it and now everyone can understand “what are those XYZ values” used further in the article.
CIE Lab color space
A Lab color space is a color-opponent space with dimension L for luminance and a and b for the color-opponent dimensions, based on nonlinearly-compressed CIE XYZ color space coordinates."
As said in the previous definition, CIE Lab color space, also know as CIE 1976 color space, stands for:
L*, the luminance
a*, the red/green color-opponent dimension
b* , the yellow/blue color-opponent dimension
The Lab color model has been created to serve as a device independent model to be used as a reference. It is based directly on the CIE 1931 XYZ color space as an attempt to linearize the perceptibility of color differences.
The non-linear relations for L*, a*, and b* are intended to mimic the logarithmic response of the eye, coloring information is referred to the color of the white point of the system.
///<summary> /// Gets or sets L component. ///</summary> publicdouble L { get { returnthis.l; } set { this.l = value; } } ///<summary> /// Gets or sets a component. ///</summary> publicdouble A { get { returnthis.a; } set { this.a = value; } } ///<summary> /// Gets or sets a component. ///</summary> publicdouble B { get { returnthis.b; } set { this.b = value; } } publicCIELab(double l, double a, double b) { this.l = l; this.a = a; this.b = b; } publicoverrideboolEquals(Object obj) { if(obj==null || GetType()!=obj.GetType()) returnfalse; return (this == (CIELab)obj); } publicoverrideintGetHashCode() { return L.GetHashCode() ^ a.GetHashCode() ^ b.GetHashCode(); }
}
There are still many other formats like RYB and CcMmYK. I still don’t intend to create a “color framework”(so do I), but if you have other ideas…
Conversion between models
A - RGB Conversions
Converting RGB color to any other model is the basis in conversion algorithms. It implies a normalisation of red, green and blue : value ranges now from [0…255] to [0…1].
a - RGB to HSB
The conversion principle is the one below:
H ? [0, 360]
S, V, R, G, B ? [0, 1]
RGB to HSB
RGB to HSB
V = MAX
Well! Interesting! But what’s the C# equivalent? Here it is.
///<summary> /// Converts RGB to HSL. ///</summary> ///<param name="red">Red value, must be in [0,255].</param> ///<param name="green">Green value, must be in [0,255].</param> ///<param name="blue">Blue value, must be in [0,255].</param> publicstatic HSL RGBtoHSL(int red, int green, int blue) { double h=0, s=0, l=0;
// normalize red, green, blue values double r = (double)red/255.0; double g = (double)green/255.0; double b = (double)blue/255.0; double max = Math.Max(r, Math.Max(g, b)); double min = Math.Min(r, Math.Min(g, b)); // hue if(max == min) { h = 0; // undefined } elseif(max==r && g>=b) { h = 60.0*(g-b)/(max-min); } elseif(max==r && g<b) { h = 60.0*(g-b)/(max-min) + 360.0; } elseif(max==g) { h = 60.0*(b-r)/(max-min) + 120.0; } elseif(max==b) { h = 60.0*(r-g)/(max-min) + 240.0; } // luminance l = (max+min)/2.0; // saturation if(l == 0 || max == min) { s = 0; } elseif(0<l && l<=0.5) { s = (max-min)/(max+min); } elseif(l>0.5) { s = (max-min)/(2 - (max+min)); //(max-min > 0)? } returnnew HSL( Double.Parse(String.Format("{0:0.##}", h)), Double.Parse(String.Format("{0:0.##}", s)), Double.Parse(String.Format("{0:0.##}", l)) ); }
Note: You have probably noticed String.Format in the final line. It’s the .NET solution for keeping the same rounding behavior. If you don’t understand what I mean, try the sample code below:
You didn’t notice a problem? Ok, rouding 4.45 should have returned 4.5 and not 4.4. The solution is using String.Format() which always applies “round-to-even” method.
c - RGB to CMYK
The conversion principle is the one below :
R, G, B ? [0, 1]
t**C’M’Y’ = {1 - R, 1 - G, 1 - B}
K = min{C’, M’, Y’}
t**CMYK = {0, 0, 0, 1}
if K = 1tCMYK = { (C’ - K)/(1 - K), (M’ - K)/(1 - K), (Y’ - K)/(1 - K), K } otherwise
///<summary> /// Converts RGB to YUV. ///</summary> ///<param name="red">Red must be in [0, 255].</param> ///<param name="green">Green must be in [0, 255].</param> ///<param name="blue">Blue must be in [0, 255].</param> publicstatic YUV RGBtoYUV(int red, int green, int blue) { YUV yuv = new YUV();
// normalizes red, green, blue values double r = (double)red/255.0; double g = (double)green/255.0; double b = (double)blue/255.0; yuv.Y = 0.299*r + 0.587*g + 0.114*b; yuv.U = -0.14713*r -0.28886*g + 0.436*b; yuv.V = 0.615*r -0.51499*g -0.10001*b; return yuv; }
e - RGB to web color
Haaa! Something I can explain.
As you probably already know, web colors can be defined in two ways: for example, “red” can be defined as rgb(255,0,0) or #FF0000.
The explanation of the second form is simple :
“#” character tells that the format is the hexadecimal one.
The last 6 characters define 3 pairs: one for “Red”, one for “Green” and one for “Blue”.
Each pair is a hexadecimal value (base 16) of a value which ranges from 0 to 255.
So, you can divide each color component by 16 and replace numbers superior to 9 by theirs hexadecimal value (eg. 10 = A, 11 = B, etc.)… but the best way is to use String.Format() habilities.
1 2 3 4 5 6 7 8 9 10
///<summary> /// Converts a RGB color format to an hexadecimal color. ///</summary> ///<param name="r">Red value.</param> ///<param name="g">Green value.</param> ///<param name="b">Blue value.</param> publicstaticstringRGBToHex(int r, int g, int b) { return String.Format("#{0:x2}{1:x2}{2:x2}", r, g, b).ToUpper(); }
As I said before, converting to the CIE Lab color model is a little bit tricky: we need to convert to CIE XYZ before trying to have Lab values.
1 2 3 4 5 6 7
///<summary> /// Converts RGB to CIELab. ///</summary> publicstatic CIELab RGBtoLab(int red, int green, int blue) { return XYZtoLab( RGBtoXYZ(red, green, blue) ); }
The conversion between XYZ and Lab is given below.
B - HSB conversions
a - HSB to RGB
The conversion principle is the one below :
H ? [0, 360]
S, V, R, G, B ? [0, 1]
Hi = [H / 60] mod 6
f = (H / 60) - Hi
p = V (1 - S)
q = V (1 - f S)
t = V (1 - (1 - f ) S)
if Hi = 0 ? R = V, G = t, B = p if Hi = 1 ? R = q, G = V, B = p if Hi = 2 ? R = p, G = V, B = t if Hi = 3 ? R = p, G = q, B = V if Hi = 4 ? R = t, G = p, B = V if Hi = 5 ? R = V, G = p, B = q
///<summary> /// Converts HSB to RGB. ///</summary> publicstatic RGB HSBtoRGB(double h, double s, double b) { double r = 0; double g = 0; double b = 0; if(s == 0) { r = g = b = b; } else { // the color wheel consists of 6 sectors. Figure out which sector // you're in. double sectorPos = h / 60.0; int sectorNumber = (int)(Math.Floor(sectorPos)); // get the fractional part of the sector double fractionalSector = sectorPos - sectorNumber; // calculate values for the three axes of the color. double p = b * (1.0 - s); double q = b * (1.0 - (s * fractionalSector)); double t = b * (1.0 - (s * (1 - fractionalSector))); // assign the fractional colors to r, g, and b based on the sector // the angle is in. switch(sectorNumber) { case0: r = b; g = t; b = p; break; case1: r = q; g = b; b = p; break; case2: r = p; g = b; b = t; break; case3: r = p; g = q; b = b; break; case4: r = t; g = p; b = b; break; case5: r = b; g = p; b = q; break; } } returnnew RGB( Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", r*255.0)) ), Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", g*255.0)) ), Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", b*255.0)) ) ); }
b - HSB to HSL
Nothing new: conversion principle is to convert to RGB and then to HSB.
///<summary> /// Converts CMYK to RGB. ///</summary> publicstatic Color CMYKtoRGB(double c, double m, double y, double k) { int red = Convert.ToInt32((1-c) * (1-k) * 255.0); int green = Convert.ToInt32((1-m) * (1-k) * 255.0); int blue = Convert.ToInt32((1-y) * (1-k) * 255.0); return Color.FromArgb(red, green, blue); }
b - CMYK to HSL
Nothing new: conversion principle is to convert to RGB and then to HSL.
1 2 3 4 5 6 7 8 9
///<summary> /// Converts CMYK to HSL. ///</summary> publicstatic HSL CMYKtoHSL(double c, double m, double y, double k) { RGB rgb = CMYKtoRGB(c, m, y, k);
return RGBtoHSL(rgb.Red, rgb.Green, rgb.Blue); }
c - CMYK to HSB
Nothing new: conversion principle is to convert to RGB and then to HSB.
1 2 3 4 5 6 7 8 9
///<summary> /// Converts CMYK to HSB. ///</summary> publicstatic HSB CMYKtoHSB(double c, double m, double y, double k) { RGB rgb = CMYKtoRGB(c, m, y, k);
return RGBtoHSB(rgb.Red, rgb.Green, rgb.Blue); }
d - CMYK to YUV
Nothing new: conversion principle is to convert to RGB and then to YUV.
1 2 3 4 5 6 7 8 9
///<summary> /// Converts CMYK to YUV. ///</summary> publicstatic YUV CMYKtoYUV(double c, double m, double y, double k) { RGB rgb = CMYKtoRGB(c, m, y, k);
///<summary> /// Converts YUV to RGB. ///</summary> ///<param name="y">Y must be in [0, 1].</param> ///<param name="u">U must be in [-0.436, +0.436].</param> ///<param name="v">V must be in [-0.615, +0.615].</param> publicstatic RGB YUVtoRGB(double y, double u, double v) { RGB rgb = new RGB();
Nothing new: conversion principle is to convert to RGB and then to HSL.
1 2 3 4 5 6 7 8 9 10 11 12
///<summary> /// Converts YUV to HSL. ///</summary> ///<param name="y">Y must be in [0, 1].</param> ///<param name="u">U must be in [-0.436, +0.436].</param> ///<param name="v">V must be in [-0.615, +0.615].</param> publicstatic HSL YUVtoHSL(double y, double u, double v) { RGB rgb = YUVtoRGB(y, u, v);
return RGBtoHSL(rgb.Red, rgb.Green, rgb.Blue); }
c - YUV to HSB
Nothing new: conversion principle is to convert to RGB and then to HSB.
1 2 3 4 5 6 7 8 9 10 11 12
///<summary> /// Converts YUV to HSB. ///</summary> ///<param name="y">Y must be in [0, 1].</param> ///<param name="u">U must be in [-0.436, +0.436].</param> ///<param name="v">V must be in [-0.615, +0.615].</param> publicstatic HSB YUVtoHSB(double y, double u, double v) { RGB rgb = YUVtoRGB(y, u, v);
return RGBtoHSB(rgb.Red, rgb.Green, rgb.Blue); }
d - YUV to CMYK
Nothing new: conversion principle is to convert to RGB and then to CMYK.
1 2 3 4 5 6 7 8 9 10 11 12
///<summary> /// Converts YUV to CMYK. ///</summary> ///<param name="y">Y must be in [0, 1].</param> ///<param name="u">U must be in [-0.436, +0.436].</param> ///<param name="v">V must be in [-0.615, +0.615].</param> publicstatic CMYK YUVtoCMYK(double y, double u, double v) { RGB rgb = YUVtoRGB(y, u, v);
Nothing really new, the principle is to convert to XYZ and then to RGB :
1 2 3 4 5 6 7
///<summary> /// Converts CIELab to RGB. ///</summary> publicstatic RGB LabtoRGB(double l, double a, double b) { return XYZtoRGB( LabtoXYZ(l, a, b) ); }
Using the code
Well, after showing you the conversion algorithms, maybe there is nothing more I can tell you.
In fact, there are many other useful methods in ColorSpaceHelper. You will find:
Average color implementation (ColorSpaceHelper.GetColorDistance()).
Wheel color generation (ColorSpaceHelper.GetWheelColors()) with 32bit support (alpha).
Light spectrum color generation for (ColorSpaceHelper.GetSpectrumColors()) with 32bit support (alpha).
Conversion to and from web colors (ColorSpaceHelper.HexToColor()).
Conversion to and from System.Drawing.Color and the other structures.
Also, I am planning to write a C# class for colors for easier use in UWP and WPF apps. Hope you like it! link(not finished)