Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
source/QTReadWriteJPEG.c
////////// |
// |
// File: QTReadWriteJPEG.c |
// |
// Contains: Sample code for compressing and decompressing JPEG images. |
// |
// Copyright: © 1996-1998 by Apple Computer, Inc., all rights reserved. |
// |
// Change History (most recent first): |
// |
// <4> 02/03/99 rtm reworked prompt and filename handling to remove "\p" sequences |
// <3> 04/27/98 rtm revised to uncouple this file from its Macintosh framework, |
// to make the coding style conform to other code samples, and |
// to make it run on Windows |
// <2> 02/??/97 mwm fixed some memory leaks; expanded JPEG parsing |
// <1> 04/03/96 mwm initial coding |
// |
// |
////////// |
/* |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
////////// |
// |
// header files |
// |
////////// |
#include "QTReadWriteJPEG.h" |
#include "QTUtilities.h" |
////////// |
// |
// global variables |
// |
////////// |
GWorldPtr gGWorld = NULL; // the GWorld we load the image data into |
WindowPtr gImageWindow = NULL; // the window we display the image in |
////////// |
// |
// QTUtils_ConvertCToPascalString |
// Convert a C string into a Pascal string. |
// |
// The caller is responsible for disposing of the pointer returned by this function (by calling free). |
// |
////////// |
StringPtr QTUtils_ConvertCToPascalString (char *theString) |
{ |
StringPtr myString = (unsigned char*)(malloc(strlen(theString) + 1)); |
short myIndex = 0; |
while (theString[myIndex] != '\0') { |
myString[myIndex + 1] = theString[myIndex]; |
myIndex++; |
} |
myString[0] = (unsigned char)myIndex; |
return(myString); |
} |
////////// |
// |
// QTJPEG_PromptUserForJPEGFileAndDisplay |
// Let the user select a JPEG image file, then display it in a window. |
// |
////////// |
void QTJPEG_PromptUserForJPEGFileAndDisplay (void) |
{ |
SFTypeList myTypeList; |
StandardFileReply myReply; |
PixMapHandle myPixMap; |
Rect myRect; |
OSErr myErr = noErr; |
// have the user select a JPEG image file |
myTypeList[0] = kQTFileTypeJPEG; |
StandardGetFilePreview(NULL, 1, myTypeList, &myReply); |
if (!myReply.sfGood) |
goto bail; |
// read the file data into an offscreen graphics world |
myErr = QTJPEG_ReadJPEG(myReply.sfFile, &gGWorld); |
if (myErr != noErr) |
goto bail; |
myRect = gGWorld->portRect; |
myPixMap = GetGWorldPixMap(gGWorld); |
if (LockPixels(myPixMap)) { |
// create a window to display the image in; then draw into that window |
MacOffsetRect(&myRect, 50, 50); |
gImageWindow = NewCWindow(NULL, &myRect, myReply.sfFile.name, true, movableDBoxProc, (WindowPtr)-1L, true, 0); |
if (gImageWindow == NULL) |
goto bail; |
MacOffsetRect(&myRect, -50, -50); |
// copy the image from the offscreen port into the window |
CopyBits( (BitMapPtr)(*myPixMap), |
(BitMapPtr)(&(gImageWindow->portBits)), |
&myRect, |
&gImageWindow->portRect, |
srcCopy, |
NULL); |
} |
bail: |
UnlockPixels(myPixMap); |
} |
#if 0 |
OSErr QTJPEG_ReadJPEGPixels (FSSpec theFile, PixMapHandle *thePixels, Rect *theRect) |
{ |
ImageDescriptionHandle myDesc; |
Handle myData; |
PixMapHandle myPixMap; |
ICMDataProcRecord myLoadProc; |
Rect myRect; |
DLDataRec myDataRec; |
long mySize; |
short myRefNum; |
Ptr myDataPtr; |
OSErr myErr = paramErr; |
// save the current graphics port |
GetGWorld(&oldWorld, &oldGD); |
if (theGWorld != NULL) { |
myErr = FSpOpenDF(&theFile, fsRdWrShPerm, &myRefNum); |
if (myErr == noErr) { |
myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription)); |
if (myDesc != NULL) { |
HLock((Handle)myDesc); |
myErr = QTJPEG_ReadJPEGHeader(myRefNum, myDesc, &myRect); |
if (myErr == noErr) { |
myData = NewHandleClear(kBufferSize); |
myErr = MemError(); |
if ((myData != NULL) && (myErr == noErr)) { |
*thePixels = NewHandle(4* (myRect.bottom-myRect.top) * (myRect.right-myRect.left)); |
myErr = MemErr(); |
if ((*thePixels != NULL) && (myErr == noErr)){ |
myErr = SetFPos(myRefNum, fsFromStart, 0); |
if (myErr == noErr) { |
mySize = kBufferSize; |
// make sure the file size is greater than the buffer size |
(void)GetEOF(myRefNum, &mySize); |
if (kBufferSize > mySize) |
mySize = mySize; |
myErr = FSRead(myRefNum, &mySize, *myData); |
if (myErr == noErr) { |
mySize = (*myDesc)->dataSize - kBufferSize; |
if (mySize < 0) |
mySize = 0; |
myDataRec.fRefNum = myRefNum; |
myDataRec.fFileLength = mySize; |
myDataRec.fOrigPtr = *myData; |
myDataRec.fEndPtr = *myData + kBufferSize; |
myLoadProc.dataProc = NewICMDataProc(QTJPEG_DataLoadingProc); |
myLoadProc.dataRefCon = (long)&myDataRec; |
myDataPtr = StripAddress(*myData); |
myErr = FDecompressImage( myDataPtr, |
myDesc, |
myPixMap, |
&myRect, |
NULL, |
srcCopy, |
NULL, |
NULL, |
NULL, |
codecHighQuality, |
anyCodec, |
kBufferSize, |
&myLoadProc, |
NULL); |
DisposeRoutineDescriptor(myLoadProc.dataProc); |
HUnlock(myData); |
UnlockPixels(myPixMap); |
// restore the previous graphics world |
SetGWorld(oldWorld, oldGD); |
if (myErr == noErr) |
*theGWorld = myGWorld; |
} |
} |
if (myErr != noErr) |
DisposeGWorld(myGWorld); |
} |
DisposeHandle(myData); |
} |
} |
HUnlock((Handle)myDesc); |
DisposeHandle((Handle)myDesc); |
} |
FSClose(myRefNum); // close the file |
} |
} |
return(myErr); |
} |
#endif |
////////// |
// |
// QTJPEG_ReadJPEG |
// Open a JPEG file with supplied FSSpec; the graphics world containing the image is returned through theGWorld. |
// |
////////// |
OSErr QTJPEG_ReadJPEG (FSSpec theFile, CGrafPtr *theGWorld) |
{ |
ImageDescriptionHandle myDesc; |
Handle myData; |
GWorldPtr myGWorld = NULL; |
GWorldPtr oldWorld; |
GDHandle oldGD; |
PixMapHandle myPixMap; |
ICMDataProcRecord myLoadProc; |
Rect myRect; |
DLDataRec myDataRec; |
long mySize; |
short myRefNum; |
Ptr myDataPtr; |
OSErr myErr = paramErr; |
// save the current graphics port |
GetGWorld(&oldWorld, &oldGD); |
if (theGWorld != NULL) { |
myErr = FSpOpenDF(&theFile, fsRdWrShPerm, &myRefNum); |
if (myErr == noErr) { |
myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription)); |
if (myDesc != NULL) { |
HLock((Handle)myDesc); |
myErr = QTJPEG_ReadJPEGHeader(myRefNum, myDesc, &myRect); |
if (myErr == noErr) { |
myData = NewHandleClear(kBufferSize); |
myErr = MemError(); |
if ((myData != NULL) && (myErr == noErr)) { |
myErr = QTJPEG_NewJPEGWorld(&myGWorld, (*myDesc)->depth, myRect); |
if ((myGWorld != NULL) && (myErr == noErr)){ |
myErr = SetFPos(myRefNum, fsFromStart, 0); |
if (myErr == noErr) { |
myPixMap = GetGWorldPixMap(myGWorld); |
LockPixels(myPixMap); |
SetGWorld(myGWorld, NULL); |
HLock(myData); |
mySize = kBufferSize; |
// make sure the file size is greater than the buffer size |
(void)GetEOF(myRefNum, &mySize); |
if (kBufferSize < mySize) |
mySize = kBufferSize; |
myErr = FSRead(myRefNum, &mySize, *myData); |
if (myErr == noErr) { |
mySize = (*myDesc)->dataSize - kBufferSize; |
if (mySize < 0) |
mySize = 0; |
myDataRec.fRefNum = myRefNum; |
myDataRec.fFileLength = mySize; |
myDataRec.fOrigPtr = *myData; |
myDataRec.fEndPtr = *myData + kBufferSize; |
myLoadProc.dataProc = NewICMDataProc(QTJPEG_DataLoadingProc); |
myLoadProc.dataRefCon = (long)&myDataRec; |
myDataPtr = StripAddress(*myData); |
myErr = FDecompressImage( myDataPtr, |
myDesc, |
myPixMap, |
&myRect, |
NULL, |
srcCopy, |
NULL, |
NULL, |
NULL, |
codecHighQuality, |
anyCodec, |
kBufferSize, |
&myLoadProc, |
NULL); |
DisposeRoutineDescriptor(myLoadProc.dataProc); |
HUnlock(myData); |
UnlockPixels(myPixMap); |
// restore the previous graphics world |
SetGWorld(oldWorld, oldGD); |
if (myErr == noErr) |
*theGWorld = myGWorld; |
} |
} |
if (myErr != noErr) |
DisposeGWorld(myGWorld); |
} |
DisposeHandle(myData); |
} |
} |
HUnlock((Handle)myDesc); |
DisposeHandle((Handle)myDesc); |
} |
FSClose(myRefNum); // close the file |
} |
} |
return(myErr); |
} |
////////// |
// |
// QTJPEG_ReadJPEGHeader |
// Read the JPEG header and fill out the specified ImageDescription with needed info. |
// |
////////// |
OSErr QTJPEG_ReadJPEGHeader (short theRefNum, ImageDescriptionHandle theDesc, Rect *theRect) |
{ |
long mySize; |
short mySkip; |
UInt8 myMarker; |
Boolean isJFIF = false; |
Boolean readingExtension = false; |
OSErr myErr = noErr; |
// set file position to beginning of file |
myErr = SetFPos(theRefNum, fsFromStart , 0); |
if (myErr != noErr) |
return(myErr); |
// get file length, so we don't overflow |
myErr = GetEOF(theRefNum, &mySize); |
if (myErr != noErr) |
return(myErr); |
(*theDesc)->dataSize = mySize; |
// loop forever |
while (true) { |
myMarker = QTJPEG_FindNextMarker(theRefNum); |
switch (myMarker) { |
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: |
myErr = QTJPEG_HandleAPPOMarker(myMarker, theRefNum, theDesc, &readingExtension); |
if (myErr != noErr) |
return(myErr); |
break; |
case kCommentMarker: |
QTJPEG_SkipLength(theRefNum); |
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: |
myErr = QTJPEG_HandleSOFMarker(theRefNum, theDesc, readingExtension); |
if (myErr != noErr) |
return(myErr); |
if (!readingExtension) { |
MacSetRect(theRect, 0, 0, (*theDesc)->width, (*theDesc)->height); |
return(noErr); |
} |
break; |
case kDACMarker: |
mySkip = QTJPEG_ReadWord(theRefNum) - 2; |
mySkip *= QTJPEG_ReadWord(theRefNum); |
myErr = SetFPos(theRefNum, fsFromMark, mySkip); |
break; |
case kSOSMarker: |
QTJPEG_HandleSOSMarker(theRefNum); |
break; |
case kDHTMarker: |
case kDQTMarker: |
case kRSTOMarker: |
QTJPEG_SkipLength(theRefNum); |
break; |
case kEOIMarker: // we reached the end of image |
// we are reading an extension so keep going |
if (readingExtension) |
readingExtension = false; |
break; |
} |
} |
return(myErr); |
} |
////////// |
// |
// QTJPEG_FindNextMarker |
// Find the next marker in the specified file. |
// |
////////// |
UInt8 QTJPEG_FindNextMarker (long theRefNum) |
{ |
UInt8 myMarker; |
myMarker = QTJPEG_ReadByte(theRefNum); |
while (myMarker == kStartMarker) |
myMarker = QTJPEG_ReadByte(theRefNum); |
while (myMarker == 0x00) { |
myMarker = QTJPEG_ReadByte(theRefNum); |
while (myMarker != kStartMarker) |
myMarker = QTJPEG_ReadByte(theRefNum); |
myMarker = QTJPEG_ReadByte(theRefNum); |
} |
return(myMarker); |
} |
////////// |
// |
// QTJPEG_HandleAPPOMarker |
// Handle an APPO marker in the specified file. |
// |
////////// |
OSErr QTJPEG_HandleAPPOMarker (UInt8 theMarker, long theRefNum, ImageDescriptionHandle theDesc, Boolean *readingExtension) |
{ |
Fixed xRes, yRes; |
long myLength; |
short myUnits; |
short myVersion; |
UInt8 myExtension; |
UInt8 myType[5]; |
OSErr myErr = noErr; |
// read skip bytes - header length - skip count |
myLength = QTJPEG_ReadWord(theRefNum) - 2; |
if ((theMarker == kAPPOMarker) && (myLength >= 14)) { |
myType[0] = QTJPEG_ReadByte(theRefNum); |
myType[1] = QTJPEG_ReadByte(theRefNum); |
myType[2] = QTJPEG_ReadByte(theRefNum); |
myType[3] = QTJPEG_ReadByte(theRefNum); |
myType[4] = QTJPEG_ReadByte(theRefNum); |
// check to see if we really have the JFIF header |
if ((myType[0] == 'J') && |
(myType[1] == 'F') && |
(myType[2] == 'I') && |
(myType[3] == 'F')) { |
myVersion = QTJPEG_ReadWord(theRefNum); |
if (myVersion < 0x100) |
return(paramErr); // don't know this |
else |
(*theDesc)->version = myVersion; |
myUnits = QTJPEG_ReadByte(theRefNum); |
xRes = QTJPEG_ReadWord(theRefNum); |
yRes = QTJPEG_ReadWord(theRefNum); |
switch (myUnits) { |
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); // yRes?? RTM |
break; |
default: |
break; |
} |
(*theDesc)->hRes = xRes; |
(*theDesc)->vRes = yRes; |
myLength -= 12; |
myErr = SetFPos(theRefNum, fsFromMark, myLength); |
} else { |
if ((myType[0] == 'J') && |
(myType[1] == 'F') && |
(myType[2] == 'X') && |
(myType[3] == 'X')) { |
*readingExtension = true; // next markers are extensions; ignore them |
myExtension = QTJPEG_ReadByte(theRefNum); |
switch (myExtension) { |
case 0x10: |
case 0x11: |
case 0x13: |
break; |
default: |
return(paramErr); |
} |
} |
} |
} else |
myErr = SetFPos(theRefNum, fsFromMark, myLength); |
return(myErr); |
} |
////////// |
// |
// QTJPEG_HandleSOFMarker |
// Handle an SOF marker in the specified file. |
// |
////////// |
OSErr QTJPEG_HandleSOFMarker (long theRefNum, ImageDescriptionHandle theDesc, Boolean readingExtension) |
{ |
short myWidth = 0; |
short myHeight = 0; |
short myComponents; |
short myLength; |
StringPtr myTitle = QTUtils_ConvertCToPascalString(kWindowTitle); |
OSErr myErr = noErr; |
if (!readingExtension) { |
myLength = QTJPEG_ReadWord(theRefNum); |
QTJPEG_ReadByte(theRefNum); |
myHeight = QTJPEG_ReadWord(theRefNum); |
myWidth = QTJPEG_ReadWord(theRefNum); |
// make sure we do have something to display |
if ((myWidth != 0) && (myHeight != 0)) { |
// now set up the image description |
(*theDesc)->idSize = sizeof(ImageDescription); |
(*theDesc)->cType = FOUR_CHAR_CODE('jpeg'); |
(*theDesc)->dataRefIndex = 0; |
(*theDesc)->revisionLevel = 0; |
(*theDesc)->vendor = 0; |
(*theDesc)->temporalQuality = 0; |
(*theDesc)->spatialQuality = codecNormalQuality; |
(*theDesc)->width = myWidth; |
(*theDesc)->height = myHeight; |
(*theDesc)->frameCount = 1; |
BlockMove(myTitle, (*theDesc)->name, 13); |
(*theDesc)->clutID = -1; |
myComponents = QTJPEG_ReadByte(theRefNum); |
switch (myComponents) { |
case 1: |
(*theDesc)->depth = 40; |
break; |
case 3: |
(*theDesc)->depth = 32; |
break; |
case 4: |
(*theDesc)->depth = 32; |
break; |
default: |
myErr = paramErr; |
return(myErr); |
break; |
} |
myErr = SetFPos(theRefNum, fsFromMark, myLength - 8); |
return(noErr); |
} |
} else { |
myLength = QTJPEG_ReadWord(theRefNum) - 2; |
myErr = SetFPos(theRefNum, fsFromMark, myLength); |
if (myErr != noErr) |
return(myErr); |
} |
free(myTitle); |
return(myErr); |
} |
////////// |
// |
// QTJPEG_HandleSOSMarker |
// Handle an SOS marker in the specified file. |
// |
////////// |
void QTJPEG_HandleSOSMarker (long theRefNum) |
{ |
short myComponents; |
short myWord; |
QTJPEG_ReadWord(theRefNum); |
myComponents = QTJPEG_ReadByte(theRefNum); |
for (myWord = 0; myWord < myComponents; myWord++) |
QTJPEG_ReadWord(theRefNum); |
} |
////////// |
// |
// QTJPEG_ReadByte |
// Read the next byte from the specified file. |
// |
////////// |
UInt8 QTJPEG_ReadByte (short theRefNum) |
{ |
UInt8 myData; |
long myBytesNeeded = sizeof(char); |
(void)FSRead(theRefNum, &myBytesNeeded, &myData); |
return(myData); |
} |
////////// |
// |
// QTJPEG_ReadWord |
// Read the next word from the specified file. |
// |
////////// |
UInt16 QTJPEG_ReadWord (short theRefNum) |
{ |
UInt16 myData; |
long myBytesNeeded = sizeof(UInt16); |
(void)FSRead(theRefNum, &myBytesNeeded, &myData); |
// in JPEG files, the data is stored in a big-endian order |
myData = EndianU16_BtoN(myData); |
return(myData); |
} |
////////// |
// |
// QTJPEG_SkipLength |
// Skip over the length word. |
// |
////////// |
void QTJPEG_SkipLength (long theRefNum) |
{ |
UInt16 mySkip; |
mySkip = QTJPEG_ReadWord(theRefNum) - 2; |
SetFPos(theRefNum, fsFromMark, mySkip); |
} |
////////// |
// |
// QTJPEG_NewJPEGWorld |
// Return, through the theWorld parameter, a new offscreen graphics world suitable |
// for drawing a JPEG image of the specified bitdepth into. |
// |
////////// |
OSErr QTJPEG_NewJPEGWorld (GWorldPtr *theWorld, short theDepth, Rect theRect) |
{ |
GWorldPtr oldPort; |
GDHandle oldGD; |
PixMapHandle myPixMap; |
CTabHandle myCTab = NULL; |
OSErr myErr = paramErr; |
if (theWorld != NULL) { |
// save the current graphics port |
GetGWorld(&oldPort, &oldGD); |
// if depth is greater than 32, then the image is grayscale |
if (theDepth > 32) { |
myCTab = GetCTable(theDepth); |
theDepth = theDepth - 32; |
} |
// first try to allocate a GWorld in the application's heap |
myErr = NewGWorld(theWorld, theDepth, &theRect, myCTab, NULL, 0L); |
// otherwise, try to allocate a GWorld in temporary memory |
if (myErr != noErr) |
myErr = NewGWorld(theWorld, theDepth, &theRect, myCTab, NULL, useTempMem); |
if ((myErr == noErr) && (theWorld != NULL)) { |
myPixMap = GetGWorldPixMap(*theWorld); |
if (LockPixels(myPixMap)) { |
SetGWorld(*theWorld, NULL); |
EraseRect(&theRect); |
UnlockPixels(myPixMap); |
} |
} |
SetGWorld(oldPort, oldGD); |
} |
return(myErr); |
} |
////////// |
// |
// QTJPEG_SaveJPEG |
// Save the specified image as a compressed file. |
// |
////////// |
OSErr QTJPEG_SaveJPEG (GWorldPtr theWorld) |
{ |
StandardFileReply myReply; |
ImageDescriptionHandle myDesc; |
Handle myData; |
Rect myRect; |
PixMapHandle myPixMap; |
CTabHandle myCTab = NULL; |
ICMFlushProcRecord myFlushProc; |
short myRefNum; |
short myDepth; |
StringPtr myImagePrompt = QTUtils_ConvertCToPascalString(kSaveImagePrompt); |
StringPtr myImageFileName = QTUtils_ConvertCToPascalString(kSaveImageFileName); |
OSErr myErr = paramErr; |
if (theWorld == NULL) |
goto bail; |
// have the user select the name of the new image file |
StandardPutFile(myImagePrompt, myImageFileName, &myReply); |
if (!myReply.sfGood) |
goto bail; |
myDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription)); |
if (myDesc == NULL) |
goto bail; |
myRect = theWorld->portRect; |
myPixMap = GetGWorldPixMap(theWorld); |
if (LockPixels(myPixMap)) { |
// if less than 16-bit then get the color table of our GWorld |
myDepth = (**myPixMap).pixelSize; |
if (myDepth < 16) |
myCTab = (**myPixMap).pmTable; |
myData = NewHandle(kBufferSize); |
myErr = MemError(); |
if ((myData != NULL) && (myErr == noErr)) { |
CodecType myCodec = kJPEGCodecType; |
HLock(myData); |
if (myReply.sfReplacing) |
myErr = FSpDelete(&myReply.sfFile); |
myErr = FSpCreate(&myReply.sfFile, kImageFileCreator, kQTFileTypeJPEG, myReply.sfScript); |
if (myErr == noErr) |
myErr = FSpOpenDF(&myReply.sfFile, fsRdWrPerm, &myRefNum); |
if (myErr == noErr) |
myErr = SetFPos(myRefNum, fsFromStart, 0); |
if (myErr == noErr) { |
ICMFlushProcRecordPtr myFlushProcPtr = NULL; |
myFlushProc.flushProc = NewICMFlushProc(QTJPEG_DataUnloadProc); |
myFlushProc.flushRefCon = myRefNum; |
myFlushProcPtr = &myFlushProc; |
// compress the image |
myErr = FCompressImage( myPixMap, |
&myRect, |
myDepth, |
codecNormalQuality, |
myCodec, |
anyCodec, |
myCTab, |
codecFlagWasCompressed, |
kBufferSize, |
myFlushProcPtr, |
NULL, |
myDesc, |
*myData); |
if (myErr == noErr) |
myErr = SetFPos(myRefNum, fsFromStart, (**myDesc).dataSize); |
if (myErr == noErr) |
myErr = SetEOF(myRefNum, (**myDesc).dataSize); |
if (myErr == noErr) |
myErr = FSClose(myRefNum); |
HUnlock(myData); |
DisposeHandle(myData); |
DisposeRoutineDescriptor(myFlushProc.flushProc); |
} |
} |
} |
UnlockPixels(myPixMap); |
bail: |
if (myDesc != NULL) |
DisposeHandle((Handle)myDesc); |
free(myImagePrompt); |
free(myImageFileName); |
return(myErr); |
} |
////////// |
// |
// QTJPEG_DataUnloadProc |
// A data unloading procedure: write the compressed data to disk. |
// |
// The theRefCon parameter is assumed to be a file reference number of an open file. |
// |
////////// |
PASCAL_RTN OSErr QTJPEG_DataUnloadProc (Ptr theData, long theBytesNeeded, long theRefCon) |
{ |
OSErr myErr = noErr; |
if (theData == NULL) { |
// if data is NULL, set a new position in the file from the current mark, offset by bytesNeeded |
myErr = SetFPos(theRefCon, fsFromMark, theBytesNeeded); |
} else { |
// otherwise, write the specified data to disk |
myErr = FSWrite(theRefCon, &theBytesNeeded, theData); |
} |
return(myErr); |
} |
////////// |
// |
// QTJPEG_DataLoadingProc |
// A data loading procedure: read the data from disk. |
// |
// The theRefCon parameter is assumed to be a pointer to our custom data-loading record. |
// |
////////// |
PASCAL_RTN OSErr QTJPEG_DataLoadingProc (Ptr *theData, long theBytesNeeded, long theRefCon) |
{ |
long myUnusedDataLen; |
long myNewDataLen; |
DLDataPtr myDataRec; |
Ptr myDataPtr; |
OSErr myErr = noErr; |
myDataRec = (DLDataPtr)theRefCon; |
if (theData == NULL) { |
myErr = SetFPos(myDataRec->fRefNum, fsFromMark, theBytesNeeded); |
} else { |
myDataPtr = *theData; |
// if QT requests more data than is in the buffer, we will have to load more |
if ((myDataPtr + theBytesNeeded) >= myDataRec->fEndPtr) { |
// move whats left up to the front of the buffer |
myUnusedDataLen = myDataRec->fEndPtr - myDataPtr; |
BlockMove(myDataPtr, myDataRec->fOrigPtr, myUnusedDataLen); |
// now fill the buffer with new data, |
// following the data we moved to the front of the buffer |
myNewDataLen = kBufferSize - myUnusedDataLen; |
if (myNewDataLen > myDataRec->fFileLength) |
myNewDataLen = myDataRec->fFileLength; |
myDataPtr = myDataRec->fOrigPtr + myUnusedDataLen; |
myErr = FSRead(myDataRec->fRefNum, &myNewDataLen, myDataPtr); |
myDataRec->fFileLength -= myNewDataLen; |
*theData = myDataRec->fOrigPtr; |
} |
} |
return(myErr); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14