Using Image Compressor Components

This chapter shows you how to use compressors and decompressors in conjunction with the Image Compression Manager.

Performing Image Compression

This section describes what the Image Compression Manager does that affects compressors. It then provides sample code that shows how the compressor components prepare for image compression and how to compress an entire image or a horizontal band of an image.

When compressing an image, the Image Compression Manager performs three major tasks:

Choosing a Compressor

Listing 9-1 shows how the Image Compression Manager calls the ImageCodecPreCompress function before an image is compressed. The compressor component returns information about how it is able to compress the image to the Image Compression Manager, so that it can fit the destination data to the requirements of the compressor component. This information includes compressor capabilities for

  • depth of input pixels

  • minimum buffer band size

  • band increment size

  • extension width and height

When your compressor component is called with the ImageCodecPreCompress function, it can handle all aspects of the function itself, or only the most common ones. All image compressor components must handle at least one case.

Listing 9-1  Preparing for simple compression operations

pascal long ImageCodecPreCompress (Handle storage,
                                    register CodecCompressParams *p) 
{
    CodecCapabilities *capabilities = p->capabilities;
/*
    First the compressor returns which depth input pixels it
    supports based on what the application has available. This
    compressor can only work with 32-bit input pixels.
*/  
    switch ( (*p->imageDescription)->depth ) {
        case 16:
            capabilities->wantedPixelSize = 32;
            break;
        default:
            return(codecConditionErr);
            break;
    }
 
    /*
        If the buffer gets banded, return the smallest one the
        compressor can handle.
    */  
    capabilities->bandMin = 2;
 
    /*
        If the buffer gets banded, return the increment
        by which it should increase.
    */
    capabilities->bandInc = 2;
        
    capabilities->extendWidth = (*p->imageDescription)->width & 1;
    capabilities->extendHeight = (*p->imageDescription)->height & 1;
    /*
        For efficiency, if the compressor could perform extension,
        these flags would be set to 0.
    */
    return(noErr);
}

Here is a list of some of the operations your compressor component can perform during compression. It describes parameters in the compression parameters structure and indicates the operations that are required and which flags in the compressor capabilities flags field of the compressor capabilities structure must be set to allow your compressor to handle them (see Data Structures and Data Structures).

  • Depth conversion. If your compressor component can compress from the pixel depth indicated by the pixelSize field (in the pixel map structure pointed to by the srcPixmap field of the compression parameters structure), it should set the wantedPixelSize field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can support in the wantedPixelSize field. The Image Compression Manager will convert the source image to that depth.

  • Extension. If the format for the compressed data is block oriented, the compressor component can request that the Image Compression Manager allocate a buffer that is a multiple of the proper block size by setting the extendWidth and extendHeight parameters of the compressor capability structure. The new pixels are replicated from the left and bottom edges to fill the extended area. If your compressor can perform this extension itself, it should leave the extendWidth and extendHeight fields set to 0. In this case, the Image Compression Manager can avoid copying the source image to attain more efficient operation.

  • Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift the source pixels so that they are at an aligned address. If the pixelSize field of the source pixel map structure is less than 8, and your compressor component handles that depth directly, and the left address of the image (srcRect.left - srcPixMap.bounds.left) is not aligned and your compressor component can handle these pixels directly, then it should set the codecCanShift flag in the flags field of the compressor capabilities structure. If your compressor component does not set this flag, then the data will be copied to a buffer with the image shifted so the first pixel is in the most significant bit of an aligned long-word address.

  • Updating previous pixel maps. Compressors that perform temporal compression may keep their own copy of the previous frame’s pixel map, or they may update the previous frame’s pixel map as they perform the compression. In these cases, the compressor component should set the codecCanCopyPrev flag if it updates the previous pixel map with the original data from the current frame, or it should set the codecCanCopyPrevComp flag if it updates the previous pixel map with a compressed copy of the current frame.

Compressing a Horizontal Band of an Image

Listing 9-2 shows how the Image Compression Manager calls the ImageCodecBandCompress function when it wants the compressor to compress a horizontal band of an image.

Listing 9-2  Performing simple compression on a horizontal band of an image

pascal long ImageCodecBandCompress (Handle storage,
                                  register CodecCompressParams *p) 
{
    short                   width,height;
    Ptr                     cDataPtr,dataStart;
    short                   depth;
    Rect                    sRect;
    long                    offsetH,offsetV;
    Globals                 **glob = (Globals **)storage;
    register char           *baseAddr;
    long                    numLines,numStrips;
    short                   rowBytes;
    long                    stripBytes;
    char                    mmuMode = 1;
    register short          y;
    ImageDescription        **desc = p->imageDescription;
    OSErr                   result = noErr;
    
    /*
    If there is a progress function, give it an open call at
        the start of this band.
    */
 
    if (p->progressProcRecord.progressProc)
        p->progressProcRecord.progressProc (codecProgressOpen, 0,
            p->progressProcRecord.progressRefCon);
    width = (*desc)->width;
    height = (*desc)->height;
    depth = (*desc)->depth;
    dataStart = cDataPtr = p->data;
    /*
        Figure out offset to first pixel in baseAddr from the
        pixel size and bounds.
     */
    rowBytes = p->srcPixMap.rowBytes;
    sRect = p->srcPixMap.bounds;
 
    numLines = p->stopLine - p->startLine; /* number of scan lines */
    numStrips = (numLines+1)>>1;           /* number of strips in */
    stripBytes = ((width+1)>>1) * 5;
    
    /*
        Adjust the source baseAddress to be at the beginning
        of the desired rect.
    */
    switch ( p->srcPixMap.pixelSize ) {
    case 32:
        offsetH = sRect.left<<2;
        break;
    case 16:
        offsetH = sRect.left<<1;
        break;
    case 8:
        offsetH = sRect.left;
        break;
    /*
        This compressor does not handle the other cases directly.
    */
    default:
        result = codecErr;
        goto bail;
    }
    offsetV = sRect.top * rowBytes;
    baseAddr = p->srcPixMap.baseAddr + offsetH + offsetV;
    /*
        If there is not a data-unloading function,
        adjust the pointer to the next band.
    */
    
    if ( p->flushProcRecord.flushProc == nil ) {
        cDataPtr += (p->startLine>>1) * stripBytes;
    }
    else { /*
                 Make sure the compressor can deal with the
                 data-unloading function in this case.
            */
        if ( p->bufferSize < stripBytes ) {
            result = codecSpoolErr;
            goto bail;
        }
    }
    /*
        Perform the slower data-loading or progress operation, as
        required.
    */
    
    if ( p->flushProcRecord.flushProc ||
        p->progressProcRecord.progressProc ) {
        SharedGlobals *sg = (*glob)->sharedGlob;
        for ( y=0; y < numStrips; y++) {
            SwapMMUMode(&mmuMode);
            CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg);
            SwapMMUMode(&mmuMode);
            baseAddr += rowBytes<<1;
            if ( p->flushProcRecord.flushProc ) {
                if ( (result=
            p->flushProcRecord.flushProc(cDataPtr,stripBytes,
            p->flushProcRecord.flushRefCon)) != noErr) {
                    result = codecSpoolErr;
                    goto bail;
                }
            } else {
                cDataPtr += stripBytes;
            }
            if (p->progressProcRecord.progressProc) {
                if ( (result=
                    p->progressProcRecord.progressProc)
                        codecProgressUpdatePercent,
                        FixDiv(y,numStrips),
                        p->progressProcRecord.progressRefCon)
                )   != noErr ) {
                    result = codecAbortErr;
                    goto bail;
                }
            }
        }
    } else {
        SharedGlobals *sg = (*glob)->sharedGlob;
        short tRowBytes = rowBytes<<1;
        SwapMMUMode(&mmuMode);
        for ( y=numStrips; y--; ) {
            CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg);
            cDataPtr += stripBytes;
            baseAddr += tRowBytes;
        }
        SwapMMUMode(&mmuMode);
    }
}

Decompressing an Image

When decompressing an image, the Image Compression Manager performs these three major tasks:

Choosing a Decompressor

Listing 9-3 provides an example of how a decompressor is chosen. The Image Compression Manager calls the ImageCodecPreDecompress function before an image is decompressed. The decompressor returns information about how it can decompress an image. The Image Compression Manager can fit the destination pixel map to your decompressor’s requirements if it is not able to support decompression to the destination directly. The capability information the decompressor returns includes

  • depth of pixels for the destination pixel map

  • minimum band size handled

  • extension width and height required

  • band increment size

When your decompressor component is called with the ImageCodecPreDecompress function, it can handle all aspects of the call itself, or only the most common ones. All decompressors must handle at least one case.

Listing 9-3  Preparing for simple decompression

pascal long ImageCodecPreDecompress( Handle storage,
                                 register CodecDecompressParams *p)
{
    register CodecCapabilities*capabilities = p->capabilities;
    RectdRect = p->srcRect;
    
    /*  
        Check if the matrix is OK for this decompressor.
        This decompressor doesn't do anything fancy.
    */
    
    if ( !TransformRect(p->matrix,&dRect,nil) )
        return(codecConditionErr);
    /*  
        Decide which depth compressed data this decompressor can
        deal with.
    */
    
    switch ( (*p->imageDescription)->depth ) {
        case 16:
            break;
        default:
            return(codecConditionErr);
            break;
    }
        /*
            This decompressor can deal only with 32-bit pixels.
        */
    capabilities->wantedPixelSize = 32;
    
    /*
        The smallest possible band the decompressor can handle is
        2 scan lines.
    */
    
    capabilities->bandMin = 2;
    /* This decompressor can deal with 2 scan line high bands. */
    capabilities->bandInc = 2;
    
    /*
        If this decompressor needed its pixels be aligned on
        some integer multiple, you would set extendWidth and
        extendHeight to the number of pixels by which you need the
        destination extended. If you don't have such requirements
        or if you take care of them yourself, you set extendWidth
        and extendHeight to 0.
    */
    capabilities->extendWidth = p->srcRect.right & 1;
    capabilities->extendHeight = p->srcRect.bottom & 1;
    
    return(noErr);
}

Decompressor Operations

This section contains a bulleted list of some of the operations your decompressor component can perform during the decompression operation. The list describes which parameters in the decompression parameters structure indicate that the operations are required and which flags in the flags field of the compressor capabilities structure must be set to allow your decompressor to handle them (see Data Structures).

For sequences of images the conditionFlags field in the decompression parameters structure can be used to determine which parameters may have changed since the last decompression operation. These parameters are also indicated in the bulleted list.

Since your decompressor’s capabilities depend on the full combination of parameters, it must inspect all the relevant parameters before indicating that it will perform one of the operations itself. For instance, if your decompressor has hardware that can perform scaling only if the destination pixel depth is 32 and there is no clipping, then the pre-decompression operation would have to check the following fields in the decompression parameters structure: the matrix field, the pixelSize field of the destination pixel map structure pointed to by the destPixMap field, and the maskBits fields. Only then could the decompressor decide whether to set the codecCanScale flag in the capabilities field of the decompression parameters structure.

  • Scaling. The decompressor component can look at the matrix and selectively decide which scaling operations it wishes to handle. If the scaling factor specified by the matrix is not unity and your decompressor can perform the scaling operation, it must set the codecCanScale flag in the capabilities field. If it does not, then the decompressor is asked to decompress without scaling, and the Image Compression Manager performs the scaling operation afterward.

  • Depth conversion. If your component can decompress to the pixel depth indicated by the pixelSize field (of the pixel map structure pointed to by the dstPixmap field of the decompression parameters structure), it should set the wantedPixelSize field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can handle in the wantedPixelSize field.

  • Dithering. When determining whether depth conversion can be performed (for converting an image to a lower bit depth, or to a similar bit depth with a different color table), dithering may be required. This is specified by the dither bit in the transferMode field (0x40) of the decompression parameters structure being set. The accuracy field of the decompression parameters structure indicates whether fast dithering is acceptable (accuracy less than or equal to codecNormalQuality) or whether true error diffusion dithering should be used (accuracy greater than codecNormalQuality). Most decompressors do not perform true error diffusion dithering, although they can. When a decompressor cannot perform the dither operation, it should specify the higher bit depth in the wantedPixelSize field of the compressor capability structure and let the Image Compression Manager perform the depth conversion with dithering. Dithering to 16-bit destinations is normally done only if the accuracy field is set to the codecNormalQuality value. However, if your decompressor component can perform dithering fast enough, it could be performed at the lower accuracy settings as well. To indicate that your decompressor can perform dithering as specified, it should set the codecCanTransferMode flag in the capabilities field of the decompression parameters structure.

  • Color remapping. If the compressed data has an associated color lookup table that is different from the color lookup table of the destination pixel map, then the decompressor can remap the color indices to the closest available ones in the destination itself, or it can let the Image Compression Manager do the remapping. If the decompressor can do the mapping itself, it should set the codecCanRemap flag in the capabilities flags field of the decompression parameters structure.

  • Extending. If the format for the compressed data is block-oriented, the decompressor can ask that the Image Compression Manager to allocate a buffer which is a multiple of the proper block size by setting the extendWidth and extendHeight fields of the compressor capabilities structure. If the right and bottom edges of the destination image (as determined by the transformed srcRect and dstPixMap.bounds fields of the decompression parameters structure) are not a multiple of the block size that your decompressor handles, and your decompressor cannot handle partial blocks (writing only the pixels that are needed for blocks that cross the left or bottom edge of the destination), then your decompressor component must set the extendWidth and extendHeight fields in the compressor capabilities structure. In this case, the Image Compression Manager creates a buffer large enough so that no partial blocks are needed. Your component can decompress into that buffer. This is then copied to the destination by the Image Compression Manager. Your component can avoid this extra step if it can handle partial blocks. In this case, it should leave the extendWidth and extendHeight fields set to 0.

  • Clipping. If clipping must be performed on the image to be decompressed, the maskBits field of the decompression parameters structure is nonzero. In the ImageCodecPreDecompress function, it will be a region handle to the actual clipping region. If your decompressor can handle the clipping operation as specified by this region, it should set the codecCanMask bit in the capabilities flags field of the decompression parameters structure. If it does this, then the parameter passed to the ImageCodecBandDecompress function in the maskBits field will be a bitmap instead of a region. If desired, your decompressor can save a copy of the actual region structure during the pre-decompression operation.

  • Matting. If a matte must be applied to the decompressed image, the transferMode field of the decompression parameters structure is set to blend and the mattePixMap field is a handle to the pixel map to be used as the matte. If your decompressor can perform the matte operation, then it should set the codecCanMatte field in the compressor capabilities structure. If it does not, then the Image Compression Manager will perform the matte operation after the decompression is complete.

  • Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift the destination pixels so that they are at an aligned address. If the pixel size of the destination pixel map is less than 8 and your component handles that depth directly, and the left address of the image is not aligned and your component can handle these pixels directly, then it should set the codecCanShift flag in the capabilities field of the decompression parameters structure. If your component does not set this flag, the Image Compression Manager allocates a buffer for and performs the shifting after the decompression is completed.

  • Partial extraction. If the source rectangle is not the entire image and the component can decompress only the part of the image specified by the source rectangle, it should set the codecCanSrcExtract flag in the capabilities field of the decompression parameters structure. If it does not, the Image Compression Manger asks the component to decompress the entire image and copy only the required part to the destination.

Decompressing a Horizontal Band of an Image

Listing 9-4 shows how to decompress the horizontal band of an image. The Image Compression Manager calls the ImageCodecBandDecompress function when it wants a decompressor to decompress an image or a horizontal band of an image. The pixel data indicated by the baseAddr field is guaranteed to conform to the criteria your decompressor specified in the ImageCodecPreDecompress function.

Listing 9-4  Performing a decompression operation

pascal long ImageCodecBandDecompress( Handle storage,
                               register CodecDecompressParams *p) 
{
    Rect                dRect;
    long                offsetH,offsetV;
    Globals             **glob = (Globals **)storage;
    long                numLines,numStrips;
    short               rowBytes;
    long                stripBytes;
    short               width;
    register short      y;
    register char*      baseAddr;
    char                *cDataPtr;
    char                mmuMode = 1;
    OSErr               result = noErr;
 
    /*
        Calculate the real base address based on the boundary
        rectangle. If it's not a linear transformation, this
        decompressor does not perform the operation.
    */
    dRect = p->srcRect;
    if ( !TransformRect(p->matrix,&dRect,nil) )
        return(paramErr);
    /*  If there is a progress function, give it an open call at
        the start of this band.
    */
    if (p->progressProcRecord.progressProc)
        p->progressProcRecord.progressProc(codecProgressOpen,0,
            p->progressProcRecord.progressRefCon);
    
    /*
        Initialize some local variables.
    */
    
    width = (*p->imageDescription)->width;
    rowBytes = p->dstPixMap.rowBytes;
    numLines = p->stopLine - p->startLine; /* number of scan lines
                                              in this band */
    numStrips = (numLines+1)>>1;           /* number of strips in
                                              this band */ 
    stripBytes = ((width+1)>>1) * 5;       /* number of bytes in
                                              1 strip of blocks */  
    cDataPtr = p->data;
    
    /*
        Adjust the destination base address to be at the beginning
        of the desired rectangle.
    */
    
    offsetH = (dRect.left - p->dstPixMap.bounds.left);
    switch ( p->dstPixMap.pixelSize ) {
        case 32:
            offsetH <<=2;       /* 1 pixel = 4 bytes */
            break;
        case 16:
            offsetH <<=1;       /* 1 pixel = 2 bytes */
            break;
        case 8:                          
            break;              /* 1 pixel = 1 byte */
        default:
            result = codecErr;  /* This decompressor doesn't handle
                                 these cases, although it could. */ 
        goto bail;
    }
    offsetV = (dRect.top - p->dstPixMap.bounds.top) * rowBytes;
    baseAddr = p->dstPixMap.baseAddr + offsetH + offsetV;
 
    /*
        If your decompressor component is skipping some data,
        it just skips it here. You can tell because
        firstBandInFrame indicates this is the first band for a new
        frame, and if startLine is not 0, then that many lines were
        clipped out.
     */
    if ( (p->conditionFlags & codecConditionFirstBand) &&
            p->startLine != 0 ) {
        if ( p->dataProcRecord.dataProc ) {
            for ( y=0; y < p->startLine>>1; y++ ) {
                if ( (result=p->dataProcRecord.dataProc
                         (&cDataPtr,stripBytes,
                        p->dataProcRecord.dataRefCon)) != noErr ) {
                    result = codecSpoolErr;
                    goto bail;
                }
                cDataPtr += stripBytes;
            }
        } else
            cDataPtr += (p->startLine>>1) * stripBytes;
    }
    /*
        If there is a data-loading function spooling the data to your
        decompressor, then you have to decompress the data in the
        chunk size that is specified, or, if there is a progress
        function, you must make sure to call it as you go along.
    */
 
    if ( p->dataProcRecord.dataProc ||
         p->progressProcRecord.progressProc ) {
        SharedGlobals *sg = (*glob)->sharedGlob;
    
        for (y=0; y < numStrips; y++) {
            if (p->dataProcRecord.dataProc) {
                if ( (result=p->dataProcRecord.dataProc
                         (&cDataPtr,stripBytes,
                        p->dataProcRecord.dataRefCon)) != noErr ) {
                    result = codecSpoolErr;
                    goto bail;
                }
            }
            SwapMMUMode(&mmuMode);
            DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg);
            SwapMMUMode(&mmuMode);
            baseAddr += rowBytes<<1;
            cDataPtr += stripBytes;
 
            if (p->progressProcRecord.progressProc) {
                if ( (result=p->progressProcRecord.progressProc
                        (codecProgressUpdatePercent,
                    FixDiv(y, numStrips),
                    p->progressProcRecord.progressRefCon)) != noErr ) {
                    result = codecAbortErr;
                     goto bail;
                }
            }
        }
    
/*
    Otherwise, do the fast case.
*/
    } else {
        SharedGlobals *sg = (*glob)->sharedGlob;
        shorttRowBytes = rowBytes<<1;
 
        SwapMMUMode(&mmuMode);
        for ( y=numStrips; y--; ) {
            DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg);
            baseAddr += tRowBytes;
            cDataPtr += stripBytes;
        }
        SwapMMUMode(&mmuMode);
    }
/*
    IMPORTANT: Update the pointer to data in the decompression
    parameters structure, so that when your decompressor gets the
    next band, you'll be at the right place in your data.
*/
    p->data = cDataPtr;
    
    if ( p->conditionFlags & codecConditionLastBand ) {
        /*
            Tie up any loose ends on the last band of the frame.
        */
    }
bail:
    /*
        If there is a progress function, give it a close call
        at the end of this band.
    */
    if (p->progressProcRecord.progressProc)
        p->progressProcRecord.progressProc(codecProgressClose,0,
            p->progressProcRecord.progressRefCon);
    return(result);
}

Asynchronous Decompression

The Image Compression Manager (ICM) supports scheduled asynchronous decompression operations. By calling the Image Compression Manager function DecompressSequenceFrameWhen, applications can schedule decompression requests in advance. This allows decompressor components that support this functionality to provide reliable playback performance under a wider range of conditions.

The Apple Cinepak, Video, Animation, Component Video, and Graphics decompressors provided in QuickTime support scheduled asynchronous decompression to 8-, 16-, and 32-bit destinations (the Cinepak decompressor also supports 4-bit grayscale destinations). QuickTime also adds asynchronous decompression support to the JPEG and None decompressor components on PowerPC systems (with the QuickTime PowerPlug extension installed).

If you want to support this functionality, you must modify your decompressor component in the following ways:

Hardware Cursors

The Image Compression Manager supports hardware cursors introduced in PCI-based Macintosh computers, which eliminates cursor flicker. For all software codecs, this support requires no changes.

For codecs that manage the cursor themselves, QuickTime has a flag, codecCompletionDontUnshield, for use when calling the ICMDecompressComplete function. Use this flag to prevent the Image Compression Manager from unshielding the cursor when ICMDecompressComplete is called.

Timecode Support

QuickTime provides timecode information to decompressor components when movies are played. To support timecodes, your codec must support the function ImageCodecSetTimeCode, which allows the Image Compression Manager to set the timecode value for the next frame to be decompressed.

Working With Video Fields

The functionality of the ImageFieldSequenceExtractCombine function is performed by individual image codecs. This is because the way in which fields are stored is different for every compression format. A codec component function, ImageCodecExtractAndCombineFields, is defined for this purpose. Apple encourages developers of codecs to incorporate this function, if their compressed data format is capable of separately storing both fields of a video frame.

Accelerated Video Support

QuickTime supports codecs that accelerate certain image decompression operations. These features are most likely used by developers of video hardware boards that provide special acceleration features, such as arbitrary scaling or color space conversion.

If a codec cannot decompress directly to the screen it has the option of specifying that it can decompress to one or more types of non-RGB pixel spaces, specified as an OSType (e.g., 'yuvs'). The ICM then attempts to find a decompressor component of that type (a transfer codec) that can transfer the image to the screen. Since the ICM does not define non-RGB pixel types, the transfer codec must support additional calls to set up the offscreen. If a transfer codec cannot be found that supports the specified non-RGB pixel types, the ICM uses the None codec with an RGB offscreen buffer.

The real speed benefit comes from the fact that since the transfer codec defines the offscreen buffer, it can place the buffer in on-board memory, or even point to an overlay plane so that the offscreen image really is on the screen. In this case, the additional step of transferring the bits from offscreen memory on to the screen is avoided.

For an image decompressor component to indicate that it can decompress to non-RGB pixel types, it should, in the ImageCodecPreDecompress call, fill in the wantedDestinationPixelTypes field with a handle to a zero-terminated list of pixel types that it can decompress to. The ICM immediately makes a copy of the handle. Cinepak, for example, returns a 12-byte handle containing yuvs, yuvu, and $00000000. Since ImageCodecPreDecompress can be called often, it is suggested that codecs allocate this handle when their component is opened and simply fill in the wantedDestinationPixelTypes field with this handle during ImageCodecPreDecompress. Components that use this method should be sure to dispose the handle at close.

Apple’s Cinepak decompressor supports decompressing to 'yuvs' and 'yuvu' pixel types. Type 'yuvs' is a YUV format with u and v components signed (center point at $00), while 'yuvu' has the u and v component centered at $80.

As an example, suppose XYZ Co. had a video board that had a YUV overlay plane capable of doing arbitrary scaling. The overlay plane takes data in the same format as Cinepak’s 'yuvs' format. In this case, XYZ would make a component of type 'imdc' and subtype 'yuvs'.

The ImageCodecPreDecompress call would set the codecCanScale, codecHasVolatileBuffer, and codecImageBufferIsOnScreen bits in the capabilities flags field. The codecImageBufferIsOnScreen bit is necessary to inform the ICM that the codec is a direct screen transfer codec. A direct screen transfer codec is one that sets up an offscreen buffer that is actually onscreen (such as an overlay plane). Not setting this bit correctly can cause unpredictable results.

The real work of the codec takes place in the ImageCodecNewImageBufferMemory call. This is where the codec is instructed to prepare the non-RGB pixel buffer. The codec must fill in the baseAddr and rowBytes fields of the dstPixMap structure in CodecDecompressParams. The ICM then passes these values to the original codec (e.g., Cinepak) to decompress into.

The codec must also implement ImageCodecDisposeMemory to balance ImageCodecNewImageBufferMemory.

Since Cinepak then decompresses into the card’s overlay plane, ImageCodecBandDecompress needs to do nothing aside from calling ICMDecompressComplete.

pascal ComponentResult
ImageCodecPreDecompress(Handle storage,
        CodecDecompressParams *p)
{
    CodecCapabilities   *capabilities = p->capabilities;
    // only allow 16 bpp source
    if ((**p->imageDescription).depth != 16)
        return codecConditionErr;
    /* we only support 16 bits per pixel dest */
    if (p->dstPixMap.pixelSize != 16)
        return codecConditionErr;
 
    capabilities->wantedPixelSize = p->dstPixMap.pixelSize;
 
    capabilities->bandInc = capabilities->bandMin =
                (*p->imageDescription)->height;
 
    capabilities->extendWidth = 0;
    capabilities->extendHeight = 0;
 
    capabilities->flags =
            codecCanScale | codecImageBufferIsOnScreen |
            codecHasVolatileBuffer;
 
    return noErr;
}
 
pascal ComponentResult
 
ImageCodecBandDecompress(Handle storage,
        CodecDecompressParams *p)
{
    ICMDecompressComplete(p->sequenceID, noErr,
                codecCompletionSource | codecCompletionDest,
                &p->completionProcRecord);
 
    return noErr;
}
 
pascal ComponentResult
ImageCodecNewImageBufferMemory(Handle storage,
        CodecDecompressParams *p, long flags,
        ICMMemoryDisposedUPP memoryGoneProc,
        void *refCon)
{
    OSErr err = noErr;
    long offsetH, offsetV;
    Ptr baseAddr;
    long rowBytes;
 
    // call predecompress to check to make sure we can handle
    // this destination
    err = ImageCodecPreDecompress(storage, p);
    if (err) goto bail;
 
    // set video board registers with the scale
    XYZVideoSetScale(p->matrix);
 
    // calculate a base address to write to
    offsetH = (p->dstRect.left - p->dstPixMap.bounds.left);
    offsetV = (p->dstRect.top - p->dstPixMap.bounds.top);
    XYZVideoGetBaseAddress(p->dstPixMap, offsetH, offsetV,
                &baseAddr, &rowBytes);
 
    p->dstPixMap.baseAddr = baseAddr;
    p->dstPixMap.rowBytes = rowBytes;
    p->capabilities->flags = codecImageBufferIsOnScreen;
bail:
    return err;
}
 
pascal ComponentResult
ImageCodecDisposeMemory(Handle storage, Ptr data)
{
    return noErr;
}

Some video hardware boards that use an overlay plane require that the image area on screen be flooded with a particular RGB value or alpha-channel in order to have the overlay buffer “show through” at that location. Codecs that require this support should set the screenFloodMethod and screenFloodValue fields of the CodecDecompressParams record during ImageCodecPreDecompress. The ICM then manages the flooding of the screen buffer. This method is more reliable than having the codec attempt to flood the screen itself, and will ensure compatibility with future versions of QuickTime.

Packetization Information

QuickTime functions support packetizing compressed data streams, primarily for video conferencing applications. For this purpose, the field preferredPacketSizeInBytes was added to the compression parameters structure. Codec developers need only use this field.

Packet information is appended, word-aligned, to the end of video data. It is a variable-length array of 4-byte integers, each representing the offset in bits of the end of a packet, followed by another integer containing the number of packet hints, and finally a four-byte identifier indicating the type of appended data:

[boundary #1][boundary #2]...[boundary #N][N]['pkts']

Packets are given in bits, because some types of compressed image data (such as H.261) are cut up on bit-boundaries rather than byte-boundaries.

// given:  image data, length, and a packet number
// returns: a pointer to the start of the packet and a packet size, plus
// information about leading and trailing bits
 
char* GetNextPacket(char* data, int len, int packet, long* packet_size,
    char* leading_bits, char* trailing_bits)
{
    long *lp, packets;
    lp = (long*) data;  // 'data' must be word-aligned
    lp += len/4 - 1;
    if (*lp != 'pkts')
        return nil;
    
    packets = *lp[ -1 ];          // negative indexing is good for you
    if (packet >= packets)
        return nil;             // out of bounds
    lp -= packets;      // now 0-indexing into the packet array will work
    if (packet == 0)
    {
        *packet_size = (lp[0] + 7)/8;   // count the bits
        *leading_bits = 0;
        *trailing_bits = lp[0] % 8;
        return data;                    // in case of 0-length packet 
    }
    else
    {
        *packet_size = ( lp[pktnum] - lp[pktnum-1] + 7) / 8;
        *leading_bits = lp[packet-1] % 8 ? 8 - lp[packet-1] % 8 : 0;
        *trailing_bits = lp[packet] % 8;
        return data + lp[packet-1] / 8;
    }
}

Note that this technique can be used for further extensions by the addition of further appended formats. The last two words are always the number of words and an extension identifier.

DV Image Compressor Component

The DV image compressor component makes it possible to compress QuickTime video data into DV format. It is invoked automatically by the Image Compression Manager when an application requests output of type kDVCNTSCCodecType for NTSC DV data or kDVCPALCodecType for PAL DV data.

When creating NTSC video, the DV image compressor component generates 720 X 480 frames. When creating PAL video, it generates 720 X 576 frames.

DV Image Decompressor Component

The DV image decompressor component makes it possible to decompress DV video data. It is invoked automatically by the Image Compression Manager when an application specifies input of type kDVCNTSCCodecType for NTSC DV data or kDVCPALCodecType for PAL DV data.

There are two quality modes for DV decompression:

When a computer includes a video display adapter that performs YUV decompression in hardware, the DV image decompressor can use a YUV decompressor component written to use the hardware decompression capabilities in place of the software YUV decompressor in QuickTime, resulting in even higher performance.

Specifying the Size of an Image Buffer

You can specify the size of the image buffer used by your image compressor or decompressor component. When your component calls the ImageCodecPreDecompress or ImageCodecPreCompress function, you can specify the size of the buffer as follows:

This is illustrated in Listing 9-5.

Listing 9-5  Specifying the size of an image buffer for a codec

p->capabilities->flags |= codecWantsSpecialScaling;
p->requestedBufferWidth = 720;
p->requestedBufferHeight = 480;