Important: The information in this document is obsolete and should not be used for new development.
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.
To extract an embedded profile, your application can use the
- Note
- If you use the high-level
NCMDrawMatchedPicture
function, you do not need to extract the source profile from the PICT file.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, theCMUnflattenProfile
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:
- count the profiles in the PICT file
- extract a profile
Step 1: Count the Profiles in the PICT File
Given apicHandle
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, theTextProc
bottleneck can be defined as follows:
static pascal void TextProc (short byteCount, Ptr textAddr, Point numer, Point denom);MyCountProfilesInPicHandle
calls its ownMyDrawPicHandleUsingBottleneck
function, not shown here, to draw the picture using the bottleneck routines. Because it must increment thegCount
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 anindex
parameter that passes in the profile's index. The function stores the index in the global variablegIndex
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 theCMUnflattenProfile
function, passing it theMyflattenUPP
pointer. This invokes theMyUnflattenProc
function shown in Listing 4-9.After calling
CMUnflattenProfile
, the MyGetIndexedProfileFromPicHandle function calls theCMOpenProfile
function to open a reference to the file-based profile; then it callsCMCopyProfile
to create a temporary profile. Finally, the function disposes of the original profile. To adhere to the copyright protection for embedded profiles specified by theflags
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 theCMUnflattenProc
function, passing it a pointer to theMyUnflattenProc
function, theMyUnflattenProc
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 MyUnflattenPro
c
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 theMyUnflattenProfilesCommentProc
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 theMyUnflattenProc
function'sMyDrawPicHandleUsingBottlenecks
function calls theMyUnflattenProfilesCommentProc
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 thegChunkBaseHndl
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; } }