JPEG.c

/*************************************************************************************
#
#       JPEG.c
#
#       This segment handles JPEG.
#
#       Author(s):  Michael Marinkovich & Guillermo Ortiz
#                   marink@apple.com
#
#       Modification History: 
#
#           4/3/96      MWM     Initial coding                   
#
#       Copyright © 1992-96 Apple Computer, Inc., All Rights Reserved
#
#
#       You may incorporate this sample code into your applications without
#       restriction, though the sample code has been provided "AS IS" and the
#       responsibility for its operation is 100% yours.  However, what you are
#       not permitted to do is to redistribute the source as "DSC Sample Code"
#       after having made changes. If you're going to re-distribute the source,
#       we require that you make it clear in the source that the code was
#       descended from Apple Sample Code, but that you've made changes.
#
*************************************************************************************/
 
#include <Events.h>
#include <ToolUtils.h>
#include <Gestalt.h>
#include <OSUtils.h>
#include <Palettes.h>
 
#include "App.h"
#include "Proto.h"
 
 
// data unload buffer size
#define kBufferSize         codecMinimumDataSize
 
 
//----------------------------------------------------------------------
//
//  ReadJPEG - open a JPEG file with supplied FSSpec.
//                  
//
//----------------------------------------------------------------------
 
OSErr ReadJPEG(FSSpec spec, CGrafPtr *newWorld)
{
    OSErr                       err = noErr;
    ImageDescriptionHandle      desc;
    GWorldPtr                   theWorld = nil;
    GWorldPtr                   oldWorld;
    GDHandle                    oldGD;
    PixMapHandle                thePix;
    ICMDataProcRecord           loadProc;
    Handle                      image;
    Rect                        bounds;
    DLDataRec                   dataRec;
    long                        fileLength;
    long                        dataSize;
    short                       refNum;
    Ptr                         stripImage;
    
    GetGWorld(&oldWorld, &oldGD);
    
    if (newWorld != nil)
    {
        SetCursor(*GetCursor(watchCursor));         // set the cursor to a watch while busy
            
        err = FSpOpenDF(&spec, fsRdWrShPerm, &refNum);
        if ( err == noErr ) 
        {
            desc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
            if (desc != nil)
            {
                HLock((Handle)desc);
                err = ReadJPEGHeader(refNum, desc, &bounds);
                if (err == noErr)
                {
                    image = NewHandleClear(kBufferSize);
                    err = MemError();
                    if (image != nil && err == noErr)
                    {
                        err = NewJPEGWorld(&theWorld, (*desc)->depth, bounds);
                        if (theWorld != nil && err == noErr) 
                        {
                            err = SetFPos(refNum, fsFromStart , 0);
                            if (err == noErr)
                            {
                                thePix = GetGWorldPixMap(theWorld);
                                LockPixels(thePix);
                                SetGWorld(theWorld, nil);
                                
                                HLock(image);
                                
                                dataSize = kBufferSize;
                                
                                // make sure the filesize is greater than the buffersize
                                (void)GetEOF(refNum, &fileLength);
                                if (kBufferSize > fileLength)
                                    dataSize = fileLength;
                                    
                                err = FSRead(refNum, &dataSize, *image);
                                if (err == noErr)
                                {
                                    fileLength = (*desc)->dataSize - kBufferSize;
                                    if (fileLength < 0) 
                                        fileLength = 0;
                                        
                                    dataRec.refNum = refNum;
                                    dataRec.fileLength = fileLength;
                                    dataRec.origPtr = *image;
                                    dataRec.endPtr = *image + kBufferSize;
                                    loadProc.dataProc = NewICMDataProc(DataLoadingProc);
                                    loadProc.dataRefCon = (long)&dataRec;
                                    
                                    stripImage = StripAddress(*image);
                                    
                                    err = FDecompressImage(stripImage, desc, thePix, &bounds, nil, srcCopy, nil, nil, nil,
                                                           codecHighQuality, anyCodec, kBufferSize, &loadProc, nil);
                                                          
                                    DisposeRoutineDescriptor(loadProc.dataProc);
                                    
                                    HUnlock(image);
                                    
                                    UnlockPixels(thePix);    
                                    SetGWorld(oldWorld, oldGD);
                                    
                                    if (err == noErr)
                                        *newWorld = theWorld;
                                }
                                
                            }
                            if (err != noErr)
                                DisposeGWorld(theWorld);
                                
                        }
                        DisposeHandle(image);
 
                    }
                    
                }
                HUnlock((Handle)desc);              
                DisposeHandle((Handle)desc);
                
            }
            FSClose(refNum);                    // close the file
    
        }   
        SetCursor(&qd.arrow );              // set cursor back to arrow
    }
    else
        err = paramErr;
        
            
    return err;
 
}
 
 
//----------------------------------------------------------------------
//
//  ReadJPEGHeader - fill out the ImageDescription with needed info.
//
//
//----------------------------------------------------------------------
 
OSErr ReadJPEGHeader(short refNum, ImageDescriptionHandle desc, Rect *bounds)
{
    OSErr                   err = noErr;
    long                    imageSize;
    short                   w = 0, h = 0;
    short                   skip;
    UInt8                   marker;
    Boolean                 isJFIF = false;
    Boolean                 readingExtension = false;
    
    // set file position to beginning of file
    err = SetFPos(refNum, fsFromStart , 0);
    if (err != noErr)
        return err;
        
    // get file length so we don't overflow
    err = GetEOF(refNum, &imageSize);
    if (err != noErr)
        return err;
    
    (*desc)->dataSize = imageSize;
 
    while (true)        // loop forever
    {
        marker = FindNextMarker(refNum);
        
        switch (marker)
        {
            case kSOIMarker:
                isJFIF = true;
                break;
                
            case kAPPOMarker + 0:
            case kAPPOMarker + 1:
            case kAPPOMarker + 2:
            case kAPPOMarker + 3:
            case kAPPOMarker + 4:
            case kAPPOMarker + 5:
            case kAPPOMarker + 6:
            case kAPPOMarker + 7:
            case kAPPOMarker + 8:
            case kAPPOMarker + 9:
            case kAPPOMarker + 10:
            case kAPPOMarker + 11:
            case kAPPOMarker + 12:
            case kAPPOMarker + 13:
            case kAPPOMarker + 14:
            case kAPPOMarker + 15:
                err = HandleAPPOMarker(marker, refNum, desc, &readingExtension);
                if (err != noErr)
                    return err;
                break;
                
            case kCommentMarker:
                SkipLength(refNum);
                break;
        
            case kSOFMarker + 0:        // start of frame header marker
            case kSOFMarker + 1:
            case kSOFMarker + 2:
            case kSOFMarker + 3:
            case kSOFMarker + 5:
            case kSOFMarker + 6:
            case kSOFMarker + 7:
            case kSOFMarker + 9:
            case kSOFMarker + 10:
            case kSOFMarker + 11:
            case kSOFMarker + 13:
            case kSOFMarker + 14:
            case kSOFMarker + 15:
                err = HandleSOFMarker(refNum, desc, readingExtension);
                if (err != noErr)
                    return err;
                    
                if (!readingExtension)
                {
                    SetRect(bounds, 0, 0, (*desc)->width, (*desc)->height);
 
                    return noErr;
                }
                break;
                
            case kDACMarker:
                skip = ReadWord(refNum) - 2;
                skip *= ReadWord(refNum);
                err = SetFPos(refNum, fsFromMark, skip);
                break;
                
            case kSOSMarker:
                HandleSOSMarker(refNum);
                break;
                
            case kDHTMarker:
            case kDQTMarker:
            case kRSTOMarker:
                SkipLength(refNum);
                break;
                
            case kEOIMarker:        // we reached the end of image
                // we are reading an extension so keep going
                if (readingExtension == true)
                    readingExtension = false;
                
                break;
        }
                
    }
    
    return err;
    
}
 
 
//----------------------------------------------------------------------
//
//  FindNextMarker  -
//
//
//----------------------------------------------------------------------
 
UInt8 FindNextMarker(long refNum)
{
     UInt8          marker;
    
    marker = ReadByte(refNum);
    
    while (marker == kStartMarker)
        marker = ReadByte(refNum);
 
    while (marker == 0x00)
    {
        marker = ReadByte(refNum);
 
        while (marker != kStartMarker)
            marker = ReadByte(refNum);
            
        marker = ReadByte(refNum);
    }
        
    return marker;
    
}
 
 
//----------------------------------------------------------------------
//
//  HandleAPPOMarker  -
//
//
//----------------------------------------------------------------------
 
OSErr HandleAPPOMarker(UInt8 marker, long refNum, ImageDescriptionHandle desc, Boolean *readingExtension)
{
    OSErr           err = noErr;
    Fixed           xRes,yRes;
    long            length;
    short           w = 0, h = 0;
    short           units;
    short           version;
    UInt8           extension;
    UInt8           JIFF[5];
 
 
    // read skip bytes - header length - skip count
    length = ReadWord(refNum) - 2;
    
    if (marker == kAPPOMarker && length >= 14)
    {
        JIFF[0] = ReadByte(refNum);
        JIFF[1] = ReadByte(refNum);
        JIFF[2] = ReadByte(refNum);
        JIFF[3] = ReadByte(refNum);
        JIFF[4] = ReadByte(refNum);
        
        // check if we really have the JFIF header
        if ( JIFF[0] == 'J' && JIFF[1] == 'F'  && JIFF[2] == 'I'  && JIFF[3] == 'F' ) 
        {
            version = ReadWord(refNum);
 
            if ( version < 0x100 )
            {   
                err = paramErr;
                return err;     // don't know this
            }
            else
                (*desc)->version = version;
                
            units = ReadByte(refNum);
            xRes = ReadWord(refNum);
            yRes = ReadWord(refNum);
 
            switch ( units ) 
            {
                case 0:         // no res, just aspect ratio
                    xRes = FixMul(72L << 16, xRes << 16);
                    yRes = FixMul(72L << 16, yRes << 16);
                    break;
                    
                case 1:         // dots per inch
                    xRes = xRes << 16;
                    yRes = yRes << 16;
                    break;
                    
                case 2:         // dots per centimeter (we convert to dpi )
                    xRes = FixMul(0x28a3d, xRes << 16);
                    yRes = FixMul(0x28a3d, xRes << 16);
                    break;  
                    
                default:
                    break;
            }
            
            (*desc)->hRes = xRes;
            (*desc)->vRes = yRes;
 
            length -= 12;
            err = SetFPos(refNum, fsFromMark, length);
            
        }
        else
        {
            if ( JIFF[0] == 'J' && JIFF[1] == 'F'  && JIFF[2] == 'X'  && JIFF[3] == 'X' ) 
            {
                *readingExtension = true;       // next markers are extensions (ignore)
 
                extension = ReadByte(refNum);
                
                switch (extension)
                {
                    case 0x10:
                    case 0x11:
                    case 0x13:
                        break;
                    
                    default:
                        err = paramErr;
                        return err;
                }
                    
            }
            
        }
            
    }
    else
        err = SetFPos(refNum, fsFromMark, length);
 
    return err;
    
}
 
 
//----------------------------------------------------------------------
//
//  HandleSOFMarker  -
//
//
//----------------------------------------------------------------------
 
OSErr HandleSOFMarker(long refNum, ImageDescriptionHandle desc, Boolean readingExtension)
{
    OSErr           err = noErr;
    short           w = 0;
    short           h = 0;
    short           components;
    short           length;
 
    if (readingExtension == false)
    {
        length = ReadWord(refNum);
        ReadByte(refNum);
        h = ReadWord(refNum);
        w = ReadWord(refNum);
 
        if (w && h)             // make sure we do have something to display
        {
            /* now make up the image description */
            (*desc)->idSize         = sizeof(ImageDescription);
            (*desc)->cType          = 'jpeg';
            (*desc)->dataRefIndex   = 0;
            (*desc)->revisionLevel  = 0;
            (*desc)->vendor         = 0;
            (*desc)->temporalQuality = 0;
            (*desc)->spatialQuality = codecNormalQuality;
            (*desc)->width          = w;
            (*desc)->height         = h;
            (*desc)->frameCount     = 1;
            BlockMove("\pPhoto - JPEG",(*desc)->name,13);
            (*desc)->clutID         = -1;
            
            components = ReadByte(refNum);
            
            switch (components) 
            {
                case 1:     
                    (*desc)->depth = 40;
                    break;
 
                case 3:
                    (*desc)->depth = 32;
                    break;
 
                case 4:
                    (*desc)->depth = 32;
                    break;
                                        
                default:
                    err = paramErr;
                    return err;
                    break;
            }
            
            err = SetFPos(refNum, fsFromMark, length - 8);
            return noErr;
            
        }
        
    }
    else
    {
        length = ReadWord(refNum) - 2;
        err = SetFPos(refNum, fsFromMark, length);
        if (err != noErr)
            return err;
    }
    
    return err;
}
 
//----------------------------------------------------------------------
//
//  HandleSOSMarker  -
//
//
//----------------------------------------------------------------------
 
void HandleSOSMarker(long refNum)
{
    short           components;
    short           c;
    
    ReadWord(refNum);
    components = ReadByte(refNum);
    
    for (c = 0; c < components; c++)
        ReadWord(refNum);
    
}
 
 
//----------------------------------------------------------------------
//
//  ReadByte -
//
//
//----------------------------------------------------------------------
 
UInt8 ReadByte(short refNum)
{
    UInt8       data;
    long        bytesNeeded = sizeof(char);
 
    (void)FSRead(refNum, &bytesNeeded, &data);
    
    return data;
    
}
 
 
//----------------------------------------------------------------------
//
//  ReadWord -
//
//
//----------------------------------------------------------------------
 
UInt16 ReadWord(short refNum)
{
    UInt16      data;
    long        bytesNeeded = sizeof(UInt16);
 
    (void)FSRead(refNum, &bytesNeeded, &data);
    
    return data;
    
}
 
 
//----------------------------------------------------------------------
//
//  SkipLength  -
//
//
//----------------------------------------------------------------------
 
void SkipLength(long refNum)
{
    UInt16      skip;
    
    skip = (ReadWord(refNum) - 2);
    SetFPos(refNum, fsFromMark, skip);
    
}
 
 
//----------------------------------------------------------------------
//
//  NewJPEGWorld -
//
//
//----------------------------------------------------------------------
 
OSErr NewJPEGWorld(GWorldPtr *theWorld, short depth, Rect theRect)
{
    OSErr           err = noErr;
    GWorldPtr       oldPort;
    PixMapHandle    thePix;
    CTabHandle      cTab = nil;
    GDHandle        oldGD;
    
    if (theWorld != nil)
    {
        GetGWorld(&oldPort,&oldGD);
    
        // if depth is greater than 32 then the 
        // image is grayscale
        if (depth > 32) 
        {
            cTab = GetCTable(depth);
            depth = depth - 32;
        }
        
        err = NewGWorld(theWorld, depth, &theRect, cTab, nil, 0L);  // try our heap
        
        if (err != noErr)
            err = NewGWorld(theWorld, depth, &theRect, cTab, nil, useTempMem);  // else try temp
    
        if (err == noErr && theWorld != nil) 
        {
            thePix = GetGWorldPixMap(*theWorld);
                    
            if (LockPixels(thePix)) 
            {
                SetGWorld(*theWorld, nil);
                EraseRect(&theRect);
                
                UnlockPixels(thePix);
            }
        }
        
        SetGWorld(oldPort, oldGD);  
    }
    else
        err = paramErr;
        
    return err;
    
}
 
 
//----------------------------------------------------------------------
//
//  DoSaveJPEG -
//
//
//----------------------------------------------------------------------
 
OSErr SaveJPEG(WindowRef window)
{
    OSErr                       err = noErr;
    StandardFileReply           reply;
    ImageDescriptionHandle      desc;
    Handle                      data;
    Rect                        srcRect;
    GWorldPtr                   theWorld;
    PixMapHandle                thePix;
    CTabHandle                  cTab = nil;
    ICMFlushProcRecord          flushProc;
    short                       refNum;
    short                       depth;
    Str255                      title;
    DocHnd                      doc;
    
 
    GetWTitle(window, title);
    StandardPutFile("\pSave document as:", title, &reply) ;
    
    doc = (DocHnd)GetWRefCon(window);
    
    if (reply.sfGood && doc != nil)
    {
        desc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
 
        theWorld = (**doc).world;
        
        if (theWorld != nil && desc != nil) 
        {
            srcRect = theWorld->portRect;
            thePix = GetGWorldPixMap(theWorld);
            
            if (LockPixels(thePix)) 
            {   
                // if less than 16-bit then get the color table
                // of our GWorld
                depth = (**thePix).pixelSize;
                if (depth < 16)
                    cTab = (**thePix).pmTable;
                
                data = NewHandle(kBufferSize);
                err = MemError();
                
                if (data != nil && err == noErr) 
                {
                    HLock(data);
                                                                
                    if (reply.sfReplacing ) 
                        err = FSpDelete(&reply.sfFile);
                
                    err = FSpCreate(&reply.sfFile, 'JVWR', 'JPEG', reply.sfScript);
                    
                    if (err == noErr)
                        err = FSpOpenDF(&reply.sfFile,fsRdWrPerm, &refNum);
                        
                    if (err == noErr)
                        err = SetFPos(refNum, fsFromStart , 0);
                        
                    if (err == noErr)
                    {
                        flushProc.flushProc = NewICMFlushProc(DataUnloadProc);
                        flushProc.flushRefCon = refNum;
 
                        err = FCompressImage(thePix, &srcRect, depth,
                                             codecNormalQuality, 'jpeg',
                                             bestCompressionCodec, cTab,
                                             codecFlagWasCompressed, kBufferSize,
                                             &flushProc, nil, desc, *data);
                    }
                        
                    if (err == noErr)
                        err = SetFPos(refNum, fsFromStart, (**desc).dataSize);
 
                    if (err == noErr)
                        err = SetEOF(refNum, (**desc).dataSize);
                                 
                    if (err == noErr)       
                        err = FSClose( refNum );
                    
                    HUnlock(data);
                    DisposeHandle(data);
                    
                    DisposeRoutineDescriptor(flushProc.flushProc);
                }
            
            }
            
            UnlockPixels(thePix);
        
        }
        
        if (nil != desc)
            DisposeHandle((Handle)desc);
        
    }
    else
        err = memFullErr;
    
    return err;
    
}           
    
    
 
//----------------------------------------------------------------------
//
//  DataUnloadProc -
//
//
//----------------------------------------------------------------------
 
pascal OSErr DataUnloadProc(Ptr data, long bytesNeeded, long refCon)
{
    OSErr       err = noErr;
    
    if (data == nil)
    {
        // if data is nil QuickTime requests a new position in the 
        // file from the current mark, offset by bytesNeeded.
        err = SetFPos(refCon, fsFromMark, bytesNeeded);
    }
    else
    {   
        err = FSWrite(refCon, &bytesNeeded, data);
    }
    
    return err;
    
}
 
 
//----------------------------------------------------------------------
//
//  DataLoadingProc -
//
//
//----------------------------------------------------------------------
 
pascal OSErr DataLoadingProc(Ptr *data, long bytesNeeded, long refCon)
{
    OSErr           err = noErr;
    long            unusedDataLen;
    long            newDataLen;
    DLDataPtr       dataRec;
    Ptr             newDataPtr;
    
    dataRec = (DLDataPtr)refCon;
    
    if (data == nil)
    {
        err = SetFPos(dataRec->refNum, fsFromMark, bytesNeeded);
    }
    else
    {   
        newDataPtr = *data;
        
        // if QT requests more data than is in the buffer 
        // we will have to load more.
        if ((newDataPtr + bytesNeeded) >= dataRec->endPtr) 
        {
            // move whats left up to the front of the buffer
            unusedDataLen = dataRec->endPtr - newDataPtr;
            BlockMove(newDataPtr,dataRec->origPtr, unusedDataLen);
            
            // now fill the buffer with new data...after the 
            // data we moved to the front of the buffer of course.
            newDataLen = kBufferSize - unusedDataLen;
            
            if (newDataLen > dataRec->fileLength)
                newDataLen = dataRec->fileLength;
                
            newDataPtr = dataRec->origPtr + unusedDataLen;
            
            err = FSRead(dataRec->refNum, &newDataLen, newDataPtr);
            
            dataRec->fileLength -= newDataLen;
 
            *data = dataRec->origPtr;
        }
    }
    
 
    return err;
    
}