From Stephan Huber, "attached you'll find a refactored and improved quicktime-plugin. I moved all code related to the image-loading, swizzling etc into an extra class. The quicktime plugin supports now reading and writing images from/to streams and the code is less cluttered than previous versions.

I removed QTtexture.h/.cpp and added QTImportExport.h/.cpp. I updated the CMake-files, I hope they are alright. I used the submitted code in my own apps since two months or so and it seems pretty stable, but as always the migration to the osg-quicktime plugin may have introduced new bugs, so perfect for developer release :)"
This commit is contained in:
Robert Osfield
2008-04-17 15:30:35 +00:00
parent 1b29fb8933
commit a1f1f5114c
6 changed files with 706 additions and 728 deletions

View File

@@ -9,7 +9,7 @@ SET(TARGET_SRC
MovieData.cpp
QTUtils.cpp
QTLiveUtils.cpp
QTtexture.cpp
QTImportExport.cpp
QuicktimeImageStream.cpp
QuicktimeLiveImageStream.cpp
ReaderWriterQT.cpp
@@ -19,7 +19,7 @@ SET(TARGET_H
MovieData.h
QTUtils.h
QTLiveUtils.h
QTtexture.h
QTImportExport.h
QuicktimeImageStream.h
QuicktimeLiveImageStream.h
)

View File

@@ -0,0 +1,526 @@
/*
* QTImportExport.cpp
* cefix
*
* Created by Stephan Huber on 07.02.08.
* Copyright 2008 __MyCompanyName__. All rights reserved.
*
*/
#include <map>
#include <sstream>
#include "QTImportExport.h"
#include "QTUtils.h"
#include <osgDB/FileNameUtils>
/** small exception class bundling a error-message */
class QTImportExportException : public std::exception {
public:
QTImportExportException(int err, const std::string& msg) : std::exception(), _err(err), _msg(msg) {}
virtual const char* what() { return _msg.c_str(); }
int getErrorCode() { return _err; }
virtual ~QTImportExportException() throw () {}
private:
int _err;
std::string _msg;
};
QuicktimeImportExport::QuicktimeImportExport()
: _error(0),
_lastError("")
{
initQuicktime();
}
// ----------------------------------------------------------------------------------------------------------
// flipImage
// ----------------------------------------------------------------------------------------------------------
void QuicktimeImportExport::flipImage(unsigned char* pixels, int bytesPerPixel, unsigned int width, unsigned height)
{
// Flip the image
unsigned imageSize = width * height * bytesPerPixel;
char *tBuffer = new char [imageSize];
unsigned int rowBytes = width * bytesPerPixel;
unsigned int i,j;
for (i = 0, j = imageSize - rowBytes; i < imageSize; i += rowBytes, j -= rowBytes)
memcpy( &tBuffer[j], &pixels[i], (size_t)rowBytes );
memcpy(pixels, tBuffer, (size_t)imageSize);
delete[] tBuffer;
}
// ----------------------------------------------------------------------------------------------------------
// prepareBufferForOSG
// ----------------------------------------------------------------------------------------------------------
unsigned char* QuicktimeImportExport::pepareBufferForOSG(unsigned char * buffer, int bytesPerPixel, unsigned int width, unsigned int height)
{
unsigned char *pixels = new unsigned char [height * width * 4];
unsigned char *dstp = pixels;
unsigned char *srcp = buffer;
unsigned int i, j;
int roffset, goffset, boffset, aoffset;
aoffset = -1;
int sourceStep;
switch (bytesPerPixel) {
case 1:
sourceStep = 1;
roffset = goffset = boffset = 0;
break;
case 3:
sourceStep = 3;
roffset = 0;
goffset = 1;
boffset = 2;
break;
case 4:
sourceStep = 4;
aoffset = 1;
roffset = 2;
goffset = 3;
boffset = 0;
break;
}
for (i = 0; i < height; ++i )
{
for (j = 0; j < width; ++j )
{
dstp[0] = (aoffset < 0) ? 0 : srcp[aoffset];
dstp[1] = srcp[roffset];
dstp[2] = srcp[goffset];
dstp[3] = srcp[boffset];
srcp+=sourceStep;
dstp+=4;
}
}
flipImage(pixels, bytesPerPixel, width, height);
return pixels;
}
// ----------------------------------------------------------------------------------------------------------
// prepareBufferForQuicktime
// ----------------------------------------------------------------------------------------------------------
unsigned char* QuicktimeImportExport::prepareBufferForQuicktime(unsigned char* buffer, GLenum pixelFormat, int bytesPerPixel, unsigned int width, unsigned int height)
{
unsigned char *pixels = new unsigned char [height * width * 4];
unsigned char *dstp = pixels;
unsigned char *srcp = buffer;
unsigned int i, j;
int roffset, goffset, boffset, aoffset;
aoffset = -1;
int sourceStep;
switch (bytesPerPixel) {
case 1:
sourceStep = 1;
roffset = goffset = boffset = 0;
break;
case 3:
sourceStep = 3;
roffset = 0;
goffset = 1;
boffset = 2;
break;
case 4:
sourceStep = 4;
switch (pixelFormat) {
case GL_RGBA:
aoffset = 3;
roffset = 0;
goffset = 1;
boffset = 2;
break;
case GL_BGRA_EXT:
aoffset = 0;
roffset = 1;
goffset = 2;
boffset = 3;
break;
}
}
for (i = 0; i < height; ++i )
{
for (j = 0; j < width; ++j )
{
dstp[0] = (aoffset < 0) ? 0 : srcp[aoffset];
dstp[1] = srcp[roffset];
dstp[2] = srcp[goffset];
dstp[3] = srcp[boffset];
srcp+=sourceStep;
dstp+=4;
}
}
flipImage(pixels, 4, width, height);
return pixels;
}
// ----------------------------------------------------------------------------------------------------------
// readFromStream
// ----------------------------------------------------------------------------------------------------------
osg::Image* QuicktimeImportExport::readFromStream(std::istream & inStream, const std::string& fileTypeHint, long sizeHint)
{
char* content = NULL;
long length = 0;
if (sizeHint != 0)
{
length = sizeHint;
content = new char[length];
inStream.read (content,length);
}
else
{
int readBytes(0), newBytes(0);
char buffer[10240];
while (!inStream.eof()) {
inStream.read(buffer, 10240);
newBytes = inStream.gcount();
if (newBytes > 0) {
char* newcontent = new char[readBytes + newBytes];
if (readBytes > 0)
memcpy(newcontent, content, readBytes);
memcpy(&newcontent[readBytes], &buffer, newBytes);
readBytes += newBytes;
if (content) delete[] content;
content = newcontent;
}
}
length = readBytes;
}
osg::Image* img = doImport(reinterpret_cast<unsigned char*>(content), length, fileTypeHint);
if (content) delete[] content;
return img;
}
Handle getPtrDataRef(unsigned char *data, unsigned int size, const std::string &filename)
{
// Load Data Reference
Handle dataRef;
Handle fileNameHandle;
PointerDataRefRecord ptrDataRefRec;
ComponentInstance dataRefHandler;
unsigned char pstr[255];
ptrDataRefRec.data = data;
ptrDataRefRec.dataLength = size;
/*err = */PtrToHand(&ptrDataRefRec, &dataRef, sizeof(PointerDataRefRecord));
// Open a Data Handler for the Data Reference
/*err = */OpenADataHandler(dataRef, PointerDataHandlerSubType, NULL,
(OSType)0, NULL, kDataHCanRead, &dataRefHandler);
// Convert From CString in filename to a PascalString in pstr
if (filename.length() > 255) {
CopyCStringToPascal(filename.c_str(), pstr);
//hmm...not good, pascal string limit is 255!
//do some error handling maybe?!
}
// Add filename extension
/*err = */PtrToHand(pstr, &fileNameHandle, filename.length() + 1);
/*err = */DataHSetDataRefExtension(dataRefHandler, fileNameHandle,
kDataRefExtensionFileName);
DisposeHandle(fileNameHandle);
// Release old handler which does not have the extensions
DisposeHandle(dataRef);
// Grab the SAFE_NEW version of the data ref from the data handler
/*err = */ DataHGetDataRef(dataRefHandler, &dataRef);
CloseComponent(dataRefHandler);
return dataRef;
}
osg::Image* QuicktimeImportExport::doImport(unsigned char* data, unsigned int sizeData, const std::string& fileTypeHint)
{
GWorldPtr gworld = 0;
OSType pixelFormat;
int rowStride;
GraphicsImportComponent gicomp = 0;
Rect rectImage;
GDHandle origDevice = 0;
CGrafPtr origPort = 0;
ImageDescriptionHandle desc = 0;
int depth = 32;
unsigned int xsize, ysize;
unsigned char* imageData;
// Data Handle for file data ( & load data from file )
Handle dataRef = getPtrDataRef(data, sizeData, fileTypeHint);
try {
OSErr err = noErr;
// GraphicsImporter - Get Importer for our filetype
GetGraphicsImporterForDataRef(dataRef, 'ptr ', &gicomp);
// GWorld - Get Texture Info
err = GraphicsImportGetNaturalBounds(gicomp, &rectImage);
if (err != noErr) {
throw QTImportExportException(err, "GraphicsImportGetNaturalBounds failed");
}
xsize = (unsigned int)(rectImage.right - rectImage.left);
ysize = (unsigned int)(rectImage.bottom - rectImage.top);
// ImageDescription - Get Image Description
err = GraphicsImportGetImageDescription(gicomp, &desc);
if (err != noErr) {
throw QTImportExportException(err, "GraphicsImportGetImageDescription failed");
}
// ImageDescription - Get Bit Depth
HLock(reinterpret_cast<char **>(desc));
// GWorld - Pixel Format stuff
pixelFormat = k32ARGBPixelFormat; // Make sure its forced...NOTE: i'm pretty sure this cannot be RGBA!
// GWorld - Row stride
rowStride = xsize * 4; // (width * depth_bpp / 8)
// GWorld - Allocate output buffer
imageData = new unsigned char[rowStride * ysize];
// GWorld - Actually Create IT!
QTNewGWorldFromPtr(&gworld, pixelFormat, &rectImage, 0, 0, 0, imageData, rowStride);
if (!gworld) {
throw QTImportExportException(-1, "QTNewGWorldFromPtr failed");
}
// Save old Graphics Device and Graphics Port to reset to later
GetGWorld (&origPort, &origDevice);
// GraphicsImporter - Set Destination GWorld (our buffer)
err = GraphicsImportSetGWorld(gicomp, gworld, 0);
if (err != noErr) {
throw QTImportExportException(err, "GraphicsImportSetGWorld failed");
}
// GraphicsImporter - Set Quality Level
err = GraphicsImportSetQuality(gicomp, codecLosslessQuality);
if (err != noErr) {
throw QTImportExportException(err, "GraphicsImportSetQuality failed");
}
// Lock pixels so that we can draw to our memory texture
if (!GetGWorldPixMap(gworld) || !LockPixels(GetGWorldPixMap(gworld))) {
throw QTImportExportException(0, "GetGWorldPixMap failed");
}
//*** Draw GWorld into our Memory Texture!
GraphicsImportDraw(gicomp);
// Clean up
UnlockPixels(GetGWorldPixMap(gworld));
SetGWorld(origPort, origDevice); // set graphics port to offscreen (we don't need it now)
DisposeGWorld(gworld);
CloseComponent(gicomp);
DisposeHandle(reinterpret_cast<char **>(desc));
DisposeHandle(dataRef);
}
catch (QTImportExportException e)
{
setError(e.what());
if (gworld) {
UnlockPixels(GetGWorldPixMap(gworld));
SetGWorld(origPort, origDevice); // set graphics port to offscreen (we don't need it now)
DisposeGWorld(gworld);
}
if (gicomp)
CloseComponent(gicomp);
if (desc)
DisposeHandle(reinterpret_cast<char **>(desc));
if (imageData)
delete[] imageData;
if (dataRef)
DisposeHandle(dataRef);
return NULL;
}
unsigned int glpixelFormat;
switch(depth >> 3) {
case 3 :
glpixelFormat = GL_RGB;
break;
case 4 :
glpixelFormat = GL_RGBA;
break;
default :
delete imageData;
setError("unknown pixelformat");
return NULL;
break;
}
unsigned char* swizzled = pepareBufferForOSG(imageData, depth >> 3, xsize, ysize);
delete[] imageData;
osg::Image* image = new osg::Image();
image->setFileName(fileTypeHint.c_str());
image->setImage(xsize,ysize,1,
depth >> 3,
glpixelFormat,
GL_UNSIGNED_BYTE,
swizzled,
osg::Image::USE_NEW_DELETE );
return image;
}
void QuicktimeImportExport::writeToStream(std::ostream& outStream, osg::Image* image, const std::string& fileTypeHint)
{
std::string ext = osgDB::getFileExtension(fileTypeHint);
//Build map of extension <-> osFileTypes
static std::map<std::string, OSType> extmap;
if (extmap.size() == 0) {
extmap["jpg"] = kQTFileTypeJPEG;
extmap["jpeg"] = kQTFileTypeJPEG;
extmap["bmp"] = kQTFileTypeBMP;
extmap["tif"] = kQTFileTypeTIFF;
extmap["tiff"] = kQTFileTypeTIFF;
extmap["png"] = kQTFileTypePNG;
extmap["gif"] = kQTFileTypeGIF;
extmap["psd"] = kQTFileTypePhotoShop;
extmap["sgi"] = kQTFileTypeSGIImage;
extmap["rgb"] = kQTFileTypeSGIImage;
extmap["rgba"] = kQTFileTypeSGIImage;
}
std::map<std::string, OSType>::iterator cur = extmap.find(ext);
// can not handle this type of file, perhaps a movie?
if (cur == extmap.end())
return;
unsigned int numBytes = image->computeNumComponents(image->getPixelFormat());
unsigned char* pixels = prepareBufferForQuicktime(
image->data(),
image->getPixelFormat(),
numBytes,
image->s(),
image->t()
);
OSType desiredType = cur->second;
GraphicsExportComponent geComp = NULL;
GWorldPtr gw = 0;
Handle dataHandle;
dataHandle = NewHandle(0);
try {
OSErr err = OpenADefaultComponent(GraphicsExporterComponentType, desiredType, &geComp);
Rect bounds = {0,0, image->t(), image->s()};
err = QTNewGWorldFromPtr(&gw, k32ARGBPixelFormat, &bounds, 0,0,0, pixels, image->s()*4);
if (err != noErr) {
throw QTImportExportException(err, "could not create gworld for type " + ext);
}
err = GraphicsExportSetInputGWorld(geComp, gw);
if (err != noErr) {
throw QTImportExportException(err, "could not set input gworld for type " + ext);
}
err = GraphicsExportSetOutputHandle( geComp, dataHandle);
if (err != noErr) {
throw QTImportExportException(err, "could not set output file for type " + ext);
}
// Set the compression quality (needed for JPEG, not necessarily for other formats)
if (desiredType == kQTFileTypeJPEG) {
err = GraphicsExportSetCompressionQuality(geComp, codecLosslessQuality);
if (err != noErr) {
throw QTImportExportException(err, "could not set compression for type " + ext);
}
}
if(4 == numBytes)
{
err = GraphicsExportSetDepth( geComp, k32ARGBPixelFormat ); // depth
}
// else k24RGBPixelFormat???
// do the export
err = GraphicsExportDoExport(geComp, NULL);
if (err != noErr) {
throw QTImportExportException(err, "could not do the export for type " + ext);
}
if (geComp != NULL)
CloseComponent(geComp);
if (gw) DisposeGWorld (gw);
if (pixels) free(pixels);
outStream.write(*dataHandle, GetHandleSize(dataHandle));
DisposeHandle(dataHandle);
}
catch (QTImportExportException e)
{
setError(e.what());
if (geComp != NULL) CloseComponent(geComp);
if (gw != NULL) DisposeGWorld (gw);
if (pixels) free(pixels);
DisposeHandle(dataHandle);
}
}

View File

@@ -0,0 +1,65 @@
/*
* QTImportExport.h
* cefix
*
* Created by Stephan Huber on 07.02.08.
* Copyright 2008 StephanMaximilianHuber, digitalmind. All rights reserved.
*
*/
#include <osg/Image>
#ifndef QT_IMPORT_EXPORT_HEADER
#define QT_IMPORT_EXPORT_HEADER
/** small helper class handling the im- and export of image data via quicktime */
class QuicktimeImportExport {
public:
/** ctor */
QuicktimeImportExport();
/** readFromString tries to read a chunk of bytes and interpret it as an image.
* @param istream the input stream
* @param fileTypeHint you can speed up the conversion by providing a filename with extension, so quicktime has not to guess the image's type
* @param sizeHint useful, if you are streaming data, if you provide a sizeHint ony sizeHint bytes are read from the stream
*/
osg::Image* readFromStream(std::istream & inStream, const std::string& fileTypeHint, long sizeHint = 0);
/** writes an osg::Image to a stream, using fileTypeHint as a hint whar format you want to write. */
void writeToStream(std::ostream& outStream, osg::Image* image, const std::string& fileTypeHint) ;
/** get the last error-message */
const std::string getLastErrorString() { return _lastError; }
/** return true if no error occured */
bool success() { return (_error == false); }
protected:
/** flips an image */
void flipImage(unsigned char* buffer, int bytesPerPixel, unsigned int width, unsigned height);
/** do some swizzling, so osg can use the image */
unsigned char* pepareBufferForOSG(unsigned char * buffer, int bytesPerPixel, unsigned int width, unsigned height);
/** do some swizzling, so quicktime can use the image */
unsigned char* prepareBufferForQuicktime(unsigned char* buffer, GLenum pixelFormat, int bytesPerPixel, unsigned int width, unsigned int height) ;
/** sets an error-msg */
void setError(const std::string& msg) { _lastError = msg; _error = true; }
/** do the import */
osg::Image* doImport(unsigned char* buffer, unsigned int dataSize, const std::string& fileTypeHint);
private:
bool _error;
std::string _lastError;
};
#endif

View File

@@ -1,396 +0,0 @@
/*
* QTtexture.c
* Cocoa rostrumMIP
*
* Created by philatki on Thu Nov 29 2001.
* Copyright (c) 2001 __MyCompanyName__. All rights reserved.
*
*/
/*
PORTIONS OF THIS CODE ARE COPYRIGHT APPLE COMPUTER -
Copyright: Copyright <20> 2001 Apple Computer, Inc., All Rights Reserved
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<6C>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.
*
*/
#ifdef __APPLE__
#include <Carbon/Carbon.h>
#include <QuickTime/ImageCompression.h> // for image loading and decompression
#include <QuickTime/QuickTimeComponents.h> // for file type support
#else
#include <ImageCompression.h> // for image loading and decompression
#include <QuickTimeComponents.h> // for file type support
#endif
#include <osg/GL> // for OpenGL API
#include "QTUtils.h"
#include "QTtexture.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ==================================
enum // how to scale image to power of two on read if scaling
{
kNone = 1,
kNearest, // find nearest power of 2
kNearestLess, // nearest power of 2 which is less than or equal image dimension
KNearestGreater, // nearest power of 2 which is greater than or equal image dimension
k32, // use this size specifically
k64,
k128,
k256,
k512,
k1024,
k2048,
k4096,
k8192
};
// Default values for images loading
short gTextureScale = k1024; // for non-tiled images the type of texture scaling to do
short gMaxTextureSize = 4096; // maximum texture size to use for application
Boolean gfTileTextures = true; // are multiple tiled textures used to display image?
Boolean gfOverlapTextures = true; // do tiled textures overlapped to create correct filtering between tiles? (only applies if using tiled textures)
Boolean gfClientTextures = false; // 10.1+ only: texture from client memory
Boolean gfAGPTextures = false; // 10.1+ only: texture from AGP memory without loading to GPU can be set after inmage loaded
Boolean gfNPOTTextures = false; // 10.1+ only: use Non-Power Of Two (NPOT) textures
// ---------------------------------
static long GetScaledTextureDimFromImageDim (long imageDimension, short scaling);
static unsigned char * LoadBufferFromImageFile (FSSpec fsspecImage, short imageScale,
long * pOrigImageWidth, long * pOrigImageHeight, long *pOrigDepth,
long * pBufferWidth, long * pBufferHeight, long * pBufferDepth);
// ---------------------------------
// based on scaling determine the texture dimension which fits the image dimension passe in
// kNone: no scaling just use image dimension (will not guarentee support power for 2 textures)
// kNearest: find nearest power of 2 texture
// kNearestLess: find nearest power of 2 texture which is less than image dimension
// kNearestGreater: find nearest power of 2 texture which is greater than image dimension
// k32 - k1024: use this specific texture size
static long GetScaledTextureDimFromImageDim (long imageDimension, short scaling)
{
switch (scaling)
{
case kNone: // no scaling
return imageDimension;
break;
case kNearest: // scale to nearest power of two
{
// find power of 2 greater
long i = 0, texDim = 1, imageTemp = imageDimension;
while (imageTemp >>= 1) // while the dimension still has bits of data shift right (losing a bit at a time)
i++; // count shifts (i.e., powers of two)
texDim = texDim << i; // shift our one bit representation left the like amount (i.e., 2 ^ i)
if (texDim >= gMaxTextureSize) // if we are too big or at max size
return gMaxTextureSize; // then must use max texture size
// are we closer to greater pow 2 or closer to higher pow 2?
// compare to the power of two that is double of initial guess
else if (((texDim << 1) - imageDimension) < (imageDimension - texDim))
return (texDim << 1); // if it is closer away then return double guess
else
return texDim; // otherwise orginal guess is closer so return this
}
break;
case kNearestLess:
{
// find power of 2 lower
long i = 0, texDim = 1;
while (imageDimension >>= 1) // while the dimension still has bits of data shift right (losing a bit at a time)
i++; // count shifts (i.e., powers of two)
texDim = texDim << i; // shift our one bit representation left the like amount (i.e., 2 ^ i)
return texDim; // returns the maxium power of two that is less than or equal the texture dimension
}
break;
case KNearestGreater:
{
// find power of 2 greater
long i = 0, texDim = 1;
while (imageDimension >>= 1) // while the dimension still has bits of data shift right (losing a bit at a time)
i++; // count shifts (i.e., powers of two)
texDim = texDim << (i + 1); // shift our one bit representation left the like amount (i.e., 2 ^ (i + 1))
return texDim; // returns the minimum power of two that is greater than or equal the texture dimension
}
break;
case k32: // return hard values for texture dimension
return 32;
break;
case k64:
return 64;
break;
case k128:
return 128;
break;
case k256:
return 256;
break;
case k512:
return 512;
break;
case k1024:
return 1024;
break;
case k2048:
return 2048;
break;
case k4096:
return 8192;
break;
case k8192:
return 8192;
break;
}
return 0;
}
static char errMess[256];
char *QTfailureMessage(void) { return errMess; }
static unsigned char * LoadBufferFromImageFile ( FSSpec fsspecImage,
short imageScale,
long *pOrigImageWidth, long *pOrigImageHeight, long *pOrigImageDepth,
long *pBufferWidth, long *pBufferHeight, long *pBufferDepth)
{
unsigned char * pImageBuffer = NULL;
GWorldPtr pGWorld = NULL;
OSType pixelFormat;
long rowStride; // length, in bytes, of a pixel row in the image
GraphicsImportComponent giComp; // componenet for importing image
Rect rectImage; // rectangle of source image
ImageDescriptionHandle hImageDesc; // handle to image description used to get image depth
MatrixRecord matrix;
GDHandle origDevice; // save field for current graphics device
CGrafPtr origPort; // save field for current graphics port
OSStatus err = noErr; // err return value
// zero output params
*pOrigImageWidth = 0;
*pOrigImageHeight = 0;
*pOrigImageDepth = 0;
*pBufferWidth = 0;
*pBufferHeight = 0;
*pBufferDepth = 0;
// get imorter for the image tyoe in file
GetGraphicsImporterForFile (&fsspecImage, &giComp);
if (err != noErr) { // if we have an error
sprintf ( errMess, "couldnt find importer\n");
return NULL; // go away
}
// Create GWorld
err = GraphicsImportGetNaturalBounds (giComp, &rectImage); // get the image bounds
if (err != noErr) {
sprintf ( errMess, "failed to GraphicsImportGetNaturalBounds");
return NULL; // go away if error
}
// create a handle for the image description
hImageDesc = (ImageDescriptionHandle) NewHandle (sizeof (ImageDescriptionHandle));
HLock ((Handle) hImageDesc); // lock said handle
err = GraphicsImportGetImageDescription (giComp, &hImageDesc); // retrieve the image description
if (err != noErr) {
sprintf ( errMess, "failed to GraphicsImportGetImageDescription");
return NULL; // go away if error
}
*pOrigImageWidth = (long) (rectImage.right - rectImage.left); // find width from right side - left side bounds
*pOrigImageHeight = (long) (rectImage.bottom - rectImage.top); // same for height
// we will use a 24-bit rgb texture or a 32-bit rgba
if ((**hImageDesc).depth == 32) *pOrigImageDepth=4;
else *pOrigImageDepth=3;
*pBufferDepth = 32; // we will use a 32 bbit texture (this includes 24 bit images)
pixelFormat = k32ARGBPixelFormat;
bool doScaling = false;
if (doScaling)
{
int scalefac;
// note - we want texels to stay square, so
if ((*pOrigImageWidth) > (*pOrigImageHeight))
{
*pBufferWidth = GetScaledTextureDimFromImageDim ( *pOrigImageWidth, imageScale ) ;
*pBufferHeight=*pBufferWidth;
scalefac = X2Fix ((float) (*pBufferWidth) / (float) *pOrigImageWidth);
}
else
{
*pBufferHeight = GetScaledTextureDimFromImageDim (*pOrigImageHeight, imageScale );
*pBufferWidth = *pBufferHeight;
scalefac = X2Fix ((float) (*pBufferHeight) / (float) *pOrigImageHeight);
}
}
else
{
// NOTE: scaling of the image removed, this is already done inside osg::Image
*pBufferWidth = *pOrigImageWidth;
*pBufferHeight= *pOrigImageHeight;
}
SetRect (&rectImage, 0, 0, (short) *pBufferWidth, (short) *pBufferHeight); // l, t, r. b set image rectangle for creation of GWorld
rowStride = *pBufferWidth * *pBufferDepth >> 3; // set stride in bytes width of image * pixel depth in bytes
const long len = rowStride * *pBufferHeight;
pImageBuffer = new unsigned char [ len ]; // build new buffer exact size of image (stride * height)
// pImageBuffer = (unsigned char *) NewPtrClear (rowStride * *pBufferHeight); // build new buffer exact size of image (stride * height)
if (NULL == pImageBuffer)
{
sprintf ( errMess, "failed to allocate image buffer");
CloseComponent(giComp); // dump component
return NULL; // if we failed to allocate buffer
}
// create a new gworld using our unpadded buffer, ensure we set the pixel type correctly for the expected image bpp
QTNewGWorldFromPtr (&pGWorld, pixelFormat, &rectImage, NULL, NULL, 0, pImageBuffer, rowStride);
if (NULL == pGWorld)
{
sprintf ( errMess, "failed to create GWorld");
// DisposePtr ((Ptr) pImageBuffer); // dump image buffer
delete [] pImageBuffer;
pImageBuffer = NULL;
CloseComponent(giComp);
return NULL; // if we failed to create gworld
}
GetGWorld (&origPort, &origDevice); // save onscreen graphics port
// decompress (draw) to gworld and thus fill buffer
SetIdentityMatrix (&matrix); // set transform matrix to identity (basically pass through)
TranslateMatrix ( &matrix, -X2Fix(0.5f * *pOrigImageWidth), -X2Fix(0.5f * *pOrigImageHeight));
ScaleMatrix (&matrix, X2Fix(1.0), X2Fix(-1.0), X2Fix (0.0), X2Fix (0.0));
TranslateMatrix ( &matrix, X2Fix(0.5f * *pOrigImageWidth), X2Fix(0.5f * *pOrigImageHeight));
err = GraphicsImportSetMatrix(giComp, &matrix); // set our matrix as the importer matrix
if (err == noErr)
err = GraphicsImportSetGWorld (giComp, pGWorld, NULL); // set the destination of the importer component
if (err == noErr)
err = GraphicsImportSetQuality (giComp, codecLosslessQuality); // we want lossless decompression
if ((err == noErr) && GetGWorldPixMap (pGWorld) && LockPixels (GetGWorldPixMap (pGWorld)))
GraphicsImportDraw (giComp); // if everything looks good draw image to locked pixmap
else
{
sprintf ( errMess, "failed to Set Matrix or GWorld or Quality or GetPixMap");
DisposeGWorld (pGWorld); // dump gworld
pGWorld = NULL;
// DisposePtr ((Ptr) pImageBuffer); // dump image buffer
delete [] pImageBuffer;
pImageBuffer = NULL;
CloseComponent(giComp); // dump component
return NULL;
}
UnlockPixels (GetGWorldPixMap (pGWorld)); // unlock pixels
CloseComponent(giComp); // dump component
SetGWorld(origPort, origDevice); // set current graphics port to offscreen
// done with gworld and image since they are loaded to a texture
// DisposeGWorld (pGWorld); // do not need gworld
// pGWorld = NULL;
return pImageBuffer;
}
// new implementation of darwinPathToFSSpec
// the old code fails for me under os x 10.1.5
// the code below is from an example of apple, modified to fit our needs
// the example can be found at
// <http://developer.apple.com/samplecode/Sample_Code/Files/MoreFilesX.htm>
FSSpec *darwinPathToFSSpec (char *fname ) {
FSSpec *fs;
OSStatus result;
#if defined( __APPLE__ )
FSRef ref;
result = FSPathMakeRef( (UInt8*)fname, &ref, false); // fname is not a directory
if (result!=0) return NULL;
/* and then convert the FSRef to an FSSpec */
fs = (FSSpec *) malloc(sizeof(FSSpec));
result = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, fs, NULL);
if (result==0) return fs; // success
// failed:
free(fs);
return NULL;
#else
// windows implementation to get a fsspec
fs = (FSSpec *) malloc(sizeof(FSSpec));
result = NativePathNameToFSSpec(fname, fs, 0 /* flags */);
if (0 == result)
return fs;
free(fs);
return NULL;
#endif
}
unsigned char*
LoadBufferFromDarwinPath ( const char *fname, long *origWidth, long *origHeight, long *origDepth,
long *buffWidth, long *buffHeight,
long *buffDepth)
{
FSSpec *fs;
fs=darwinPathToFSSpec ( const_cast<char*>( fname ) );
if (fs == NULL) {
sprintf ( errMess, "error creating path from fsspec" );
return NULL;
}
else
return LoadBufferFromImageFile ( *fs, kNone, origWidth,origHeight,origDepth,buffWidth,buffHeight,buffDepth);
}

View File

@@ -1,19 +0,0 @@
#ifndef __QTTEXTURE_H__
#define __QTTEXTURE_H__
#ifdef __cplusplus
extern "C" {
#endif
unsigned char* LoadBufferFromDarwinPath ( const char *fname, long *origWidth,
long *origHeight, long *origDepth,
long *buffWidth, long *buffHeight, long *buffDepth);
char* QTfailureMessage(void);
FSSpec *darwinPathToFSSpec (char *fname );
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -14,6 +14,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#ifndef __APPLE__
#include "Components.h"
@@ -27,7 +29,7 @@
#endif
#include "QTUtils.h"
#include "QTLiveUtils.h"
#include "QTtexture.h"
#include "QTImportExport.h"
#include "QuicktimeImageStream.h"
#include "QuicktimeLiveImageStream.h"
@@ -169,7 +171,7 @@ public:
osg::notify(osg::ALWAYS) << std::endl;
osg::notify(osg::ALWAYS) << "Video Component/Input IDs follow: " << std::endl;
osg::notify(osg::ALWAYS) << std::endl;
for (int device_input = 0; device_input < video_device_list.size(); ++device_input)
for (unsigned int device_input = 0; device_input < video_device_list.size(); ++device_input)
{
OSG_SGDevicePair device_pair = video_device_list[device_input];
osg::notify(osg::ALWAYS) << device_pair.first.c_str() << " " << device_pair.second.c_str() << std::endl;
@@ -183,7 +185,7 @@ public:
osg::notify(osg::ALWAYS) << std::endl;
osg::notify(osg::ALWAYS) << "Audio Component/Input IDs follow: " << std::endl;
osg::notify(osg::ALWAYS) << std::endl;
for (int device_input = 0; device_input < audio_device_list.size(); ++device_input)
for (unsigned int device_input = 0; device_input < audio_device_list.size(); ++device_input)
{
OSG_SGDevicePair device_pair = audio_device_list[device_input];
osg::notify(osg::ALWAYS) << device_pair.first.c_str() << " " << device_pair.second.c_str() << std::endl;
@@ -246,332 +248,132 @@ public:
return moov;
}
long origWidth, origHeight,buffWidth,buffHeight,buffDepth,origDepth;
QuicktimeImportExport importer;
// NOTE - implememntation means that this will always return 32 bits, so it is hard to work out if
// an image was monochrome. So it will waste texture memory unless it gets a monochrome hint.
std::ifstream is;
is.open (fileName.c_str(), std::ios::binary | std::ios::in );
is.seekg (0, std::ios::end);
long length = is.tellg();
is.seekg (0, std::ios::beg);
unsigned char *pixels = LoadBufferFromDarwinPath ( fileName.c_str(), &origWidth,&origHeight,&origDepth,
&buffWidth,&buffHeight,
&buffDepth);
osg::ref_ptr<osg::Image> image = importer.readFromStream(is, fileName, length);
is.close();
if (!importer.success() || (image == NULL)) {
osg::notify(osg::WARN) << "Error reading file " << file << " : " << importer.getLastErrorString() << std::endl;
return ReadResult::ERROR_IN_READING_FILE;
}
// IMPORTANT -
// origDepth in BYTES, buffDepth in BITS
if (pixels == 0)
{
osg::notify(osg::WARN) << "LoadBufferFromDarwinPath failed " << fileName.c_str() << QTfailureMessage() << std::endl;
return 0;
}
_qtExitObserver.addMedia(image.get());
unsigned int pixelFormat;
switch(origDepth)
{
case 1 :
pixelFormat = GL_RGB;
break;
case 2 :
pixelFormat = GL_LUMINANCE_ALPHA;
break;
case 3 :
pixelFormat = GL_RGB;
break;
case 4 :
pixelFormat = GL_RGBA;
break;
default :
osg::notify(osg::WARN) << "Unknown file type in " << fileName.c_str() << " with " << origDepth << std::endl;
pixelFormat = (GLenum)-1;
return 0;
break;
}
{
unsigned char *srcp=pixels, *dstp=pixels;
int i, j;
// swizzle entire image in-place
unsigned char r, g, b, a;
for (i=0; i<buffHeight; i++ ) {
switch (origDepth)
return image.release();
}
virtual ReadResult readImage (std::istream& is, const osgDB::ReaderWriter::Options* options=NULL) const
{
std::string filename = "";
long sizeHint(0);
// check options for a file-type-hint
if (options) {
std::istringstream iss(options->getOptionString());
std::string opt;
while (iss >> opt)
{
/*
since 8-bit tgas will get expanded into colour, have to use RGB code for 8-bit images
case 1 :
for (j=0; j<buffWidth; j++ ) {
dstp[0]=srcp[2];
srcp+=4;
dstp++;
}
break;
*/
case 2 :
for (j=0; j<buffWidth; j++ ) {
dstp[1]=srcp[0];
dstp[0]=srcp[2];
srcp+=4;
dstp+=2;
}
break;
case 1 :
case 3 :
for (j=0; j<buffWidth; j++ ) {
dstp[0]=srcp[1];
dstp[1]=srcp[2];
dstp[2]=srcp[3];
srcp+=4;
dstp+=3;
}
break;
case 4 :
for (j=0; j<buffWidth; j++ ) {
r=srcp[1];
g=srcp[2];
b=srcp[3];
a=srcp[0];
dstp[0]=r;
dstp[1]=g;
dstp[2]=b;
dstp[3]=a;
srcp+=4;
dstp+=4;
}
break;
default :
// osg::notify(osg::WARN) << "ERROR IN RETURNED PIXEL DEPTH, CANNOT COPE" << std::endl;
return 0;
break;
int index = opt.find( "=" );
if( opt.substr( 0, index ) == "filename" ||
opt.substr( 0, index ) == "FILENAME" )
{
filename = opt.substr( index+1 );
} else if( opt.substr( 0, index ) == "size" ||
opt.substr( 0, index ) == "SIZE" )
{
std::string sizestr = opt.substr( index+1 );
sizeHint = atol(sizestr.c_str());
}
}
}
}
}
QuicktimeImportExport importer;
osg::ref_ptr<osg::Image> image = importer.readFromStream(is, filename, sizeHint);
if (!importer.success() || (image == NULL)) {
osg::notify(osg::WARN) << "Error reading from stream " << importer.getLastErrorString() << std::endl;
return ReadResult::ERROR_IN_READING_FILE;
}
_qtExitObserver.addMedia(image.get());
return image.release();
}
virtual WriteResult writeImage(const osg::Image &img,const std::string& fileName, const osgDB::ReaderWriter::Options*) const
{
std::string ext = osgDB::getFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
initQuicktime();
Image* image = new Image();
image->setFileName(fileName.c_str());
image->setImage(buffWidth,buffHeight,1,
buffDepth >> 3,
pixelFormat,
GL_UNSIGNED_BYTE,
pixels,
osg::Image::USE_NEW_DELETE );
//Buidl map of extension <-> osFileTypes
std::map<std::string, OSType> extmap;
notify(DEBUG_INFO) << "image read ok "<<buffWidth<<" "<<buffHeight<<std::endl;
extmap.insert(std::pair<std::string, OSType>("jpg", kQTFileTypeJPEG));
extmap.insert(std::pair<std::string, OSType>("jpeg", kQTFileTypeJPEG));
extmap.insert(std::pair<std::string, OSType>("bmp", kQTFileTypeBMP));
extmap.insert(std::pair<std::string, OSType>("tif", kQTFileTypeTIFF));
extmap.insert(std::pair<std::string, OSType>("tiff", kQTFileTypeTIFF));
extmap.insert(std::pair<std::string, OSType>("png", kQTFileTypePNG));
extmap.insert(std::pair<std::string, OSType>("gif", kQTFileTypeGIF));
extmap.insert(std::pair<std::string, OSType>("psd", kQTFileTypePhotoShop));
extmap.insert(std::pair<std::string, OSType>("sgi", kQTFileTypeSGIImage));
extmap.insert(std::pair<std::string, OSType>("rgb", kQTFileTypeSGIImage));
extmap.insert(std::pair<std::string, OSType>("rgba", kQTFileTypeSGIImage));
// add the media to the observer for proper clean up on exit
_qtExitObserver.addMedia(image);
std::map<std::string, OSType>::iterator cur = extmap.find(ext);
return image;
}
virtual WriteResult writeImage(const osg::Image &img,const std::string& fileName, const osgDB::ReaderWriter::Options*) const
{
std::string ext = osgDB::getFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
initQuicktime();
//Buidl map of extension <-> osFileTypes
std::map<std::string, OSType> extmap;
extmap.insert(std::pair<std::string, OSType>("jpg", kQTFileTypeJPEG));
extmap.insert(std::pair<std::string, OSType>("jpeg", kQTFileTypeJPEG));
extmap.insert(std::pair<std::string, OSType>("bmp", kQTFileTypeBMP));
extmap.insert(std::pair<std::string, OSType>("tif", kQTFileTypeTIFF));
extmap.insert(std::pair<std::string, OSType>("tiff", kQTFileTypeTIFF));
extmap.insert(std::pair<std::string, OSType>("png", kQTFileTypePNG));
extmap.insert(std::pair<std::string, OSType>("gif", kQTFileTypeGIF));
extmap.insert(std::pair<std::string, OSType>("psd", kQTFileTypePhotoShop));
// extmap.insert(std::pair<std::string, OSType>("tga", kQTFileTypeTargaImage));
extmap.insert(std::pair<std::string, OSType>("sgi", kQTFileTypeSGIImage));
extmap.insert(std::pair<std::string, OSType>("rgb", kQTFileTypeSGIImage));
extmap.insert(std::pair<std::string, OSType>("rgba", kQTFileTypeSGIImage));
std::map<std::string, OSType>::iterator cur = extmap.find(ext);
// can not handle this type of file, perhaps a movie?
if (cur == extmap.end())
// can not handle this type of file, perhaps a movie?
if (cur == extmap.end())
return WriteResult::FILE_NOT_HANDLED;
OSType desiredType = cur->second;
GraphicsExportComponent geComp = NULL;
std::ofstream os(fileName.c_str(), std::ios::binary | std::ios::trunc | std::ios::out);
if(os.good())
{
QuicktimeImportExport exporter;
exporter.writeToStream(os, const_cast<osg::Image*>(&img), fileName);
if (exporter.success())
return WriteResult::FILE_SAVED;
}
OSErr err = OpenADefaultComponent(GraphicsExporterComponentType, desiredType, &geComp);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not open Graphics epxorter for type " << ext << ", Err: " << err << std::endl;
return WriteResult::FILE_NOT_HANDLED;
}
GWorldPtr gw = NULL;
// we are converting the images back to 32bit, it seems, that quicktime can't handle others
unsigned long desiredPixelFormat = k32ARGBPixelFormat;
// we need to swizzle the colours again :)
unsigned int numBytes = img.computeNumComponents(img.getPixelFormat());
unsigned int buffWidth = img.s();
unsigned int buffHeight = img.t();
char * pixels = (char*) malloc(buffHeight * buffWidth * 4);
const unsigned char *srcp = img.data();
char *dstp=pixels;
unsigned int i, j;
for (i=0; i<buffHeight; i++ ) {
switch (numBytes) {
case 1 :
dstp[0] = 0;
dstp[1] = srcp[0];
dstp[2] = srcp[0];
dstp[3] = srcp[0];
srcp+=1;
dstp+=4;
break;
case 3 :
for (j=0; j<buffWidth; j++ ) {
dstp[0]=0;
dstp[1]=srcp[0];
dstp[2]=srcp[1];
dstp[3]=srcp[2];
srcp+=3;
dstp+=4;
}
break;
case 4 :
for (j=0; j<buffWidth; j++ ) {
// shift from RGBA to ARGB
dstp[0]=srcp[3];
dstp[1]=srcp[0];
dstp[2]=srcp[1];
dstp[3]=srcp[2];
srcp+=4;
dstp+=4;
}
break;
default :
// osg::notify(osg::WARN) << "ERROR IN RETURNED PIXEL DEPTH, CANNOT COPE" << std::endl;
return WriteResult::ERROR_IN_WRITING_FILE;
break;
}
}
// Flip the image
unsigned imageSize = buffWidth*buffHeight*4;
char *tBuffer = (char*)malloc((size_t)imageSize);
unsigned int rowBytes = buffWidth * 4;
for (i = 0, j = imageSize - rowBytes; i < imageSize; i += rowBytes, j -= rowBytes)
memcpy( &tBuffer[j], &pixels[i], (size_t)rowBytes );
memcpy(pixels, tBuffer, (size_t)imageSize);
free(tBuffer);
FSSpec* fileSpec = NULL;
try {
Rect bounds;
SetRect(&bounds, 0,0, img.s(), img.t());
err = QTNewGWorldFromPtr(&gw, desiredPixelFormat, &bounds, 0,0,0, pixels, buffWidth*4);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not create gworld for type " << ext << ", Err: " << err << std::endl;
throw err;
}
// create a dummy file at location
FILE *fp = fopen(fileName.c_str(), "wb");
if (!fp) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not create file!" << std::endl;
throw err;
}
fclose(fp);
// get an FSSpec to the file, so quicktime can handle the file.
fileSpec = darwinPathToFSSpec( const_cast<char*>(fileName.c_str()) );
if (fileSpec == NULL) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not get FSSpec" << std::endl;
throw err;
}
err = GraphicsExportSetInputGWorld(geComp, gw);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not set input gworld for type " << ext << ", Err: " << err << std::endl;
throw err;
}
err = GraphicsExportSetOutputFile(geComp, fileSpec);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not set output file for type " << ext << ", Err: " << err << std::endl;
throw err;
}
// Set the compression quality (needed for JPEG, not necessarily for other formats)
if (desiredType == kQTFileTypeJPEG) {
err = GraphicsExportSetCompressionQuality(geComp, codecLosslessQuality);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not set compression for type " << ext << ", Err: " << err << std::endl;
throw err;
return WriteResult::ERROR_IN_WRITING_FILE;
}
virtual WriteResult writeImage (const osg::Image& img, std::ostream& os, const Options* options=NULL) const
{
std::string filename = "file.jpg"; // use jpeg if not otherwise specified
if (options) {
std::istringstream iss(options->getOptionString());
std::string opt;
while (iss >> opt)
{
int index = opt.find( "=" );
if( opt.substr( 0, index ) == "filename" ||
opt.substr( 0, index ) == "FILENAME" )
{
filename = opt.substr( index+1 );
}
}
}
if(32 == numBytes)
{
err = GraphicsExportSetDepth( geComp,
k32ARGBPixelFormat ); // depth
}
// else k24RGBPixelFormat???
// do the export
err = GraphicsExportDoExport(geComp, NULL);
if (err != noErr) {
osg::notify(osg::WARN) << "ReaderWriterQT: could not save file for type " << ext << ", Err: " << err << std::endl;
throw err;
}
if (geComp != NULL)
CloseComponent(geComp);
DisposeGWorld (gw);
if (fileSpec != NULL ) free(fileSpec);
if (pixels) free(pixels);
return WriteResult::FILE_SAVED;
}
}
QuicktimeImportExport exporter;
exporter.writeToStream(os, const_cast<osg::Image*>(&img), filename);
if (exporter.success())
return WriteResult::FILE_SAVED;
return WriteResult::ERROR_IN_WRITING_FILE;
}
catch (...) {
if (geComp != NULL) CloseComponent(geComp);
if (gw != NULL) DisposeGWorld (gw);
if (fileSpec != NULL ) free(fileSpec);
if (pixels) free(pixels);
return WriteResult::ERROR_IN_WRITING_FILE;
}
}
mutable QuicktimeExitObserver _qtExitObserver;
mutable QuicktimeExitObserver _qtExitObserver;
};
// now register with Registry to instantiate the above