Appendix B. Listing of the Application cocoifl

This appendix presents source code for a working color conversion application that uses the Coloratura CMS. A summary of the operations performed appears in "Example Outline of a Color Conversion Program". The sections of this appendix correspond to the sections of the summary.

The program, which is called cocoifl and is in /usr/cms/examples, is written in C++ because it uses the C++ bindings of the Image Format Library (IFL) to manage image files. However, cocoifl does not rely heavily on techniques specific to C++; programmers who know only C can benefit from looking at this example. The IFL also has C bindings, so you could convert cocoifl into a C program. The data flow for cocoifl is the same as that illustrated in Figure 1-2, but without intermediate profiles.

The program cocoifl works; it is not a piece of pseudocode. Therefore, it includes code for manipulating files and error handling, code that is, strictly speaking, irrelevant to the Coloratura CMS.

As you look at the source code, recall that the names of Coloratura objects use the following convention: function names start with "cms", type names start with "CMS", and library constant names start with "CMS_". The names of IFL objects begin with "ifl". In the following discussion, little is said about the IFL objects. For more information about the IFL see the ImageVision Library Programming Guide and the reference page IFL(3).

The application takes an input image file and input and destination profiles, and performs a color conversion (hence "coco") on an output image file. The usage is:

cocoifl [-s < src profile> | -a < src profile>] -d <dst profile> -o <outfile> <infile>

With the -s option the source profile is always used; with the -a option, the source profile is used if the input image does not have an embedded profile. The syntax is similar to that for the color conversion commands supplied by the Coloratura CMS; see the man pages cocogif(1), cocojpeg(1), and cocostiff(1).

The source code for cocoifl contains two functions: an error handler and a main.

Code for Loading Header Files

//See page 9
// cocoifl:
//
//    A simple color management program 
//    using the Image File Library (IFL)
//
 
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <getopt.h>
 
#include <ifl/iflError.h>
#include <ifl/iflFormat.h>
#include <ifl/iflFile.h>
#include <ifl/iflDataSize.h>
#include <ifl/iflConfig.h>
#include <ifl/iflTileIter.h>
#include <ifl/iflMinMax.h>
#include "ic.h"
#include "cms.h"
 
// simple function to decode IFL status, print message and exit
 
void
errorExit(char* prefix, iflStatus err)
{
    char msg[1024];
    iflStatusToString(err, msg, sizeof msg);
    fprintf(stderr, "cocoifl: %s: %s\n", prefix, msg);
    exit(EXIT_FAILURE);
}

Code for Declaring Variables

// see page 9
void
main(int argc, char* argv[])
{
    int  c, fPassThru;
    char *nameAnon, *nameSrc, *nameDst, *nameIn, *nameOut;
    CMSProfile profSrc, profDst, profs[2];
    CMSContext ctxt;  
    CMSPixelBuffer      pbIn, pbOut;
    CMSTfm tfm;
    icHeader header;
    int32 status;
    iflColorModel cm;
    void* profileData;
    int  profileSize;
    void*  bufOut;
    iflSize pgSizeOut, pgSizeIn;
    fPassThru = FALSE;

Code for Opening the Coloratura CMS, the Input Image File, and the Output Profile

    // see page 9
 
    if( cmsOpen(&ctxt) != CMS_SUCCESS) {
           fprintf(stderr, "Can't open CMS\n");
           exit(EXIT_FAILURE);
    }
    profSrc = (CMSProfile) NULL;
    while ((c = getopt(argc, argv, "a:d:ho:s:")) != -1) {
       switch (c) {
       case `a':
       nameAnon = optarg;
       break;
 
       case `d':
       nameDst = optarg;
       break;
 
       case `o':
       nameOut = optarg;
       break;
 
       case `s':
       nameSrc = optarg;
       break;
 
       case `h':
       default:
        fprintf(stderr, "Usage: cocoifl <infilename> <outfilename>\n");
        exit(EXIT_FAILURE);
       }
    }
 
    iflStatus err;
    iflFile *in;
 
    if (optind < argc) {
        nameIn = argv[argc-1];
        in = iflFile::open(nameIn, O_RDONLY, &err);
    } else {
         // no filename given, read from stdin 
         in = iflFile::open(0, NULL, O_RDONLY, NULL, &err);
    }
 
    if (in == NULL) errorExit("Unable to open input file", err);
 
    // Set up the output file. Most parameters match the input, but
    // the number of channels and colorModel may change.

    if(cmsOpenProfile(ctxt, nameDst, &profDst) != CMS_SUCCESS) {
        fprintf(stderr, "Can't open dest profile %s\n", nameDst);
        exit(EXIT_FAILURE); 
    }
         cmsGetProfileHeader(ctxt, profDst, &header); 

Code for Preparing the Output Pixel Buffer and Open an Output Image File

// page 10
pbOut.encode = header.colorSpace;
pbOut.channels = 3;
switch (header.colorSpace) {
case icSigXYZData:
case icSigLabData:
case icSigLuvData:
case icSigYCbCrData:
case icSigYxyData:
    cm = iflMultiSpectral;
    break;
 
case icSigRgbData:
    // this is needed for GIF in particular
    if (in->getColorModel() == iflRGBPalette) 
    cm = iflRGBPalette;
    else
    cm = iflRGB;
    break;
 
case icSigGrayData:
     cm = iflLuminance;
     pbOut.channels = 1;
     break;
 
case icSigHsvData:
     cm = iflHSV;
     break;
 
case icSigHlsData:
case icSigCmykData:
     cm = iflCMYK;
     pbOut.channels = 4;
     break;
 
case icSigCmyData:
     cm = iflCMY;
     pbOut.channels = 3;
     break;
 
case icSig2colorData:
     pbOut.channels = 2;
     cm = iflMultiSpectral;
     break;
case icSig15colorData:
    pbOut.channels++;
case icSig14colorData:
    pbOut.channels++;
case icSig13colorData:
    pbOut.channels++;
case icSig12colorData:
    pbOut.channels++;
case icSig11colorData:
    pbOut.channels++;
case icSig10colorData:
    pbOut.channels++;
case icSig9colorData:
    pbOut.channels++;
case icSig8colorData:
    pbOut.channels++;
case icSig7colorData:
    pbOut.channels++;
case icSig6colorData:
    pbOut.channels++;
case icSig5colorData:
    pbOut.channels++;
case icSig4colorData:
    pbOut.channels++;
case icSig3colorData:
    pbOut.channels++;
    cm= iflMultiSpectral;
    break;
}
 
iflSize sizeSetup;
in->getSize(sizeSetup, in->getOrientation());
if ( cm != iflRGBPalette)
    sizeSetup.c = pbOut.channels;
iflFileConfig cfgOut = iflFileConfig(&sizeSetup,
                                     in->getDataType(),
                                     in->getOrder(),
                                     cm,
                                     in->getOrientation(),
                                     in->getCompression());
iflFile *out = iflFile::create(nameOut,
                               in,
                               &cfgOut,
                               in->getFormat(),
                               &err);
if (out == NULL) errorExit("Unable to create output file", err);

Code for Selecting an Input Profile

// see page 10
// Try to open a source profile. Here's the order we search:
// 1) profile specified with -s
// 2) embedded profile
// 3) profile specified with -a
// 4) default profile
// if any of these are specified and fail, we just pass the 
// image through unmodified
 
if (nameSrc != (char *) NULL) {
    // profile specified with -s is always used 
    if(cmsOpenProfile(ctxt, nameSrc, &profSrc) != CMS_SUCCESS) {
       fprintf(stderr, "Can't open source profile %s\n", nameSrc);
       fPassThru = TRUE;
     }
} else if (in->getICCProfile(profileSize, profileData) == iflOKAY) {
    // look for embedded profile.
    if (cmsImportProfile(ctxt, (uint32) profileSize, profileData,       
                         &profSrc) != CMS_SUCCESS) {
        fprintf(stderr, "Can't open embedded profile");
        fPassThru = TRUE;
 }
     in->freeICCProfile(profileData);
} else if (nameAnon != (char *)NULL) {
    // look for anonymous profile
    if(cmsOpenProfile(ctxt, nameAnon, &profSrc) != CMS_SUCCESS) {
        fprintf(stderr, "Can't open anonymous profile %s\n", nameAnon);
        fPassThru = TRUE;
     }
} else {
    // look for default profile based on number of image type 
    switch(in->getColorModel()) {
    case iflRGB:
             if(cmsOpenProfile(ctxt, CMS_DEFAULT_MONITOR, &profSrc) !=
               CMS_SUCCESS) {
                 fprintf(stderr, "Can't open default profile %s\n",
                   CMS_DEFAULT_MONITOR);
                 fPassThru = TRUE;
             }
             break;
    case iflCMYK:
             if(cmsOpenProfile(ctxt, CMS_DEFAULT_CMYK, &profSrc) !=
               CMS_SUCCESS) {
                 fprintf(stderr, "Can't open default profile %s\n",
                   CMS_DEFAULT_CMYK);
                 fPassThru = TRUE;
             }
             break;
    default:
            fPassThru = TRUE;
            break;
    }
}

Code for Creating a Transform and Initializing Buffers

// see page 10
if (!fPassThru) {
     profs[0] = profSrc;
     profs[1] = profDst;
     if((status = cmsCreateTfm(ctxt, 2, profs, 
         CMS_USE_DEFAULT_CMM, &tfm)) != CMS_SUCCESS) {
           fprintf(stderr, "Can't create the transform: returned %d\n",
             status);
           fPassThru = TRUE;
     }
}
 
out->getPageSize(pgSizeOut, out->getOrientation());
pbOut.bitsPerChannel = 8;
pbOut.bytesPerPixel = pbOut.channels;
pbIn.bitsPerChannel = 8;
 
if (cm == iflRGBPalette) {
    const iflColormap  *cmap;
 
    in->getColormap(cmap);
    int numChan = cmap->getNumChans();
    iflDataType dataType = cmap->getDataType();
    int length = cmap->getLength();
 
     if (numChan == 3 && dataType == iflUChar) {
         unsigned char buf[768], *pc;
         unsigned char *pr = (unsigned char *)cmap->getChan(0);
         unsigned char *pg = (unsigned char *)cmap->getChan(1);
         unsigned char *pb = (unsigned char *)cmap->getChan(2);
 
         // interleave the channels
         pc = buf;
         for (int i = 0; i < length; i++) {
             *pc++ = *pr++;
             *pc++ = *pg++;
             *pc++ = *pb++;
         }
         pbOut.width = length;
         pbOut.height = 1;
         pbOut.data = buf;
 
         pbIn.width = length;
         pbIn.height = 1;
         pbIn.channels = 3;
         pbIn.bytesPerPixel = 3;
         pbIn.encode = icSigRgbData;
         pbIn.data = buf;
 
         if ((status = cmsApplyTfm(ctxt, tfm, &pbIn, &pbOut)) !=
          CMS_SUCCESS){
            fprintf(stderr, "Can't apply tfm: returned %d\n", status);
            exit (EXIT_FAILURE);
         }
 
         unsigned char bufOut[768];
         pr = bufOut;
         pg = pr + 256;
         pb = pg+256;
         pc = buf;
         // repack the channels
         for (i = 0; i < length; i++) {
              *pr++ = *pc++;
              *pg++ = *pc++;
              *pb++ = *pc++;
         }
 
         iflColormap  cmapOut = iflColormap(bufOut, 3, dataType, 0,
             length -1);
         cmapOut.setData(bufOut);
         out->setColormap(&cmapOut);
    }
    fPassThru = TRUE;
} else if (!fPassThru) {
 
    // allocate an output buffer for modified pixels 
    int bufsizeOut = iflDataSize(out->getDataType(), pgSizeOut);
 
    bufOut = new char [bufsizeOut];
    if (bufOut == NULL) {
        fprintf(stderr, "cocoifl: unable to allocate %d bytes\n",
          bufsizeOut);
        exit(EXIT_FAILURE);
    }
    pbOut.data = bufOut;
}
// now set up the input  
cmsGetProfileHeader(ctxt, profSrc, &header); 
pbIn.channels = in->getCsize();
pbIn.bytesPerPixel = pbIn.channels; // XXX only 1 byte/channel for now
pbIn.encode = header.colorSpace;
 
in->getPageSize(pgSizeIn, in->getOrientation());
int bufsizeIn = iflDataSize(in->getDataType(), pgSizeIn);
void* bufIn = new char [bufsizeIn];
if (bufIn == NULL) {
    fprintf(stderr, "cocoifl: unable to allocate %u bytes\n",     
      bufsizeIn);
    exit(EXIT_FAILURE);
}
pbIn.data = bufIn;
if (fPassThru) {
    // We'll just copy output from input without any color transform
    // on the pixels.  There may already have been a tranform on the
    // colormap.
    pbOut.data = bufIn;
}
 
int sizeProf;
void *dataProf;

Code for Embedding the Output Profile in the Output Image File

// see page 11
 
if (cmsExportProfile(ctxt, profDst, (uint32 *) &sizeProf, (void **)
                     &dataProf) == CMS_SUCCESS) {
    (void)  out->setICCProfile(sizeProf, dataProf);
    cmsFreeProfileExport(ctxt, dataProf);
}

Code for Transforming Pixel Data and Cleaning Up

// see page 11
iflSize sizeOut;
out->getSize(sizeOut, out->getOrientation());
iflConfig config(out->getDataType(), out->getOrder(), sizeOut.c,
                  NULL, 0, out->getOrientation());
 
iflTileIter iter(iflTile3Dint(0, 0, 0, sizeOut.x, sizeOut.y,
    sizeOut.z), pgSizeOut, sizeOut.c);
 
while (iter.more()) {
    iflSize rwSize(iflMin(sizeOut.x - iter.x, pgSizeOut.x),
                   iflMin(sizeOut.y - iter.y, pgSizeOut.y),
                   iflMin(sizeOut.z - iter.z, pgSizeOut.z),
                   iflMin(sizeOut.c - iter.c, pgSizeOut.c));
 
    err = in->getTile(iter.x, iter.y, iter.z,
                      rwSize.x, rwSize.y, rwSize.z,
                      bufIn, &config);
    if (err != iflOKAY) errorExit("Couldn't read page from input", 
                                    err);
 
    if (!fPassThru) {
        // the width and height may change with each tile
        pbOut.width = pbIn.width = rwSize.x;
        pbOut.height = pbIn.height = rwSize.y;
        if ((status = cmsApplyTfm(ctxt, tfm, &pbIn, &pbOut)) !=
          CMS_SUCCESS){
            fprintf(stderr, "Can't apply tfm: returned %d\n", status);
            exit (EXIT_FAILURE);
        }
    }
 
    err = out->setPage(pbOut.data, iter.x, iter.y, iter.z, iter.c,
                       rwSize.x, rwSize.y, rwSize.z, pbOut.channels);
    if (err != iflOKAY) errorExit("Couldn't write page to output",  
                                     err);
}
delete[] bufIn;
delete[] bufOut;
err = out->flush();
if (err != iflOKAY) errorExit("Error flushing output file", err);
err = out->close();
if (err != iflOKAY) errorExit("Error closing output file", err);
err = in->close();
if (err != iflOKAY) errorExit("Error closing input file", err);
 
}