PATHDocumentation > Mac OS 8 and 9 > Mutlimedia and Graphics > ColorSync Manager >

Managing Color With ColorSync


Extracting Profiles Embedded in Pictures

To color match or gamut check a picture embedded in a document, your application should first check for embedded profiles in the document. If a profile is found, your application can then open a reference to the profile and use it as the source profile. This process requires you to locate and identify the profile for the image within the document and extract the profile data from the document file.

Note

If you use the QuickDraw-specific 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 function CMUnflattenProfile . This function takes a pointer to a low-level data-transfer function that your application supplies 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.

Prior to ColorSync 2.5, 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. The CMM is determined by the selection process described in How the ColorSync Manager Selects a CMM . 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.

Starting with ColorSync 2.5, the ColorSync Manager calls your transfer function directly, without going through the preferred, or any, CMM. On return from CMUnflattenProfile , the value of preferredCMMnotfound is guaranteed to be false .

Listing 3-9 and Listing 3-10 show portions of a sample application called CSDemo, available as part of the ColorSync SDK. You can find the complete sample application on the Developer CD series, or at the web site http://developer.apple.com/sdk.

In these listings, 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. Listing 3-9 counts the profiles in a PICT file, while Listing 3-10 extracts a profile, identified by an index number, from a PICT file.

Counting the Profiles in the PICT File

Given a picHandle value to a picture containing an embedded profile, the sample code shown in Listing 3-9 counts the number of profiles in the picture.

The MyCountProfilesInPicHandle function calls the Toolbox function SetStdCProcs to get the current QuickDraw drawing bottleneck procedures, then sets the bottlenecks to its own routines. It initializes its global counter, gCount , which holds a single count summing both ColorSync 1.0 profiles and version 2.x profiles, to zero. The MyCountProfilesInPicHandle function calls its own drawing function, MyDrawPicHandleUsingBottleneck , not shown here, to draw the picture. The drawing function sets up a port that uses the private bottleneck routines.

As the picture is drawn, the MyCountProfilesCommentProc bottleneck procedure counts the number of profiles encountered. MyCountProfilesCommentProc checks for both version 1.0 profiles and version 2.x profiles and increments the global count when it finds either type. You can easily modify this code to keep separate counts if necessary.

MyCountProfilesInPicHandle doesn't use any other QuickDraw bottlenecks, so it uses nonoperational routines (routines that do nothing but return) for all other bottlenecks. The prototype for a function to handle the TextProc bottleneck, for example, can be defined as follows:

static pascal void MyNoOpTextProc ( short byteCount,
                                    Ptr textAddr,
                                    Point numer,
                                    Point denom);

For a general discussion of customizing QuickDraw's bottleneck routines, see "Customizing QuickDraw's Text Handling" in Inside Macintosh: Text .

Listing 3-9 Counting the number of profiles in a picture

CMError MyCountProfilesInPicHandle (PicHandle pict, unsigned long *count)
{
    OSErr       theErr = 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. */
    theErr = MyDrawPicHandleUsingBottlenecks (pict, procs, nil);

/* Obtain the result from the count global variable. */
    *count = gCount;

/* Clean up and return. */
    DisposeRoutineDescriptor(procs.textProc);
    DisposeRoutineDescriptor(proc.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 count.
            break;

        case cmComment;
            // Break if dataSize is too small to be a selector.
            if (dataSize <= 4) break;

            // Since dataSize is >= 4, we can get a selector from the first long.
            selector = *((long *)(*dataHandle));
            if (selector == cmBeginProfileSel)
                gCount ++;  // We found a ColorSync 2.xprofile; increment the count.
            break;                      
    }
}

Extracting a Profile

Flattening refers to transferring a profile stored in an independent disk file to an external profile format that can be embedded in a graphics document. Unflattening refers to transferring from the embedded format to an independent disk file.

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

Part A: Calling the Unflatten Function

Listing 3-10 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 specifies 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 3-11 .

The function MyGetIndexedProfileFromPicHandle, shown in Listing 3-10 , first calls CMUnflattenProfile to create an independent file-based profile, then calls the function CMOpenProfile to open a temporary profile reference to the file-based profile. It then calls CMCopyProfile to create a copy of the profile reference. Finally, the function disposes of the original profile. The purpose for creating a temporary profile, copying it into the specified location, then deleting the temporary profile, is to adhere to the copyright protection for embedded profiles specified by the flags field in the profile header.

Listing 3-10 Calling the CMUnflattenProfile function to extract an embedded profile

CMError MyGetIndexedProfileFromPicHandle (PicHandle pict,
                                            unsigned long index,
                                            CMProfileRef *prof,
                                            CMProfileLocation *profLoc)
{
    CMError             theErr;
    unsigned long       refCon;
    CMFlattenUPP        myFlattenUPP;
    Boolean             preferredCMMNotFound;
    Boolean             tempCreated;
    FSSpec              tempSpec;
    CMProfileRef        tempProf;
    CMProfileLocation   tempProfLoc;
    
    // Init for error handling.
    theErr = noErr;
    tempCreated = false;
    tempProf = nil;
    
    // Create a universal procedure pointer for the
    // unflatten procedure shown in Listing 3-11.
    myFlattenUPP = NewCMFlattenProc(MyUnflattenProc);
    
    // Pass the pict as the refcon.
    refCon = (unsigned long) pict;
    
    // Set the global index variable to the index of the profile we're looking for.
    gIndex = index;
    
    // The next call invokes the MyUnflattenProc shown in Listing 3-11.
    //On return, tempSpec identifies the newly created profile on disk.
    theErr = CMUnflattenProfile(&tempSpec, myFlattenUPP,(void*)&refCon,
                            &preferredCMMNotFound);
    DisposeRoutineDescriptor(myFlattenUPP);// Dispose of the procedure pointer.
    require(theErr == noErr, cleanup);
    tempCreated = true;
    
    // Open the newly created profile, create a temporary profile reference for it,
    // copy the temporary reference, then close it and delete the profile file.
    tempProfLoc.locType = cmFileBasedProfile;
    tempProfLoc.u.fileLoc.spec = tempSpec;
    
    theErr = CMOpenProfile(&tempProf, &tempProfLoc);
    require(theErr == noErr, cleanup);
    
    theErr = CMCopyProfile(prof, profLoc, tempProf);
    require(theErr == noErr, cleanup);
    
// Do any necessary cleanup:close profile and delete file spec.
cleanup:
    
    if (tempProf)
        theErr = CMCloseProfile(tempProf);
    
    if (tempCreated)
        theErr = FSpDelete(&tempSpec);
    
    return theErr;
}

Part B: Unflattening the Profile

Prior to ColorSync 2.5, your transfer function is called by the CMM that handles the unflatten operation. Starting with ColorSync 2.5, however, the ColorSync Manager calls your transfer function directly, without going through the preferred, or any, CMM.

When the code in MyGetIndexedProfileFromPicHandle ( Listing 3-10 ) calls the CMUnflattenProc function, passing it a pointer to the MyUnflattenProc function, the MyUnflattenProc function ( Listing 3-11 ) is called by ColorSync or by the CMM (depending on the version of ColorSync) to perform the low-level profile data transfer from the document file.

When the MyUnflattenProc function is called 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. For a general discussion of customizing QuickDraw's bottleneck routines, see "Customizing QuickDraw's Text Handling" in Inside Macintosh: Text .

When the MyUnflattenProc function is called 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 3-12 .

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

Listing 3-11 The unflatten procedure

pascal OSErr MyUnflattenProc (long command,
                                long *sizePtr,
                                void *dataPtr,
                                void *refConPtr)
{
    OSErr                   theErr = noErr;
    static CQDProcs         procs;
    static GWorldPtr        offscreen;
    PicHandle               pict;
    switch (command)
    {
        case cmOpenReadSpool:
            theErr = NewSmallGWorld(&offscreen);
            if (theErr)
                return theErr;

            /* Replace the QuickDraw bottleneck routines, mostly with routines
                that do nothing, but also with our unflatten comments routine,
                so that we can intercept the comments we are interested in and
                ignore everything else. */
            SetStdCProcs(&procs);
            procs.textProc = NewQDTextProc (MyNoOpTextProc);
            procs.lineProc = NewQDLineProc (MyNoOpLineProc);
            procs.rectProc = NewQDRectProc (MyNoOpRectProc);
            procs.rRectProc = NewQDRRectPro (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);
            theErr = MyDrawPicHandleUsingBottlenecks (pict, procs, offscreen);
            /* This invokes MyUnflattenProfilesCommentProc shown in Listing 3-12. */
            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;
            }
            /* Dispose of our offscreen world and the routine descriptors
                for our bottlenect routines. */
            DisposeGWorld(offscreen);
            DisposeRoutineDescriptor(procs.MyNoOpTextPrc);
            DisposeRoutineDescriptor(procs.MyNoOpLinePrc);
            DisposeRoutineDescriptor(procs.MyNoOpRectProc);
            DisposeRoutineDescriptor(procs.MyNoOpRRectPrc);
            DisposeRoutineDescriptor(procs.MyNoOpOvalProc);
            DisposeRoutineDescriptor(procs.MyNoOpArcProc);
            DisposeRoutineDescriptor(procs.MyNoOpPolyPrc);
            DisposeRoutineDescriptor(procs.MyNoOpRgnProc);
            DisposeRoutineDescriptor(procs.MyNoOpBitsProc);
            DisposeRoutineDescriptor(procs.MyUnflattenProfilesCommentPrc);
            DisposeRoutineDescriptor(procs.MyNoOpTxMeasPrc);
            break;
        default:
            break;
    }
    return theErr;
}

Part C: Calling the Comment Procedure

When the MyUnflattenProc function's MyDrawPicHandleUsingBottlenecks function calls the MyUnflattenProfilesCommentProc function, the function shown in Listing 3-12 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 3-12 The comment procedure

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

    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;
        theErr = 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;
                theErr = 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;
                theErr = HandToHand(&gChunkBaseHndl);
                gChunkSize = dataSize;
                gChunkOffset = 4;
                break;

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

© 1988-1999 Apple Computer, Inc. — (Last Updated 20 Jan 99)