Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Advanced Color Imaging on the Mac OS /
Chapter 4 - Developing ColorSync-Supportive Applications / Developing Your ColorSync-Supportive Application


Extracting Profiles Embedded in Pictures

To color match or gamut check a picture embedded in a document, your application must first extract the source profile used to create the image, if the profile is embedded in the document, then open a reference to the profile. This process entails locating and identifying the profile for the image within the document and transferring the profile data from the document file.

Note
If you use the high-level NCMDrawMatchedPicture function, you do not need to extract the source profile from the PICT file.
To extract an embedded profile, your application can use the CMUnflattenProfile function. This function takes a pointer to a low-level data-transfer function that your application must supply to transfer the profile data from the document containing it. This function assumes that your low-level data-transfer function is informed about the context of the profile. After all of the profile data has been transferred, the CMUnflattenProfile function returns the file specification for the profile.

When your application calls the CMUnflattenProfile function, the ColorSync Manager uses the Component Manager to pass the pointer to your low-level data-transfer function along with the reference constant your application can use as it desires. If available, the preferred CMM calls your low-level data-transfer function. (If the preferred CMM is not available, the ColorSync Manager follows the CMM selection algorithm described in "How the ColorSync Manager Selects a CMM" (page 4-7), to determine which CMM to use.) The CMM calls your low-level data-transfer function, directing it to open the file containing the profile, read segments of the profile data, and return the data to the CMM's calling function.

The CMM communicates with your low-level data-transfer function using a command parameter to identify the operation to perform. To facilitate the transfer of profile data from the file to the CMM, the CMM passes to your function a pointer to a data buffer for data, the size in bytes of the profile data your function should return, and the reference constant passed from the calling application.

On return, your function passes to the CMM segments of the profile data and the number of bytes of profile data you actually return.

Listing 4-7 and Listing 4-8 show portions of a sample application called CSDemo, available as part of the ColorSync SDK. You can find the complete sample application at the following web site:


ftp://ftp.apple.com/developer/Development_Kits/
These listings assume that all variables beginning with a lowercase letter "g" are global variables previously defined. The application uses global variables to pass data between functions that do not include reference constant parameters. The listings implement two primary steps:

Step 1: Count the Profiles in the PICT File

Given a picHandle value to the picture containing the embedded profile, the sample code shown in Listing 4-7 counts the number of profiles in the picture to identify the profile to extract.

The MyCountProfilesInPicHandle function sets up the port and its bottlenecks and initializes its global counter, which holds a single count summing both ColorSync 1.0 profiles and version 2.x profiles. MyCountProfilesInPicHandle counts the number of profiles as the picture is being drawn, using the MyCountProfilesCommentProc bottleneck procedure. MyCountProfilesInPicHandle doesn't use any other bottlenecks, so it defines nonoperational routines for them. For example, the TextProc bottleneck can be defined as follows:

static pascal void TextProc (short byteCount, Ptr textAddr, 
                      Point numer, Point denom);
MyCountProfilesInPicHandle calls its own MyDrawPicHandleUsingBottleneck function, not shown here, to draw the picture using the bottleneck routines. Because it must increment the gCount global counter for both ColorSync 1.0 profiles and version 2.x profiles, MyCountProfilesCommentProc checks for both types of profiles.

Listing 4-7 Counting the number of profiles in a picture

CMError MyCountProfilesInPicHandle (PicHandle pict, unsigned long *count)
{
   OSErr    err = noErr;
   CQDProcs procs;

   /* set up bottleneck for picComments so we can count the profiles */
   SetStdCProcs(&procs);
   procs.textProc= NewQDTextProc (MyNoOpTextProc);
   procs.lineProc= NewQDLineProc (MyNoOpLineProc);
   procs.rectProc= NewQDRectProc (MyNoOpRectProc);
   procs.rRectProc = NewQDRRectProc (MyNoOpRRectProc);
   procs.ovalProc = NewQDOvalProc (MyNoOpOvalProc);
   procs.arcProc = NewQDArcProc (MyNoOpArcProc);
   procs.polyProc = NewQDPolyProc (MyNoOpPolyProc);
   procs.rgnProc = NewQDRgnProc (MyNoOpRgnProc);
   procs.bitsProc = NewQDBitsProc (MyNoOpBitsProc);
   procs.commentProc = NewQDCommentProc (MyCountProfilesCommentProc);
   procs.txMeasProc = NewQDTxMeasProc (MyNoOpTxMeasProc);

/* initialize the global counter to be incremented by the commentProc*/
   gCount = 0;

/* draw the picture and count the profiles while drawing */
   err = MyDrawPicHandleUsingBottlenecks (pict, procs, nil);

/* obtain the result from the count global variable */
   *count = gCount;

/* clean up and return*/
   DisposeRoutineDescriptor(procs.textProc);
   DisposeRoutineDescriptor(procs.lineProc);
   DisposeRoutineDescriptor(procs.rectProc);
   DisposeRoutineDescriptor(procs.rRectProc);
   DisposeRoutineDescriptor(procs.ovalProc);
   DisposeRoutineDescriptor(procs.arcProc);
   DisposeRoutineDescriptor(procs.polyProc);
   DisposeRoutineDescriptor(procs.rgnProc);
   DisposeRoutineDescriptor(procs.bitsProc);
   DisposeRoutineDescriptor(procs.commentProc);
   DisposeRoutineDescriptor(procs.txMeasProc);
}

pascal void MyCountProfilesCommentProc (short kind,
                              short dataSize,
                              Handle dataHandle)
{
   long  selector;

   switch (kind)
   {
      case cmBeginProfile
         gCount ++;           /* we found a ColorSync 1.0 profile */
                              /* increment the counter*/
         break;

      case cmComment; 
         if (dataSize <= 4) break;/* dataSize is too small for selector
                               so break and get the selector 
                               from the first long */
         selector = *((long *)(*dataHandle));
         if (selector == cmBeginProfileSel) 
            gCount ++;        /* we found a version 2 profile; increment
                                 the counter */
            break;               
   }
}

Step 2: Extract the Profile

This part of the sample application identifies the profile to flatten, flattens the profile, and creates a temporary profile disposing of the original one. To perform these tasks, the code must again draw the picture using the bottleneck routines.

Part A: Calling the Unflatten Function

Listing 4-8 shows the MyGetIndexedProfileFromPicHandle entry point function that drives the process of unflattening the profile. The function creates a universal procedure pointer (UPP), MyflattenUPP, that points to the low-level data-transfer procedure.

A PICT handle may contain more than one profile. To identify the profile to unflatten, the MyGetIndexedProfileFromPicHandle function contains an index parameter that passes in the profile's index. The function stores the index in the global variable gIndex so that the value is accessible by the application's other functions that check for the correct profile and extract it. Then, the function calls the CMUnflattenProfile function, passing it the MyflattenUPP pointer. This invokes the MyUnflattenProc function shown in Listing 4-9.

After calling CMUnflattenProfile, the MyGetIndexedProfileFromPicHandle function calls the CMOpenProfile function to open a reference to the file-based profile; then it calls CMCopyProfile to create a temporary profile. Finally, the function disposes of the original profile. To adhere to the copyright protection for embedded profiles specified by the flags field in the profile header, MyGetIndexedProfileFromPicHandle creates a temporary profile and disposes of the original.

Listing 4-8 Calling the unflatten profile function to extract an embedded profile

CMError MyGetIndexedProfileFromPicHandle (PicHandle pict,
                                 unsigned long index,
                                 CMProfileRef *prof,
                                 CMProfileLocation *profLoc)
{
   unsigned long  refCon;
   CMFlattenUPP   MyflattenUPP;
   CMError        cmErr = noErr;
   Boolean        preferredCMMNotFound;
   FSSpec         tempSpec;
   CMProfileRef   tempProf;
   CMProfileLocation tempProfLoc;

   /* create a universal procedure pointer for unflatten procedure */
   MyflattenUPP = NewCMFlattenProc(MyUnflattenProc);

   /* following assumes that index <= count */

   refCon = (unsigned long) pict;
   gIndex = index;   


   /* next call invokes the MyUnflattenProc shown in Listing 4-9 */
   cmErr = CMUnflattenProfile(&tempSpec, MyflattenUPP,(void*)&refCon, 
                        &preferredCMMNotFound);
   DisposeRoutineDescriptor(MyflattenUPP);

   if (cmErr)
      return cmErr;

   tempProfLoc.locType = cmFileBasedProfile;
   tempProfLoc.u.fileLoc.spec = tempSpec;
   cmErr = CMOpenProfile(&tempProf, &tempProfLoc);
   if (cmErr)
      return cmErr;
   cmErr = CMCopyProfile(prof, profLoc, tempProf);
   cmErr = CMCloseProfile(tempProf);
   cmErr = FSpDelete(&tempSpec);

   return cmErr;
}

Part B: Calling the Unflatten Function

When the code in MyGetIndexedProfileFromPicHandle (Listing 4-8) calls the CMUnflattenProc function, passing it a pointer to the MyUnflattenProc function, the MyUnflattenProc function (Listing 4-9) is called by the CMM to perform the low-level profile data transfer from the document file.

When the CMM calls this function with an open command, the function initializes global variables, creates a graphics world, and installs bottleneck procedures in the graphics world. The only bottleneck procedure actually used is MyUnflattenProfilesCommentProc, which checks the picture comments as the picture is drawn offscreen to identify the desired profile.

When the CMM calls the MyUnflattenProc function with a read command, the function reads the appropriate segment of data from a chunk and returns it. To accomplish this, it calls the MyDrawPicHandleUsingBottlenecks function with the appropriate bottleneck procedure installed. In turn, this invokes the MyUnflattenProfilesCommentProc shown in Listing 4-10.

When the CMM calls MyUnflattenProc with a close command, the function releases any memory it allocated and disposes of the graphics world and bottlenecks.

Listing 4-9 The unflatten procedure

pascal OSErr MyUnflattenProc (long command,
                        long *sizePtr,
                        void *dataPtr,
                        void *refConPtr)
{
   OSErr             err = noErr;
   static CQDProcs   procs;
   static GWorldPtr  offscreen;
   PicHandle         pict;
   switch (command)
   {
      case cmOpenReadSpool:
         err = NewSmallGWorld(&offscreen);
         if (err)
            return err;
         SetStdCProcs(&procs);
         procs.textProc    = NewQDTextProc (MyNoOpTextProc);
         procs.lineProc    = NewQDLineProc (MyNoOpLineProc);
         procs.rectProc    = NewQDRectProc (MyNoOpRectProc);
         procs.rRectProc   = NewQDRRectProc (MyNoOpRRectProc);
         procs.ovalProc    = NewQDOvalProc (MyNoOpOvalProc);
         procs.arcProc     = NewQDArcProc (MyNoOpArcProc);
         procs.polyProc    = NewQDPolyProc (MyNoOpPolyProc);
         procs.rgnProc     = NewQDRgnProc (MyNoOpRgnProc);
         procs.bitsProc    = NewQDBitsProc(MyNoOpBitsProc);
         procs.commentProc = NewQDCommentProc (MyUnflattenProfilesCommentProc);
         procs.txMeasProc  = NewQDTxMeasProc (MyNoOpTxMeasProc);

         gChunkBaseHndl = nil;
         gChunkIndex = 0;
         gChunkOffset = 0;
         gChunkSize = 0;
         break;

   case cmReadSpool:
      if (gChunkOffset > gChunkSize)/* if we overread the last chunk */
      {
         return ioErr;           /* use system I/O error value */
      }
      if (gChunkOffset == gChunkSize)/* if we used up the last chunk */
      {
         if (gChunkBaseHndl !=nil)
         {
            HUnlock(gChunkBaseHndl);/* dispose of the previous chunk */
            DisposeHandle(gChunkBaseHndl);
            gChunkBaseHndl = nil;
         }
         gChunkIndex++;       /* read in a new chunk */
         gChunkOffset = 0;
         gCount = 0;
         gChunkCount = 0;
         pict = *((PicHandle *)refConPtr);
         err = MyDrawPicHandleUsingBottlenecks (pict, procs, offscreen);
                     /* this invokes MyUnflattenProfilesCommentProc shown in
                        Listing 4-10 */
         if (gChunkBaseHndl==nil)/* check to see if we're overread */
            return ioErr;     /* if so, return system I/O error value */
         HLock(gChunkBaseHndl);
      }
      if (gChunkOffset < gChunkSize)
      {
         *sizePtr = MIN(gChunkSize-gChunkOffset, *sizePtr);
         BlockMove((Ptr)(&((*gChunkBaseHndl)[gChunkOffset])),
            (Ptr)dataPtr, *sizePtr);
         gChunkOffset += (*sizePtr);
      }
      break;
      case cmCloseSpool:
         if (gChunkBaseHndl != nil) 
         {
            HUnlock(gChunkBaseHndl);/* dispose of the previous chunk */
            DisposeHandle(gChunkBaseHndl);
            gChunkBaseHndl = nil;
         }
         DisposeGWorld(offscreen);
         DisposeRoutineDescriptor(procs.TextProc);
         DisposeRoutineDescriptor(procs.LineProc);
         DisposeRoutineDescriptor(procs.RectProc);
         DisposeRoutineDescriptor(procs.RRectProc);
         DisposeRoutineDescriptor(procs.OvalProc);
         DisposeRoutineDescriptor(procs.ArcProc);
         DisposeRoutineDescriptor(procs.PolyProc);
         DisposeRoutineDescriptor(procs.RgnProc);
         DisposeRoutineDescriptor(procs.BitsProc);
         DisposeRoutineDescriptor(procs.MyUnflattenProfilesCommentProc);
         DisposeRoutineDescriptor(procs.txMeasProc);
         break;
      default:
         break;
   }
   return err;
}

Part C: Calling the Comment Procedure

When the MyUnflattenProc function's MyDrawPicHandleUsingBottlenecks function calls the MyUnflattenProfilesCommentProc function, the function shown in Listing 4-10 finds the profile identified by the index, finds the correct segment of data within the profile, and stores the data in the gChunkBaseHndl global variable.

Listing 4-10 The comment procedure

pascal void MyUnflattenProfilesCommentProc (short kind,
                                 short dataSize,
                                 Handle dataHandle)
{
   long  selector;
   OSErr err;

   if (gChunkBaseHndl != nil) return;
            /* the handle is in use; this shouldn't happen */
   if (gCount > gIndex) return;
            /* we have already found the profile */
   switch (kind)
   {
   case cmBeginProfile:
      gCount ++;     /* we found a version 1 profile */
      gChunkCount = 1;/* v1 profiles should only have 1 chunk */
      if (gCount != gIndex) break;
                     /* this is not the profile we're looking for */
      if (gChunkCount != gChunkIndex) break;
                     /* this is not the chunk we're looking for */
      gChunkBaseHndl = dataHandle;
      err = HandToHand(&gChunkBaseHndl);
      gChunkSize = dataSize;
      gChunkOffset = 0;
      break;

   case cmComment:
      if (dataSize <= 4) break;
                     /* the dataSize too small for selector, so break */
      selector = *((long *)(*dataHandle));
                     /* get the selector from the first long in data */
      switch (selector)
      {
         case cmBeginProfileSel:
            gCount ++;        /* we found a version 2 profile */
            gChunkCount = 1;
            if (gCount != gIndex) break;
                           /* this is not the profile we're looking for */
            if (gChunkCount!=gChunkIndex) break;
                           /* this is not the chunk we're looking for */
            gChunkBaseHndl = dataHandle;
            err = HandToHand(&gChunkBaseHndl);
            gChunkSize = dataSize;
            gChunkOffset = 4;
            break;

         case cmContinueProfileSel:
            gChunkCount ++;
            if (gCount != gIndex) break;
                           /* this is not the profile we're looking for */
            if (gChunkCount!=gChunkIndex) break;
                           /* this is not the chunk we're looking for */
            gChunkBaseHndl = dataHandle;
            err = HandToHand(&gChunkBaseHndl);
            gChunkSize = dataSize;
            gChunkOffset = 4;
            break;

         case cmEndProfileSel:
                           /* check to see if we're overreading */
            gChunkCount = 0;
            break;
      }
      break;
   }     
}

Previous Book Contents Book Index Next

© Apple Computer, Inc.
13 NOV 1996