KCMS CMM Developer's Guide
  Suchtext Nur in diesem Buch
Dieses Buch im PDF-Format herunterladen

KCMS Framework Operations

3

The Kodak Color Management System (KCMS) is a flexible and powerful framework for developing color management technology. You can add attributes to the current list and incorporate new color processing technology.
This chapter contains the following information:
  • A description of the profile format
  • An overview of the KCMS framework architecture
  • An introduction to how the framework works using sample API programs and the corresponding framework action

Profile Format

KCMS uses the ICC profile format as the default profile format. The profile format specification is a PostScript file located on-line in /usr/openwin/demo/kcms/docs/icc.ps. By supporting this specification, profiles can be used on all participating vendors' color management systems.
Much of the work in processing and handling ICC format profiles is included by default in the framework. To speed your development cycle use as much of this default technology as possible. You can develop your own profile format within the framework.

Note - It is strongly recommended that you extend the ICC profile format rather than develop your own. If you do not use the ICC profile format your profiles will not be easily ported to other platforms.

KCMS Framework Architecture

Figure 3-1 illustrates the KCMS framework architecture and how the KCMS classes are used to implement the KCMS framework. The framework is implemented by manipulating an array of KcsProfile objects within a set of "C" wrapper functions. The "C" wrapper functions are C-to-C++ calls that make up the KCMS API. The following sections help explain this diagram.

Grafik

Figure 3-1

KcsProfile

KcsProfile objects are created from a static store which is a KcsIO object. KcsProfile objects are described using one of the types in the KcsProfileDesc structure which is defined in the kcstypes.h header file. Objects can read from and write to data in a static store. Examples of a static store include a file and memory. KcsProfile objects generated internally by the framework use a KcsMemoryBlock object.
The KcsProfile class static member function, createProfile() reads the CMM Id from the static store and generates a pointer to the KcsProfile derivative. The CMM Id is located at byte 4 in the ICC profile format. If the CMM Id has no associated runtime derivative, the default KcsProfile derivative, KcsProfileKCMS, is used.

Note - The CMM Id must be in a set location in the file; that is the same location as used by the ICC profile format.

The KcsProfile class contains a set of public member functions that correspond to the KCMS API functions shown in the following table.
Table 3-1 KcsProfile
KCMS API FunctionKcsProfile Member Functions
KcsLoadProfile()load()
KcsSaveProfile()save()
KcsSetAttribute()setAttribute()
KcsGetAttribute()getAttribute()
KcsConnectProfiles()connect()
KcsEvaluate()evaluate()
KcsUpdateProfile()updateXforms()

KcsProfileFormat

Each KcsProfile base class contains a pointer to a KcsProfileFormat object. This allows the architecture to link different profile formats and keep the KcsProfile class independent of the actual profile format. The KcsProfileFormat object is created based on the profile format Id and
profile version number. The ICC profile format Id is acsp, located at byte 36. The version number is derived from the profile version number; ICC profile byte 8. The framework uses the version number with the profile format Id so that it can handle different versions of profile formats. For non-ICC profile formats the format Id and version number must be at the same byte location in the static store.

KcsAttributeSet

Each KcsProfileFormat base class contains a pointer to a KcsAttributeSet object and handles all of the functionality for attributes. Using the KcsIO class associated with the parent KcsProfile, the KcsAttributeSet object can load itself from the static store. KcsAttributeSet does not use the KcsIO class directly; it uses the KcsChunkSet utility class to access the static store. KcsChunkSet knows how to handle the mapping from desired information blocks to its actual location in the static store. KcsChunkSet and KcsIO have no knowledge of the contents of the data. That is left to the calling class.

KcsXform

The KcsXform base class contains an array of KcsXforms. The primary function of KcsXform (or transforms) is to manipulate color data. KcsXform also uses the KcsChunkSet class to load from and save to static store.

KCMS Framework Flow Examples

The following examples will help you better understand the KCMS architecture and the flow of control and data between the KCMS API and the KCMS framework. Use Figure 3-1 on page 32 as a reference.

Loading a Profile

The example explains how a profile is loaded.
  1. Using the KcsIO derivative, the CMM Id of the profile is determined.

  1. The KcsProfile::createProfile() static method is called and loading starts. The CMM Id of the profile is used as a key to determine the particular KcsProfile derivative to load. The association of the CMM Id with dynamically loadable module is made using entries in the OWconfig file.

    Once dynamically loaded, the module returns a pointer to a KcsProfile object. If the particular CMM Id has no match in the OWconfig file, the default KcsProfile derivative, KcsProfileKCMS, is used. There is a special CMM Id key dflt entry in the OWconfig file, so that you can override the default KcsProfile class. If you do this then you must duplicate all of the functionality handled in the default class.

  2. The load() method is then called on the KcsProfile object pointer that was created in step 2. This causes a KcsProfileFormat object pointer to be created using an entry in the OWconfig file and then loads itself.

    The profile format Id, byte 36, of the ICC profile format is used as the key to this entry in the OWconfig file.

  3. The KcsProfileFormat object contains pointers to a KcsAttributeSet object and an array of KcsXforms. These objects are also created and their load() methods called to load themselves from the static store.

    The KcsAttributeSet object can be derived from directly since it is statically linked into the KCMS framework. The KcsXform array has an OWconfig entry that uses as a key the 4-byte identifier. For ICC-based profiles, use the 8- and 16-bit LUT tags, mft1 and mft2.

  4. If all pieces of the profile are loaded, a KCS_SUCCESS status is returned.

Getting Attributes

This example shows you how to get a profile's attributes with KcsGetAttribute(), once the profile is loaded.

Grafik

Figure 3-2 KcsGetAttribute()

  1. For the appropriate KcsProfile object in the array, call its getAttribute() method.

  2. The KcsProfileXXXX::getAttribute calls its KcsProfileFormat::getAttribute() method.

  3. This in turn calls its KcsAttributeSet::getAttribute() method.

  4. The KcsAttributeSet::getAttribute() method gets the attribute and returns it back up the chain to the API layer.

A similar flow of control is true for the other KCMS API calls.

KCMS Framework Primary Operations

The following examples describe how the framework operates from the perspective of the KCMS "C" API. These examples illustrate sequences of operations in the primary framework, attributes, and calibration and characterization.

Loading a Profile From the Solaris File System

The framework must have a profile with which to operate. The following API code sample loads a scanner profile with a file name.
Code 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);  
  }  

Creating a KcsIO Object

In this example the KCMS framework is informed from the API layer that a profile description of type KcsSolarisProfile is to be loaded. It uses KcsLoadProfile(). The name of the profile and the options for opening that file are also specified using the solarisFile entry in the KcsProfileDesc structure.
Use Figure 3-3 on page 38 to illustrate the following implementation of the KcsLoadProfile() API call.

Grafik

Figure 3-3 KcsIO

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

  2. All profiles access their data using the independent access mechanism KcsIO. A 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. There is a third derivative, KcsRemoteFile, to be used with the classes KcsSolarisFile and KcsXWindow classes. In this 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 four-character string and searches entries in the OWconfig file for the entries that correspond to loadable KcsIO classes. If a match is found, the KcsIO module is dynamically loaded. This supplies the framework with a shared object to load.

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

    See Chapter 2, "CMM Runtime Derivative" for more details.

  4. Once the KcsSolarisFile object pointer is loaded, the fileName, hostName and open(2) arguments are used to search for the profile. The hostName is first checked to see if the file is on a local or remote machine. Depending on the location, the KcsSolarisFile 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 the file is on a remote machine the fileName and hostName are passed to KcsRemoteFile and an object pointer is created.

As shown in Code Example 3-2, the KcsFile or KcsRemoteFile pointer which the KcsSolarisFile file object contains is then used to override the KcsIO methods.
Code 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 has been created the profile can be loaded. The following diagram illustrates the process.

Grafik

Figure 3-4 KcsProfile


The first step 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 in the profile. 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 has been created you can ask it to load itself using the generated KcsIO. The following diagram illustrates the process.

Grafik

Figure 3-5 KcsProfileFormat

The KcsProfile object creates a KcsProfileFormat object pointer using createProfileFormat() which searches the OWconfig file for loadable entries based on the profile format bytes. For ICC profiles this is always acsp. Once the KcsProfileFormat object is created, the library generates a KcsAttributeSet object and an array of KcsXform objects.

Loading a KcsProfileFormat Object

The pointers to objects contained within the KcsProfileFormat object load themselves using the KcsChunkSet class. The following diagram illustrates the process.

Grafik

Figure 3-6 KcsProfileFormat

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 profile is successfully loaded, the number of entries in the global profile array is incremented, and the profile Id is returned to the application.

Loading an X11 Window System Profile

In this 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.
Code 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 Code Example 3-2 on page 40, 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

The following API code example shows you how connect two profiles together once they have been loaded.
Code 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 KcsConnectProfiles() API call is implemented as follows:
  1. Get a new valid index with getNewValidProfileIndex() for the connected profile.

  2. The new connected profile needs a KcsIO class to handle its input and output. This is currently only stored in memory, so a KcsMemoryBlock object is created.

  3. A KcsProfile object is created that can link together sequences of profiles.

  4. Each profile in the sequence is then attached with attach() to the newly created KcsProfile object. attach() reference counts the objects. All classes are reference counted through inheritance from the KcsShareable class.

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

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

  1. Once connected, the new profile Id is returned to the calling application for later reference and the classes are generally cleaned up.

Evaluating Data Without Optimization

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

Grafik

Figure 3-7

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 API code excerpt below evaluates data without optimization.
Code 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");  

Code Example 3-5 Evaluating Data Without Optimization (Continued)

       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 is the process of reducing multiple transforms into a single transform. The KCMS API code excerpt below evaluates data with optimization for speed.
Code 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, only if the reference count drops to zero will the memory be released.
Freeing a profile, loaded via KcsSolarisProfile or KcsXWindowProfile, closes the associated file descriptor or RPC connection if the file is located on a remote machine. Use the KcsFreeProfile(profile) API call to free a profile.

Attributes

The following examples show you how to get and set attributes.

Setting an Attribute

When setting an attribute, the KcsProfile in the global array passes the setting of the attribute to the KcsAttributeSet object contained in its KcsProfileFormat object. This is illustrated in Figure 3-2 on page 36 and in the following KCMS API code excerpt.
Code 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);  

Code Example 3-7 Setting an Attribute

       fprintf(stderr, "unable to set whitepoint: %s\n", errDesc.desc);  
       KcsFreeProfile(profileid);  
       return (-1);  
  }  

Getting an Attribute

When getting an attribute, the KcsProfile in the array passes the getting of the attribute to the KcsAttributeSet object, replacing set with get, contained in its KcsProfileFormat object. This is illustrated in Figure 3-2 on page 36 and in the following KCMS API code excerpt.
Code 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 API calls: KcsCreateProfile(), KcsUpdateProfile(), KcsSetAttribute(), and KcsSaveProfile(). 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 three types of devices: scanners, monitors, and printers. It is your decision 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.

Attributes are set using the normal mechanisms. The following is a KCMS API code excerpt showing characterization and calibration.
Code Example 3-9 Characterization and Calibration

  /* Fill out the measurement structures with dummy data*/  
  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;/*sample data in Luminance_flat_out array */  
  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;  
  
  status = KcsUpdateProfile(profileid, NULL, calData, NULL);  
  if (status != KCS_SUCCESS) {  

Code Example 3-9 Characterization and Calibration (Continued)

       status = KcsGetLastError(&errDesc);  
       printf("UpdateProfile error: %s\n", errDesc.desc);  
       KcsFreeProfile(profileid);  
       exit(1);  
  }  
  free(calData);  

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.
Code 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. You do this with the same mechanism as that described in steps 2 to 5 of "Loading a Profile" on page 34.
Code 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) {  

Code Example 3-11 Saving a Profile to a Different Description

       status = KcsGetLastError(&errDesc);  
       printf("SaveProfile error: %s\n", errDesc.desc);  
  }