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:
Loading profiles
Connecting profiles
Evaluating data
Freeing profiles
Getting and setting attributes
Accessing characterization and calibration data
Saving profiles
Within the primary framework, events are illustrated and described in sequence to explain what actually takes place when each "C" API call is made.
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.
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.
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:
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.
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.
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.
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.
// 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); }
Once a KcsIO object has been created, the profile can be loaded. Figure 3-4 illustrates creating a KcsProfile object.
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.
Once a KcsProfile object has been created, you can ask it to load itself using the generated KcsIO. Figure 3-5 illustrates the process.
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.
The pointers to objects contained within the KcsProfileFormat object load themselves using the KcsChunkSet class. Figure 3-6 illustrates the process.
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.
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.
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.
Example 3-4 is a "C" API code excerpt that shows how to connect two profiles together once they have been loaded.
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:
It calls getNewValidProfileIndex()method to get a new valid index for the connected profile from the dynamically allocated global array of profiles.
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.
It creates a KcsProfile object that can link together sequences of profiles.
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.)
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.
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.
Once connected, it returns the new profile Id to the calling application for later reference, and generally cleans up the classes.
The evaluation path of data is different for unoptimized and optimized sequences. Figure 3-7 shows both paths.
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.
/* 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); }
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.
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 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.
The examples below show how to get and set attributes.
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.
/* 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); }
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.
/* 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 are accessed using the following KCMS "C" API calls:
KcsCreateProfile()
KcsUpdateProfile()
KcsSetAttribute()
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 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.
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.
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 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.
status = KcsSaveProfile(profileid, NULL); if(status != KCS_SUCCESS) { status = KcsGetLastError(&errDesc); printf("SaveProfile error: %s\n", errDesc.desc); }
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.
/* 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); }