KCMS CMM Developer's Guide

KCMS Framework Primary Operations

The remainder of this chapter describes how the KCMS framework operates from the perspective of the KCMS "C" API. Code examples show KCMS "C" API calls that perform the following tasks:

Within the primary framework, events are illustrated and described in sequence to explain what actually takes place when each "C" API call is made.

Loading a Profile From the Solaris File System

The framework must have a profile with which to operate. Example 3-1 is a KCMS "C" API code excerpt that loads a scanner profile with a file name.


Example 3-1 Loading a Profile from the Solaris File System

KcsProfileId      

scannerProfile; KcsProfileDesc						scannerDesc; KcsStatusId						status;

char			*in_prof= "kcmsEKmtk600zs";  scannerDesc.type =

KcsSolarisProfile; scannerDesc.desc.solarisFile.fileName = in_prof;

scannerDesc.desc.solarisFile.hostName = NULL;

scannerDesc.desc.solarisFile.oflag = O_RDONLY;

scannerDesc.desc.solarisFile.mode = 0;  /* Load the scanner profiles */ status

= KcsLoadProfile(&scannerProfile, &scannerDesc,  			KcsLoadAllNow); if

(status != KCS_SUCCESS) { 	fprintf(stderr,"scanner KcsLoadProfile failed

error =  			0x%x\n", status); 	return(-1); }

In the example, the KCMS API layer calls KcsLoadProfile() to inform the KCMS framework that a profile description of type KcsSolarisProfile is to be loaded. The name of the profile and the options for opening that file are also specified using the solarisFile entry in the KcsProfileDesc structure.

Figure 3-3 Creating a KcsIO Object

Graphic

Creating a KcsIO Object

As a result of the call to KcsLoadProfile() the framework creates a KcsIO object. Figure 3-3 illustrates how the KcsLoadProfile() API call is implemented. The legend indicates the source of each call (KCMS "C" API layer, framework, "C" wrapper). In addition, the legend shows where the OWconfig file and the loadable CMM module fit into the overall scheme of loading the profile to creating a KcsIO object.

Looking ahead for a moment, Figure 3-4, Figure 3-5, and Figure 3-6 show the progression of framework calls that ultimately load the profile as chunks of data and return the profile Id to the calling application.

Returning now to Figure 3-3, the KCMS framework and the dynamic loading mechanism perform the following task sequence when KcsLoadProfile() is called:

  1. The framework gets a new profile Id. The framework maintains a dynamically allocated global array of profiles. The getNewValidProfileIndex() method allocates a new profile entry.

  2. The framework creates a KcsIO pointer. (All profiles access their data using the KcsIO independent access mechanism.) The KcsIO pointer is created based on the type field of the KcsProfileDesc structure pointer passed in from KcsLoadProfile().

    Two externally available types are built into the libkcs library, KcsFile and KcsMemoryBlock. A third derivative, KcsRemoteFile, is used with the KcsSolarisFile and KcsXWindow classes. In this particular example, KcsSolarisFile is not built into the libkcs library, so the dynamic loading mechanism creates one.

  3. The dynamic loading mechanism turns the KcsProfileDesc->type structure pointer field into a 4-character string and searches entries in the OWconfig file for the entries that correspond to loadable KcsIO classes. If it finds a match, it dynamically loads the KcsIO module. This action supplies the framework with a shared object to load. (See "KcsIO Example" for details.)

    The KcsIO module contains calls to a list of known function names. The framework uses dlsym(3X) to bring these functions into the framework to create and load a pointer to a KcsIO derivative.

  4. Once the KcsSolarisFile object pointer is loaded, the framework uses the fileName, hostName, and open(2) arguments to search for the profile. First, it checks the hostName to see if the file is on a local or remote machine. Depending on the location, the KcsSolarisFile object reuses the existing KcsIO class derivatives.

    If the file is on a local machine, the fileName is opened using open(2), and a KcsFile object pointer is created. If, however, the file is located on a remote machine, the fileName and hostName are passed to KcsRemoteFile and a KcsRemoteFile object pointer is created.

    As shown in Example 3-2, the KcsFile or KcsRemoteFile pointer that the KcsSolarisFile file object contains is then used to override the KcsIO methods of the same name.


    Example 3-2 Overriding KcsIO Methods With KcsSolarisFile

    // Just call myIO
    
    version of the call KcsStatus KcsSolarisFile::relRead(const long aBytesWanted,
    
    void *aBuffer, const char *aCallersName) {     KcsStatus status;      status =
    
    myIO->relRead(aBytesWanted, aBuffer, aCallersName);     return (status);
    
    }
    

Creating a KcsProfile Object

Once a KcsIO object has been created, the profile can be loaded. Figure 3-4 illustrates creating a KcsProfile object.

Figure 3-4 Creating a KcsProfile Object

Graphic

In Figure 3-4, the first step to loading the profile is to create a new KcsProfile object with the createProfile() static KcsProfile method. This method uses the CMM Id of the profile, which is located in a fixed place (bytes 4-7) in the profile header. (See "ICC Profile Header" for details.) The CMM Id determines the KcsProfile derivative to be created. If the CMM Id has no corresponding entry in the OWconfig file, the default KcsProfile class is created.

Creating a KcsProfileFormat Object

Once a KcsProfile object has been created, you can ask it to load itself using the generated KcsIO. Figure 3-5 illustrates the process.

Figure 3-5 Creating a KcsProfileFormat Object

Graphic

In Figure 3-5, the KcsProfile object creates a KcsProfileFormat object pointer using createProfileFormat(). Then createProfileFormat() searches the OWconfig file for loadable entries based on the profile format Id. For ICC profiles, the profile format Id (also called the profile file signature) is always acsp. (See "ICC Profile Header" for details.) Once the KcsProfileFormat object is created, the library generates a KcsAttributeSet object and an array of pointers to KcsXform objects.

Loading a KcsProfileFormat Object

The pointers to objects contained within the KcsProfileFormat object load themselves using the KcsChunkSet class. Figure 3-6 illustrates the process.

Figure 3-6 Loading a KcsProfileFormat Object

Graphic

In Figure 3-6, the KcsChunkSet class returns the blocks of data from the file, which were requested by the KcsAttributeSet and KcsXform objects. These objects interpret the block of data, turning it into tables for processing color data or sets of attributes. The KcsIO and KcsChunkSet classes do not interpret the data.

If the Solaris file system profile is successfully loaded, the framework increments the number of entries in the global profile array, and the profile Id is returned to the application.

Loading an X11 Window System Profile

In the next example, the framework loads a profile associated with a particular X11 Window System visual. The KcsXWindow object converts the display, visual, and screen information into a profile loaded into the KCMS framework. Example 3-3 is a KCMS "C" API code excerpt that shows this.


Example 3-3 Loading an X11 Window System Profile

if ((dpy =

XOpenDisplay(hostname)) == NULL) { 	fprintf(stderr, "Couldn't open

the X display \n"); 	exit(1); }  profileDesc.type = KcsWindowProfile;

profileDesc.desc.xwin.dpy = dpy; profileDesc.desc.xwin.visual =

DefaultVisual(dpy,  			DefaultScreen(dpy)); profileDesc.desc.xwin.screen =

DefaultScreen(dpy);  status = KcsLoadProfile(&profile, &profileDesc, 

			KcsLoadAttributesNow); if (status != KCS_SUCCESS) { 	status =

KcsGetLastError(&errDesc); 	fprintf(stderr,"KcsLoadProfile failed

error = %s\n",  			errDesc.desc); 	exit(1); }

The only difference between this example and Example 3-2, is the type of KcsIO class loaded. That example showed how to load a KcsSolarisFile object rather than a KcsXWindow object.

Connecting Two Loaded Profiles

Example 3-4 is a "C" API code excerpt that shows how to connect two profiles together once they have been loaded.


Example 3-4 Connecting Two Loaded Profiles

profileSequence[0] =

scannerProfile; profileSequence[1] = monitorProfile; status =

KcsConnectProfiles(&completeProfile, 2, profileSequence, op,

&failedProfileNum); if (status != KCS_SUCCESS) { 	fprintf(stderr,

"Connect Profiles failed in profile number  			%d\n",

failedProfileNum); 	KcsFreeProfile(monitorProfile);

	KcsFreeProfile(scannerProfile); 	return(-1); }

The KCMS framework implements the K()csConnectProfiles() API call as follows:

  1. It calls getNewValidProfileIndex()method to get a new valid index for the connected profile from the dynamically allocated global array of profiles.

  2. The new connected profile needs a KcsIO class to handle its I/O. Currently, this is stored in memory only, so it creates a KcsMemoryBlock object.

  3. It creates a KcsProfile object that can link together sequences of profiles.

  4. It attaches each profile in the sequence with attach() to the newly created KcsProfile object. The attach()method reference counts the objects. (Note that all classes are reference counted through inheritance from the KcsShareable class.)

  5. Once attached, the KcsAttributeSet object composes the attributes from the two KcsProfile members in the array into a single set of attributes for the newly created profile object.

  6. It links the KcsXform array so that the "into" and "out of" profile connection space (PCS) transforms of each KcsProfile object (profile) can be connected. When color data is processed through this sequence, it moves from input profile to PCS and from PCS to output profile.

  7. Once connected, it returns the new profile Id to the calling application for later reference, and generally cleans up the classes.

Evaluating Data Without Optimization

The evaluation path of data is different for unoptimized and optimized sequences. Figure 3-7 shows both paths.

Figure 3-7 Optimized And Unoptimized Evaluation

Graphic

In the unoptimized case, when evaluate() is called, the color data is moved from input space to PCS and from PCS to output space. This is achieved by passing the data through the appropriate KcsXform object in the KcsXform object array. The KCMS "C" API code excerpt shown in Example 3-5 evaluates data without optimization.


Example 3-5 Evaluating Data Without Optimization

/* set up the pixel

layout and color correct the image */ if (depth == 24)

	setupPixelLayout24(&pixelLayoutIn, image_in); else

	setupPixelLayout8(&pixelLayoutIn, red, green, blue,  			maplength); 

status = KcsEvaluate(completeProfile, op, &pixelLayoutIn, 

			&pixelLayoutIn); if (status != KCS_SUCCESS) { 	fprintf(stderr,

"EvaluateProfile failed\n"); 	KcsFreeProfile(monitorProfile);

	KcsFreeProfile(scannerProfile); 	KcsFreeProfile(completeProfile);

	return(-1); }

Evaluating Data With Optimization

When a profile sequence is optimized for speed, a set of tables is generated that does not require the color data to be passed through the PCS. As a result, the connected profile contains a composed KcsXform object that moves data directly from input space to output space. (Composition reduces multiple transforms into a single transform.) The KCMS "C" API code excerpt shown in Example 3-6 evaluates data with optimization for speed.


Example 3-6 Evaluating Data With Optimization for Speed

status =

KcsOptimizeProfile(completeProfile, KcsOptSpeed,  			KcsLoadAllNow); if

(status != KCS_SUCCESS) { 	 fprintf(stderr, "OptimizeProfile

failed\n"); 	 KcsFreeProfile(monitorProfile); 	

KcsFreeProfile(scannerProfile); 	 return(-1); }  /* set up the pixel layout

and color correct the image */ setupPixelLayout24(&pixelLayoutIn,

image_in);  status = KcsEvaluate(completeProfile, op, &pixelLayoutIn, 

			&pixelLayoutIn);  if (status != KCS_SUCCESS) { 	fprintf(stderr,

"EvaluateProfile failed\n"); 	KcsFreeProfile(monitorProfile);

	KcsFreeProfile(scannerProfile); 	KcsFreeProfile(completeProfile);

	return(-1); }

Freeing a Profile

Freeing a profile causes each of the objects pointed to by the profile Id in the framework's global array to release all of its associated data. If a given object is a shared or reference-counted object, the memory is released only if the reference count drops to zero.

Freeing a profile loaded via KcsSolarisProfile or KcsXWindowProfile closes the associated file descriptor or remote procedure call (RPC) connection if the file is located on a remote machine. Use the KcsFreeProfile(profile)() KCMS "C" API call to free a profile.

Attributes

The examples below show how to get and set attributes.

Setting an Attribute

When setting an attribute, the KcsProfile object in the KcsProfile object array passes the setting of the attribute to the KcsAttributeSet object contained in its KcsProfileFormat object. This is illustrated in Figure 3-2 and in the KCMS API code excerpt shown in Example 3-7.


Example 3-7 Setting an Attribute

/* double2icFixed

converts a double float to a signed 15 16 fixed point   * number */ /* Set

white point */ test_double[] = 0.2556; test_double[1] = 0.600189;

test_double[2] = 0.097794; attrValue.base.countSupplied = 1

attrValue.base.type = icSigXYZType; attrValue.base.sizeof(icXYZNumber);

attrValue.val.icXYZ.[0].X = double2icfixed(test_double[0], 

		icSigS15Fixed16ArrayType); attrValue.val.icXYZ.[0].Y =

double2icfixed(test_double[1],  		icSigS15Fixed16ArrayType);

attrValue.val.icXYZ.[0].Z = double2icfixed(test_double[2], 

		icSigS15Fixed16ArrayType); rc = KcsSetAttribute(profileid,

icSigMediaWhitepointTag, &attrValue); if (rc != KCS_SUCCESS {

	KcsGetLastError(&errDesc); 	fprintf(stderr, "unable to set

whitepoint: %s\n", errDesc.desc); 	KcsFreeProfile(profileid); 	return

(-1); }

Getting an Attribute

When getting an attribute, the KcsProfile object in the array passes the getting of the attribute to the KcsAttributeSet object contained in its KcsProfileFormat object (replacing set with get). This is illustrated in Figure 3-2 and in the KCMS API code excerpt shown in Example 3-8.


Example 3-8 Getting an Attribute

/* Get the colorants */

/* icfixed2double converts signed 15.16 fixed point number to a double   *

float */ /*Red */ attrValuePtr = (KcsAttributeValue *)

malloc(sizeof(KcsAttributeBase) +  	sizeof(icXYZNumber));

attrValuePtr->base.type = icSigXYZArrayType;

attrValuePtr->base.countSupplied = 1; status = KcsGetAttribute(profileid,

icSigRedColorantTag, attrValuePtr); if (status != KCS_SUCCESS) { 	status =

KcsGetLastError(&errDesc); 	printf("GetAttribute error: $s\n",

errDesc.desc); 	KcsFreeProfile(profileid); 	exit(1); }  XYZval = (icXYZNumber

*)attrValuePtr->val.icXYZ.data; printf("Red X=%f Y=%f Z=%f\n", 

		icfixed2double(XYZval->X, icSigS15Fixed16ArrayType), 

		icfixed2double(XYZval->Y, icSigS15Fixed16ArrayType),     

icfixed2double(XYZval->Z, icSigS15Fixed16ArrayType),

Characterization and Calibration

Characterization and calibration are accessed using the following KCMS "C" API calls:

See the SDK manual KCMS Application Developer's Guide for more information on these calls.

The KcsProfile base class contains virtual methods to characterize and calibrate two types of devices: scanners and monitors. You must decide whether to override the base functionality to take characterization and calibration data and turn it into the appropriate KcsXform data.


Note -

Currently, the default CMM supports monitor and scanner characterization and calibration only. It does not support printer characterization and calibration. However enabling hooks exist in the source so you can write a CMM that supports printers.


Attributes are set using the normal mechanisms. The KCMS "C" API code excerpt in Example 3-9 shows characterization and calibration.


Example 3-9 Characterization and Calibration

KcsCalibrationData      

       *calData;     KcsCharacterizationData														*charData;    

float														Luminance_float_out[3][256];    

double														test_double[3];   /* this is a test which does not use

real data - just a gamma curve for the *  calibration structure and the same

curve *.75 for the characterization curve. */      /*create luminance tables

with a gamma = 2.22 */  	for (j=0; j<levels; j++) { 			input_val = j *

(1.0/255.0); 			Luminance_float_out[0][j] = pow(input_val, 2.22);

			Luminance_float_out[1][j] = pow(input_val, 2.22);

			Luminance_float_out[2][j] = pow(input_val, 2.22);     }     /* Fill out the

measurement structures - The illuminant must be D50 */     test_double[0] =

0.9642;     test_double[1] = 1.0;     test_double[2] = 0.8249;     sizemeas =

(int) (sizeof(KcsMeasurementBase) + sizeof(long) + 		  	

sizeof(KcsMeasurementSample) * levels);     charData =

(KcsCharacterizationData *) malloc(sizemeas);     

charData->base.countSupplied = levels;     charData->base.numInComp = 3;

    charData->base.numOutComp = 3;     charData->base.inputSpace =

KcsCIEXYZ;     charData->base.outputSpace = KcsRGB;     for (i=0; i<

levels; i++) { 			charData->val.patch[i].weight = 1.0;

			charData->val.patch[i].standardDeviation = 0.0;

			charData->val.patch[i].sampleType = KcsChromatic; 

			charData->val.patch[i].input[KcsRGB_R] = (float)i/255;

			charData->val.patch[i].input[KcsRGB_G] = (float)i/255;

			charData->val.patch[i].input[KcsRGB_B] = (float)i/255;

			charData->val.patch[i].input[3] = 0.0; 

			charData->val.patch[i].output[KcsRGB_R] =

(Luminance_float_out[0][i])/0.75;

			charData->val.patch[i].output[KcsRGB_G] =

(Luminance_float_out[1][i])/0.75;

			charData->val.patch[i].output[KcsRGB_B] =

(Luminance_float_out[2][i])/0.75; 			charData->val.patch[i].output[3] =

0.0;     }     charData->val.patch[0].sampleType = KcsBlack;    

charData->val.patch[255].sampleType = KcsWhite;      sizemeas = (int)

(sizeof(KcsMeasurementBase) + sizeof(long) + 		   	

sizeof(KcsMeasurementSample) * levels);     calData = (KcsCalibrationData *)

malloc(sizemeas);      calData->base.countSupplied = levels;    

calData->base.numInComp = 3;     calData->base.numOutComp = 3;    

calData->base.inputSpace = KcsRGB;     calData->base.outputSpace =

KcsRGB;     for (i=0; i< levels; i++) { 			calData->val.patch[i].weight

= 1.0; 			calData->val.patch[i].standardDeviation = 0.0;

			calData->val.patch[i].sampleType = KcsChromatic; 

			calData->val.patch[i].input[KcsRGB_R] = (float)i/255;

			calData->val.patch[i].input[KcsRGB_G] = (float)i/255;

			calData->val.patch[i].input[KcsRGB_B] = (float)i/255;

			calData->val.patch[i].input[3] = 0.0; 

			calData->val.patch[i].output[KcsRGB_R] = Luminance_float_out[0][i];

			calData->val.patch[i].output[KcsRGB_G] = Luminance_float_out[1][i];

			calData->val.patch[i].output[KcsRGB_B] = Luminance_float_out[2][i];

			calData->val.patch[i].output[3] = 0.0;     }     

calData->val.patch[0].sampleType = KcsBlack;    

calData->val.patch[255].sampleType = KcsWhite;      printf("Update a

profile with characterization and calibration data.\n");     rc =

KcsUpdateProfile(profileid, charData, calData, NULL);     if(rc !=

KCS_SUCCESS) {         KcsGetLastError(&errDesc);         fprintf(stderr, 

               "unable to update profile: %s\n", errDesc.desc);   

     KcsFreeProfile(profileid);         return(-1);     }

Saving a Profile to the Same Description

Saving a profile to the same description is the same as loading in reverse. Each object pointed to or contained within the KcsProfile object is instructed, with its own save mechanisms, to write the data needed to reconstruct itself out to static store. In this case, the description is identical to that used to load the profile, so the current KcsIO associated with the profile is used. Example 3-10 is a KCMS "C" API code excerpt that saves a profile to the same description.


Example 3-10 Saving a Profile to the Same Description

status

= KcsSaveProfile(profileid, NULL); if(status != KCS_SUCCESS) { 	status =

KcsGetLastError(&errDesc); 	printf("SaveProfile error: %s\n",

errDesc.desc); }


Saving a Profile to a Different Description

To save a profile to a different description, load a new KcsIO so that the KcsProfile object can save itself. This is accomplished with the same mechanism as that described in steps 2 to 5 of "Creating a KcsIO Object" . Example 3-11 is a KCMS "C" API code excerpt that saves a profile to a different description.


Example 3-11 Saving a Profile to a Different Description

/* Application opens the

file */ if ((sfd = open(argv[2], O_RDWR|O_CREAT, 0666)) == -1) { 	perror

("save open failed"); 	exit (1); }  desc.type = KcsFileProfile;

desc.desc.file.openFileId = sfd; desc.desc.file.offset = 0; status =

KcsSaveProfile(profileid, &desc); if(status != KCS_SUCCESS) { 	status =

KcsGetLastError(&errDesc); 	printf("SaveProfile error: %s\n",

errDesc.desc); }