Documentation Archive Developer
Search

ADC Home > Reference Library > Technical Q&As > Legacy Documents > Graphics & Imaging >

Legacy Documentclose button

Important: This document is part of the Legacy section of the ADC Reference Library. This information should not be used for new development.

Current information on this Reference Library topic can be found here:

Embedding a GX Picture into a PICT


Q: Is there a QuickTime codec for converting QuickDraw GX pictures to QuickDraw PICT format? If so, can you provide this?

A: At a WWDC '95 session, a new technique for exporting GX pictures as QuickDraw PICT files was demonstrated. The method makes use of QuickTime and a new codec (COmpressor-DECompressor) which is included in the GX extension version 1.1 and greater. By using this codec, one can embed a flattened GX picture into a PICT file (or a QuickTime movie). We recommend that you use this method if you want to allow your GX application to exchange pictures with existing QuickDraw applications.

One important feature of this codec is that does not convert QuickDraw GX pictures to QuickDraw PICT in the traditional sense of the word "convert." What this codec allows is the embedding of GX objects inside a PICT file format. The advantage of this is that it allows GX pictures to be viewed (but not edited) in any application that can open a PICT file. Although "embedding" is very useful, it is quite different from "conversion."

Strictly speaking, it is not possible "convert" QuickDraw GX pictures to QuickDraw PICTs without loss of information because GX has much greater functionality than traditional QuickDraw. There is no way to represent complex transfer modes, perspective, advances typography, etc. using the QuickDraw imaging model. By using this codec, you do not lose any of these features.

If you looking for information on how to use the codec, here is sample code. This file (which was accidentally omitted from the last SDK) called "DecompressShape.c" contains all the code you need to embed a GX shape into a PICT using the new codec. The important routine in this file is:

   PicHandle DecompressShape (gxShape theShape, PicHandle proxie,
                  Boolean forPrintingOnly, Boolean eraseBackground)

A proxie can also be embedded so that the PICT can be viewed on machines without GX. On the September 95 GX SDK, this source file will be added to the GX Libraries folder (it may be renamed however to give abetter indication of its function). Note that the "DecompressShape.c" technique is quite different from that used in the older "PicturesAndPICTLibrary.c" which embeds a GX shape into a PICT using picComments. We recommend you use DecompressShape because picComments have several weaknesses including:

  1. they are limited to 32K
  2. many applications strip out any picComments they don't recognize.
  3. DrawPicture() ignores all picComments

By using the codec to embed the GX picture, all these problems are avoided.

/*
    File:       DecompressShape.h
    Contains:   graphics libraries - shape decompression
    Written by: Mike Reed
    Copyright:  (c) 1995 by Apple Computer, Inc., all rights reserved.
    Writers:
        (jtd)   John Daggett
    Change History (most recent first):
         <1>   9/14/95    jtd     First checked in.
*/
#pragma once
#ifndef decompressShapeIncludes
    #define decompressShapeIncludes
    #include <QuickDraw.h>
    #include <GXTypes.h>
    #ifdef __cplusplus
    extern "C" {
    #endif

    Handle CreateQDGXStream(gxShape source, PicHandle proxie,
         Boolean forPrintingOnly, Boolean eraseBackground);
    PicHandle DecompressShape(gxShape theShape, PicHandle proxie,
         Boolean forPrintingOnly, Boolean eraseBackground);
    PicHandle ShapeToPICT(gxShape source);
    void ShapeToScrap(gxShape source, Boolean addProxie,
         Boolean forPrintingOnly, Boolean eraseBackground);
    void DragAndDropShape(EventRecord* event, gxShape shape);
    #ifdef __cplusplus
    };
    #endif
#endif

/*
    File:       DecompressShape.c
    Contains:   graphics libraries - shape decompression
    Written by: Mike Reed
    Copyright:  (c) 1995 by Apple Computer, Inc., all rights reserved.
    Writers:
        (jtd)   John Daggett
    Change History (most recent first):
         <2>   9/14/95    jtd     replaced boolean with Boolean
         <1>   9/14/95    jtd     First checked in.
*/
#include <Drag.h>
#include <Gestalt.h>
#include <ImageCompression.h>
#include <Memory.h>
#include <Scrap.h>
#include <GXTypes.h>
#include <GXMath.h>
#include <GXGraphics.h>
#include <GXEnvironment.h>
#include "StorageLibrary.h"
#include "DecompressShape.h"
#define LONGALIGN(n)        (((n) + 3) & ~3L)
#define kAtomHeaderSize     (sizeof(Size) + sizeof(OSType))
static void RectangleToRect(const gxRectangle* gxr, Rect* qdr)
{
    qdr->left = FixedRound(gxr->left);
    qdr->top = FixedRound(gxr->top);
    qdr->right = FixedRound(gxr->right);
    qdr->bottom = FixedRound(gxr->bottom);
}
static long* AppendAtom(long stream[], Size size, OSType tag, const void* data)
{
#ifdef debugging
    if (size & 3)
        DebugStr("\patom size needs to be long aligned");
#endif
    *stream++   = size + kAtomHeaderSize;
    *stream++   = tag;
    BlockMove(data, (Ptr)stream, size);
    return (long*)((char*)stream + size);
}
/*
 *  See the comment on DecompressShape for an explanation of the parameters.
 *  This routine is used by both DecompressShape for embedding shapes in PICTs,
 *  and AddQDGXRecorderFrame for making gx movies.
*/
Handle CreateQDGXStream(gxShape source, PicHandle proxie,
    Boolean forPrintingOnly, Boolean eraseBackground)
{
    #define         gxForPrintingOnlyAtom   'fpto'
    #define         gxEraseBackgroundAtom   'erbg'
    long atomCount, shapeSize, proxieSize;
    long dataSize, fontListSize, eraseSize;
    Handle dataHdl, shapeHdl;
    gxFlatFontList* fontList;
    gxTag fontListTag;
#ifdef debugging
    GXIgnoreGraphicsWarning(tags_of_type_flst_removed);
#endif
    shapeHdl = ShapeToHandleWithFlags(source, gxFontListFlatten
             | gxFontGlyphsFlatten | gxFontVariationsFlatten);
#ifdef debugging
    GXPopGraphicsWarning();
#endif
    if (shapeHdl == nil)
        return nil;
    if (proxie)
    {   atomCount = 2;
        proxieSize = LONGALIGN(GetHandleSize((Handle)proxie));
    }
    else
    {   atomCount = 1;
        proxieSize = 0;
    }
    shapeSize = LONGALIGN(GetHandleSize(shapeHdl));
    if (forPrintingOnly)
        ++atomCount;
    if (eraseBackground)
        ++atomCount;
    fontListSize = 0;
    fontList = nil;
    GXIgnoreGraphicsWarning(count_out_of_range);
    if (GXGetShapeTags(source, gxFlatFontListItemTag, 1,
            1, &fontListTag) > 0)
    {   fontListSize = GXGetTag(fontListTag, nil, nil);
        if (fontListSize > 0)
        {   fontList = (gxFlatFontList*)NewPtr(fontListSize);
            if (fontList != nil)
            {   GXGetTag(fontListTag, nil, fontList);
                fontListSize = LONGALIGN(fontListSize);
                ++atomCount;
            }
            else
                fontListSize = 0;
        }
    }
    GXPopGraphicsWarning();     // count_out_of_range
    dataSize = atomCount * kAtomHeaderSize + shapeSize
               + proxieSize + fontListSize + sizeof(long);
    dataHdl = NewHandle(dataSize);
    if (dataHdl == nil)
    {   DisposHandle(shapeHdl);
        if (fontList)
            DisposPtr((Ptr)fontList);
        return nil;
    }

    {   long* p = (long*)*dataHdl;
        if (forPrintingOnly)
            p = AppendAtom(p, 0, gxForPrintingOnlyAtom, nil);
        if (eraseBackground)
            p = AppendAtom(p, 0, gxEraseBackgroundAtom, nil);
        if (proxie)
            p = AppendAtom(p, proxieSize, 'PICT', *proxie);
        if (fontList)
            p = AppendAtom(p, fontListSize,
                            gxFlatFontListItemTag, fontList);
        p = AppendAtom(p, shapeSize, 'qdgx', *shapeHdl);
        *p++ = 0;       // end of the atom-list
        DisposHandle(shapeHdl);
        if (fontList)
            DisposPtr((Ptr)fontList);
    }
    return dataHdl;
}
static void GetRidOfAnyQDShapeTags(gxShape shape)
{
    gxShapeType shapeType = GXGetShapeType(shape);
    if (shapeType == gxPictureType)
    {   long        index, count;
        gxShape*    subShapes;

        count = GXGetPicture(shape, nil, nil, nil, nil);
        if (count > 0)
        {   subShapes = (gxShape*)NewPtr(count * sizeof(gxShape));
            if (subShapes != nil)
            {   GXGetPicture(shape, subShapes, nil, nil, nil);
                for (index = 0; index < count; index++)
                    GetRidOfAnyQDShapeTags(subShapes[index]);
                DisposPtr((Ptr)subShapes);
            }
        }
    }
    else if (shapeType == gxRectangleType &&
             GXGetShapeTags(shape, gxQuickDrawPictTag,
             1, gxSelectToEnd, nil) > 0)
        GXSetShapeType(shape, gxPictureType);
}
/*
 *  This guy returns a Quickdraw picture containing an embedded shape,
 and a proxie of the shape, if proxie is not nil. This is called by
 ShapeToScrap and DragAndDropShape.
 *
 *  theShape            * the shape you want to embedd in a PICT
 *  proxie          * a PICT to be drawn if theShape cannot be
           drawn (optional but recommended)
 *  forPrintingOnly     * if TRUE, then the decompressor will
           always look for the proxie and theShape will only be used
           when printing. Use this setting if theShape might be too
           large or too slow when drawn from other apps. If FALSE,
           then the decompressor will draw theShape unless it
           gets an error, in which case it will look for a proxie.
 *  eraseBackground * if TRUE, the decompressor will always erase
           the background to WHITE before drawing the shape. This
           is slower, but needed if the shape does not fill its
           bounding rectangle. if FALSE, the decompressor will
           just draw the shape. Use this setting if the shape
           entirely fills its bounding rectangle.
The shape [and proxie] is embedded by constructing a stream of
atoms. Each atom begins with a size (long) and a type (OSType)
and then the data for that type. After the last atom, there
is a trailing zero (long) to mark the end of the stream. For
embedded shapes, the type is 'qdgx', and for the proxie the
type is 'PICT'. Note that the size fields are rounded up to
a multiple of 4. Finally, to alert QuickTime that the data
is in this parsable form with a possible PICT proxie, we add
a 'prxy' extension to the ImageDescriptionHandle.
Picture of this form will draw the embedded shape when an
application calls DrawPicture if GX is around, and if not,
the proxie will be drawn. When printed, the shape or the
proxie will be printed. This is meant to replace the
PicComment described in GX 1.0 for embedding shapes in
pictures.
If you want to include a flatFontList tag, be sure that
theShape is a picture, otherwise GX will not return the
tag after GXFlattenShape. The flatFontList tag makes
certain printing conditions more efficient (i.e. font
downloading to postscript printers).
Your shape must not contain a gxQuickDrawPictTag, meaning
it contains embedded QD data, becuase this will potentially
crash when it tries to print. To fix that, DecompressShape
looks for occurrances of the tag, and converts them to real
gx data by calling GXSetShapeType(shape, gxPictureType).
*/
PicHandle DecompressShape(gxShape theShape, PicHandle proxie,
                     Boolean forPrintingOnly, Boolean eraseBackground)
{
    #define             kQuickTimeGestalt       'qtim'
    PicHandle               thePicture;
    ImageDescriptionHandle  descHdl;
    ImageDescriptionPtr     descPtr;
    Handle              dataHdl;
    long                    version;
    if (Gestalt(kQuickTimeGestalt, &version) != noErr)
        return nil;
    GetRidOfAnyQDShapeTags(theShape);
    /*
     *  Move the shape's topLeft to 0,0 so that it draws neatly inside the picture frame.
     *  Note that the qdgx movie library does not move the shape, since the shape may not
     *  take up the whole frame.
    */
    {   gxRectangle bounds;
        GXGetShapeLocalBounds(theShape, &bounds);
        if (bounds.left || bounds.top)
            GXMoveShape(theShape, -bounds.left, -bounds.top);
        dataHdl = CreateQDGXStream(theShape, proxie, forPrintingOnly, eraseBackground);
        if (bounds.left || bounds.top)
            GXMoveShape(theShape, bounds.left, bounds.top);
    }
    if (dataHdl == nil)
        return nil;
    descHdl = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    if (descHdl)
    {   Rect            shortBounds;
        gxRectangle bounds;
        GXGetShapeLocalBounds(theShape, &bounds);
        RectangleToRect(&bounds, &shortBounds);
        OffsetRect(&shortBounds, -shortBounds.left,
            -shortBounds.top);  // set the topLeft of the src to 0,0
        thePicture = OpenPicture(&shortBounds);
        descPtr = *descHdl;
        descPtr->idSize = sizeof(ImageDescription);
        descPtr->cType = 'qdgx';
        descPtr->vendor = 'appl';
        descPtr->temporalQuality = codecLosslessQuality;
        descPtr->width = shortBounds.right;
        descPtr->height = shortBounds.bottom;
        descPtr->hRes = descPtr->vRes = ff(72);
        descPtr->dataSize = GetHandleSize(dataHdl);
        descPtr->frameCount = 1;
        descPtr->depth = 32;
        descPtr->clutID = -1;
        //  If there is a PICT proxie, add an image extension to
        //  tell QuickTime, in case GX is not around.
        if (proxie)
        {   Handle prxyVersionHdl = NewHandle(sizeof(long));
            if (prxyVersionHdl != nil)
            {   *(long*)*prxyVersionHdl = 0;        // version number for 'prxy' extension
                SetImageDescriptionExtension(descHdl, prxyVersionHdl, 'prxy');
            }
        }
        HLock(dataHdl);
        DecompressImage(*dataHdl, descHdl,
            ((CGrafPtr)qd.thePort)->portPixMap, &shortBounds,
            &shortBounds, srcCopy, nil);
        DisposeHandle((Handle)descHdl);
        ClosePicture();
    }
    else
        thePicture = nil;
    DisposHandle(dataHdl);
    return thePicture;
}
/*
 *  This guy returns a Quickdraw picture containing a 1-bit bitmap of the shape.
 *  This is used by ShapeToScrap to create a proxie when calling DecompressShape.
 *  If you want to make the proxie prettier (and larger), change the bitmap to 8-bit.
 *  However, if you're using this in conjunction with DecompressShape to place a
 *  gxShape on the clipboard, 1-bit should be enough, since the actual shape will be
 *  drawn, rather than the proxie (unless forPrintingOnly is true).
*/
PicHandle ShapeToPICT(gxShape source)
{
    gxRectangle bounds;
    gxShape     bitShape;
    gxBitmap        bitmap;
    PicHandle       thePicture;
    Rect            shortBounds;
    /*
     *  GetShapeLocalBounds doesn't accurately report the bounds of a gxQuickDrawPictTag.
     *  One option is to convert the tags to real GX pictures.
    */
    GetRidOfAnyQDShapeTags(source);
    GXGetShapeLocalBounds(source, &bounds);
    RectangleToRect(&bounds, &shortBounds);
    OffsetRect(&shortBounds, -shortBounds.left, -shortBounds.top);
    bitmap.width        = shortBounds.right;
    bitmap.height       = shortBounds.bottom;
    bitmap.rowBytes = bitmap.width + 31 >> 5 << 2;
    bitmap.pixelSize    = 1;
    bitmap.space        = gxIndexedSpace;
    bitmap.set          = nil;
    bitmap.profile      = nil;
    bitmap.image        = NewPtrClear(bitmap.rowBytes * bitmap.height);
    if (bitmap.image == nil)
        return nil;
    bitShape = GXNewBitmap(&bitmap, nil);
    if (bitShape != nil)
    {   gxViewGroup group   = GXNewViewGroup();
        gxViewDevice device = GXNewViewDevice(group, bitShape);
        gxViewPort port = GXNewViewPort(group);
        gxTransform trans   = GXCloneTransform(GXGetShapeTransform(source));
        GXSetShapeAttributes(source, GXGetShapeAttributes(source) | gxMapTransformShape);
        GXMoveShape(source, -bounds.left, -bounds.top);
        GXSetViewPortDither(port, 4);
        GXSetShapeViewPorts(source, 1, &port);
        GXDrawShape(source);
        GXSetShapeTransform(source, trans);
        GXDisposeTransform(trans);

        GXDisposeViewGroup(group);  /* this disposes the gxViewPort and gxViewDevice */
        GXDisposeShape(bitShape);
    }
    {   GrafPtr thePort;
        BitMap  srcBits;

        GetPort(&thePort);
        srcBits.baseAddr = bitmap.image;
        srcBits.rowBytes = bitmap.rowBytes;
        srcBits.bounds = shortBounds;
        thePicture = OpenPicture(&shortBounds);
        CopyBits(&srcBits, &thePort->portBits,
                &shortBounds, &shortBounds, srcOr, nil);
        ClosePicture();
    }

    DisposPtr((Ptr)bitmap.image);

    return thePicture;
}
/*
 *  This guy puts a Quickdraw picture on the clipboard containing an embedded shape
 *  and, if addProxie is true, a 1-bit bitmap of the shape. Call this in response to
 *  the user choosing "Copy" or "Cut" from the Edit menu. See comment for DecompressShape
 *  to explain forPrintingOnly.
*/
void ShapeToScrap(gxShape source, Boolean addProxie,
    Boolean forPrintingOnly, Boolean eraseBackground)
{
    PicHandle   picture, proxie;
    proxie = addProxie ? ShapeToPICT(source) : nil;
    picture = DecompressShape(source, proxie, forPrintingOnly, eraseBackground);
    if (proxie)
        KillPicture(proxie);
    if (picture)
    {   HLock((Handle)picture);
        ZeroScrap();
        PutScrap(GetHandleSize((Handle)picture), 'PICT', (Ptr)*picture);
        KillPicture(picture);
    }
}
/*
 *  The ItemReference is the gxShape to be sent. The dragSendRefCon is ignored.
*/
static pascal OSErr LibrarySendDataProc(FlavorType theType, void *dragSendRefCon,
                                ItemReference theItem, DragReference theDrag)
{
    OSErr   result = noErr;
    gxShape shape = (gxShape)theItem;
    switch (theType) {
    case 'qdgx':
    {   Handle flat = ShapeToHandle(shape);
        if (flat)
        {   HLock(flat);
            result = SetDragItemFlavorData(theDrag, theItem,
                'qdgx', *flat, GetHandleSize(flat), 0);
            DisposHandle(flat);
        }
        break;
    }
    case 'PICT':
    {   PicHandle proxie = ShapeToPICT(shape);
        PicHandle pict = DecompressShape(shape, proxie, false, true);
        if (proxie)
            KillPicture(proxie);
        if (pict)
        {   HLock((Handle)pict);
            result = SetDragItemFlavorData(theDrag, theItem,
                'PICT', (Ptr)*pict, GetHandleSize((Handle)pict), 0);
            KillPicture(pict);
        }
        break;
    }
    default:
        result = badDragFlavorErr;

[Sep 15 1995]