Friday, August 31, 2007

Spyder2 device under Linux

Just wanted to mention that there's an effort going on for making the Spyder device work under Linux. This guy came already quite close and can read measurements from the device - however, the data format seems to be unclear at the moment of writing these lines. Here's the URL btw.:

Tuesday, August 28, 2007

Ideas on "remote monitor profiling"

Recently I was exchanging e-mails with some french fellows that use xcalib under Linux in a professional environment. They are using a remote monitor profiling trick.

Okay, how does it basically work:
  1. They have one Windows laptop, running a ICC profile generation software. On this laptop a VNC server is running. A colorimeter is attached.
  2. There' the Linux PC with the to-be-profiled monitor attached. It has a VNC client installed, displaying the Windows screen.
They are then running the profile generation software and get a valid profile for the Linux PC out of this process.

I'm still not completely sure whether xcalib should be involved somewhere in this process (I tend to think that the Linux PC is not calibrated when the attached display is profiled). But I have an idea:

What about intercepting the Windows calls to

BOOL WINAPI GetDeviceGammaRamp(HDC hDC, LPVOID lpRamp );

BOOL WINAPI SetDeviceGammaRamp(HDC hDC, LPVOID lpRamp );

Whenver one of these Win32-API calls is issued by the calibration software, something like an RPC is issued to the Linux PC which then sets the LUTs there. So what I'm trying to say is: The profiling software is running on the Windows laptop but calibrates and profiles the Linux PC and the attached monitor.

Since I'm not too familiar with RPC I would probably do it with ssh - the Windows PC has the right key to connect to all to-be-profiled Linux-PCs and calls xcalib there. Okay, xcalib can't do it currently, but that's not a big deal to extend it for a read from stdin feature.

The same with intercepting: I have never done it before. Don't know whether it's easy to do it. But as soon as time permits I will probably try it out.

Monday, August 27, 2007


I just came across a new program called mcalib (hm, does this have to do something with xcalib?).

It is a calibration utility capable of calibrating monitors with the Pantone Huey colorimeter. This guy reverse-engineered the USB protocol of the Huey. Good job!

I must admit that I have no idea how good the Huey really is - but it's relatively cheap and I will probably go for one and extend my collection of colorimeters.

So far no real colorimetry is involved and mcalib only uses the video LUTs for making the display linear and so on ... But it's a good foundation and the code could be possibly integrated into ArgyllCMS.

Here's the link btw.:

xcalib v0.8 release

Some french fellows mailed me that xcalib doesnt work with high-end video cards which use >8bit LUTs. Okay, I had to add interpolation.

Before this happened bleader and gl.tter sent me patches for multi-monitor support. Now ATI FGLRX users and Windows users should benefit from these patches and be able to set the LUTs per display (if the hardware supports this).

This resulted in a new xcalib version that you can download from

Monday, August 13, 2007

Mozilla adds color-management

Good news! Mozilla added color-management to Firefox 3.0a7. I instantly downloaded this version and tried it with my Win32 laptop: Hey, it works! There's a test on the ICC website ( ) and Firefox passed it.

I was looking a little into the code changes and they are using LittleCMS for color-correction. So how can it be enabled?

Open about:config in Firefox 3.0a7 and set gfx.color_management.enabled to true.

Then either the monitor profile set in gfx.color_management.display_profile is used or then as a fallback the one provided by the system (already implemented?) and if even then no profile was found the sRGB profile integrated into LCMS is used.

There's a bug asscoiated with this new feature: . There a lot of information can be found of what is working and what not - I will read it completely later ;-)

One of the problems seems to be that plugins (Flash!) are currently not aware of it and so it may be hard to get the same color in HTML and Flash.

CVSpyder.dll API description

For quite a while I own a Spyder2 colorimeter. I mainly use it for color-calibration with the provided software but was always keen on using it together with open-source software.

There is an open-source project called ColorimetreHCFR (a french home-cinema-enthusiasts TV calibration software). They are (ab)using CVSpyder.dll, a Win32-dynamic-link-library that comes with some Colorvision products. With this dll an external program can access the colorimeter and make measurements.

I digged a little deeper and found the first project doing so: S2Fly . This program just makes reads and displays the measured values on the screen. However, the author seems to be no more active.

But then few weeks ago I found ColorAxis - a small program which makes reads using this dll and is open-source. Unfortunately it's written in VisualBasic - a programming language I'm not at all familiar with. You can find it at .

Okay, I thought let's do it and re-engineer the API for using it in C/C++ programs. And here it is:

/* re-engineered header file for CVSypder.dll
* (c) 2007 Stefan Döhla, published under GPLv2
* API blatanly stolen from ColorAxis VB code
* see for ColorAxis details

#ifndef __CV_SYPDER_H__
#define __CV_SPYDER_H__ __CV_SPYDER_H__

#define CV_STATUS_SUCCESS 0x01
#define CV_STATUS_FAILURE 0x00

#define CV_ERROR_API 0x00
#define CV_ERROR_SYSTEM 0x01

#define CV_ERROR_NOT_INITIALIZED 0x00010001
#define CV_ERROR_TIMEOUT 0x00010006
#define CV_LAST_FATAL_ERROR 0x0001ffff



#define CV_SCREENTYPE_CRT 0x01
#define CV_SCREENTYPE_LCD 0x02


typedef unsigned char CV_STATUS;
typedef unsigned long CV_MESSAGE;
typedef unsigned short CV_SCREENTYPE;
typedef struct
unsigned short DriverVersion;
unsigned short HardwareVersion;
unsigned char SerialNumber[CV_SERIAL_NUMBER_LEN];
} CV_VendorData;

#define DLL_IMPORT __declspec(dllimport)

#ifdef __cplusplus
extern "C" {

DLL_IMPORT CV_STATUS CALLING_CONVENTION CV_Startup(CV_VendorData * vendorData, unsigned int tmp, unsigned int tmp2);

DLL_IMPORT CV_STATUS CALLING_CONVENTION CV_GetRefreshRate(unsigned long * rate);

long * X,
long * Y,
long * Z);

long * X,
long * Y,
long * Z,
unsigned char dolevel2);

DLL_IMPORT CV_MESSAGE CALLING_CONVENTION CV_GetDetailedError(unsigned char * systemError);



#ifdef __cplusplus

#endif /* __CV_SPYDER_H__ */

This header file can be used to create either a lib (you need to create stub functions and so on - all the nasty tricks) or use it as a template to DLLImport the functions from CVSpyder.dll .

I tested it with a small program (that includes Bruce Lindbloom's color conversion code at the beginning):

/* test for for CVSypder.dll-usage
* (c) 2007 Stefan Döhla, published under GPLv2
* XYZ->RGB conversion blatanly stolen from Bruce Lindbloom
* see

#include "cvspyder.h"


/* LERP(a,b,c) = linear interpolation macro, is 'a' when c == 0.0 and 'b' when c == 1.0 */
#define LERP(a,b,c) (((b) - (a)) * (c) + (a))

typedef struct UVT {
double u;
double v;
double t;
} UVT;

double rt[31] = { /* reciprocal temperature (K) */
DBL_MIN, 10.0e-6, 20.0e-6, 30.0e-6, 40.0e-6, 50.0e-6,
60.0e-6, 70.0e-6, 80.0e-6, 90.0e-6, 100.0e-6, 125.0e-6,
150.0e-6, 175.0e-6, 200.0e-6, 225.0e-6, 250.0e-6, 275.0e-6,
300.0e-6, 325.0e-6, 350.0e-6, 375.0e-6, 400.0e-6, 425.0e-6,
450.0e-6, 475.0e-6, 500.0e-6, 525.0e-6, 550.0e-6, 575.0e-6,

UVT uvt[31] = {
{0.18006, 0.26352, -0.24341},
{0.18066, 0.26589, -0.25479},
{0.18133, 0.26846, -0.26876},
{0.18208, 0.27119, -0.28539},
{0.18293, 0.27407, -0.30470},
{0.18388, 0.27709, -0.32675},
{0.18494, 0.28021, -0.35156},
{0.18611, 0.28342, -0.37915},
{0.18740, 0.28668, -0.40955},
{0.18880, 0.28997, -0.44278},
{0.19032, 0.29326, -0.47888},
{0.19462, 0.30141, -0.58204},
{0.19962, 0.30921, -0.70471},
{0.20525, 0.31647, -0.84901},
{0.21142, 0.32312, -1.0182},
{0.21807, 0.32909, -1.2168},
{0.22511, 0.33439, -1.4512},
{0.23247, 0.33904, -1.7298},
{0.24010, 0.34308, -2.0637},
{0.24792, 0.34655, -2.4681}, /* Note: 0.24792 is a corrected value for the error found in W&S as 0.24702 */
{0.25591, 0.34951, -2.9641},
{0.26400, 0.35200, -3.5814},
{0.27218, 0.35407, -4.3633},
{0.28039, 0.35577, -5.3762},
{0.28863, 0.35714, -6.7262},
{0.29685, 0.35823, -8.5955},
{0.30505, 0.35907, -11.324},
{0.31320, 0.35968, -15.628},
{0.32129, 0.36011, -23.325},
{0.32931, 0.36038, -40.770},
{0.33724, 0.36051, -116.45}

int XYZtoCorColorTemp(double X, double Y, double Z, double *temp)
double us, vs, p, di, dm;
int i;

if ((X < 1.0e-20) && (Y < 1.0e-20) && (Z < 1.0e-20))
return(-1); /* protect against possible divide-by-zero failure */
us = (4.0 * X) / (X + 15.0 * Y + 3.0 * Z);
vs = (6.0 * Y) / (X + 15.0 * Y + 3.0 * Z);
dm = 0.0;
for (i = 0; i < 31; i++) {
di = (vs - uvt[i].v) - uvt[i].t * (us - uvt[i].u);
if ((i > 0) && (((di <>= 0.0)) || ((di >= 0.0) && (dm < 0.0))))
break; /* found lines bounding (us, vs) : i-1 and i */
dm = di;
if (i == 31)
return(-1); /* bad XYZ input, color temp would be less than minimum of 1666.7 degrees, or too far towards blue */
di = di / sqrt(1.0 + uvt[i ].t * uvt[i ].t);
dm = dm / sqrt(1.0 + uvt[i - 1].t * uvt[i - 1].t);
p = dm / (dm - di); /* p = interpolation parameter, 0.0 : i-1, 1.0 : i */
p = 1.0 / (LERP(rt[i - 1], rt[i], p));
*temp = p;
return(0); /* success */

int __cdecl main(int argc, char ** argv)
CV_VendorData data = {0};
CV_MESSAGE message;
unsigned long rate = 0;
long Xlong=0, Ylong=0, Zlong=0;
double X=0.0, Y=0.0, Z=0.0;
double x=0.0, y=0.0;
double colorTemperature = 0.0;

status = CV_Startup(&data, 1, 1);
if(status != CV_STATUS_SUCCESS)
message = CV_GetDetailedError(&status);

status = CV_GetRefreshRate(&rate);
if(status != CV_STATUS_SUCCESS)
message = CV_GetDetailedError(&status);

status = CV_UseCalibration(CV_SCREENTYPE_LCD);
status = CV_UseCalibration(CV_SCREENTYPE_CRT);
if(status != CV_STATUS_SUCCESS)
message = CV_GetDetailedError(&status);

status = CV_GetXYZ(300, &Xlong, &Ylong, &Zlong);
if(status != CV_STATUS_SUCCESS)
message = CV_GetDetailedError(&status);


x = X / (X+Y+Z);
y = Y / (X+Y+Z);

XYZtoCorColorTemp(X, Y, Z, &colorTemperature);

return 0;

As you see it's not that complicated making measurements with the CVSpyder API. It's important that one calls the functions in this order
  1. CV_Startup
  2. CV_GetRefreshRate
  3. CV_UseCalibration
  4. CV_GetXYZ
  5. CV_Shutdown
Initially I got bad reads because I haven't called CV_UseCalibration - there are weird results then!

Maybe I can manage to integrate this API into some open-source monitor calibration projects (argyll/lprof) but honestly, I'd be happier if the USB protocol can be hacked (maybe with the help of this API).

My first blog entry

Hello dear readers,

this is my first blog. I hope that I'm able to share some useful information about color-management with the internet community from time to time.