Introduction
Although windows seems to have some color management build into, none of this is found in any of the .NET classes, at least to my knowledge. Similarly, GDI and GDI+ contain almost no provisions for color manipulation or colorimetric image processing (there's is a color matrix, but no convolution or any more advanced image processing. It is also hard to operate on linear-light RGB values instead of gamma-corrected R*G*B* values). It seems Microsoft has no concern for the underlying principles and foundations of image processing and colorimetry, instead going for a simple pragmatic 'look I can adjust brightness or hue' type of approach ... Nothing wrong with that, but we expected more from a such a comprehensive framework as .NET (maybe we DO have to have a look at java and Java Advanced Imaging, or JAI, which seems built on a much more researched foundation). The net result (hehe) is that for my colorimetric imaging research I needed to write a support library, at first in VB6. This library has now been converted to VB.NET.
Background
An introduction to color is useful, check out Charles Poynton page to name but one.
Using the code
The library of mostly shared functions and subroutines and is not written for speed, instead I hope it is clear and easy to read. It makes heavy use of overloading and needs another class called Algebra to run, which is included in the download. Its main use is moving from one color space to another and applying gamma-correction using arrays of color triplets, but it also contains some routines for supporting image processing using unmanaged pointers, and GDI+. The supported color spaces are (a * in the notation means a non-linear light color space, e.g. after gamma-correction):
- Generic RGB and R*G*B*. User must, obviously, provide a transform to go to other color spaces here as there are literally 100's of RGB color spaces!
- ITU Rec 709 sRGB and sR*G*B*. A standard RGB color space used on the web, monitors and in printers.
- CIE XYZ. A human vision based tristimulus color space encompassing all visible colors.
- CIE L*a*b*. A perceputally uniform color space. In this space the Euclidean metric is proportional to color differences as perceived by a human observer, albeit under a rather restricted set of viewing conditions.
The library also defines several whitepoints
- CIE D65: Noon daylight simulation with color temperature of 6500 K
- CIE D55: Daylight simulation with color temperature of 5500 K
- CIE A: Tungsten illumination with color temperature of 3800 K
- CIE C: Fluorescent illumination with correlated color temperature about 5200 K
- CIE D93: Color temperature of 9300 K, similar to some computer monitor
- HDTVD65: High-definition television whitepoint at 6500 K
Several types of polynomial transforms between color spaces are supported, from a 3x3 linear transform to a highly non-linear, 20-term transform. The class also provides a way to compute this transform, given a set of colors in an input color space, and their desired mappings in an output color space (based on the singular value decomposition solution provided by the algebra class. Note that this routine was converted from 'Numerical Recipes in C', a well known set of numerical routines). The class also allows the use of a lookup-table or LUT to be applied prior to the polynomial transformation.
The simplest is to check out the code in the download
Sample code using the class
Here is a piece of C# code that runs through an image and transforms every pixel using a given polynomial transform and lookup table. This code also demonstrates the use of unmanaged pointers in C#, which greatly improves the speed over using managed GDI+ classes with GetPixel
and SetPixel
. This is not possible in VB.NET! It is assumed that the Color.vb class has been properly imported.
public static unsafe void PolynomialTransform(Bitmap objBitmap,
float[,]Transform, float[,] LUT )
{
int x, y, iPtrPixel0;
Rectangle rect = new Rectangle(0,0,objBitmap.Width,objBitmap.Height);
System.Drawing.Imaging.BitmapData bitmapData = objBitmap.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
iPtrPixel0 = bitmapData.Scan0.ToInt32();
byte * byPixel;
byte[] bySafePixel = new byte[3];
for (y = 0; y < bitmapData.Height; y ++)
{
byPixel = (byte *)(iPtrPixel0 + y * bitmapData.Stride);
for (x = 0; x < bitmapData.Width; x ++)
{
bySafePixel[2] = *byPixel;
bySafePixel[1] = *(byPixel + 1);
bySafePixel[0] = *(byPixel + 2);
Color.GammaRGBToGammasRGB(bySafePixel, Transform, LUT);
*byPixel = bySafePixel[2];
byPixel ++;
*byPixel = bySafePixel[1];
byPixel ++;
*byPixel = bySafePixel[0];
byPixel ++;
}
}
objBitmap.UnlockBits(bitmapData);
}
Points of Interest
No special points of intrest,except that even using unmanaged pointers, image processing in .NET is still pretty sloooow!