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.
QTReadWriteJPEG.c
////////// |
// |
// File: QTReadWriteJPEG.c |
// |
// Contains: Sample code for compressing and decompressing JPEG images. |
// |
// Written by: Michael Marinkovich and Guillermo Ortiz |
// Revised by: Tim Monroe |
// Based heavily on the existing "JPEG Sample" code. |
// |
// 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 |
// |
// This sample code illustrates how to compress and decompress JPEG images using QuickTime. |
// We use the FCompressImage function, but you could also use the CompressImage function. |
// Although this sample demonstrates only JPEG compression/decompression, you could use this |
// as a framework for other types of compression (except for the decoding of the JPEG header). |
// |
// In this sample code, we allow the user to open a JPEG image file; then we draw it into |
// a window on the screen. Your application, of course, will probably want to do more |
// interesting things with the image. We also allow the user to save an image using JPEG |
// compression. |
// |
// NOTES: |
// |
// *** (1) *** |
// 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 redistribute 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. |
// |
////////// |
////////// |
// |
// 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 |
////////// |
// |
// 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); |
} |
////////// |
// |
// 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 = 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); |
} |
////////// |
// |
// 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