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.
//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); } |
// 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; |
// 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); |
// 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); |
// 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; } } |
// 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; |
// see page 11 if (cmsExportProfile(ctxt, profDst, (uint32 *) &sizeProf, (void **) &dataProf) == CMS_SUCCESS) { (void) out->setICCProfile(sizeProf, dataProf); cmsFreeProfileExport(ctxt, dataProf); } |
// 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); } |