c385e11aff
It basically removes the static init() and exit() functions,and move them inside the observer class (the one that cleans everything up when the last media is unloaded). It also add an extra check to clean up on exit if the QuickTime env is initialized, but no media is succesfully loaded / written (it might happens with streaming resources). I tested it under WinXP with zero, one and multiple videos. Stephan reads in copy: could you kindly check if everything runs smooth under OSX as well? Also, have you got a chance to test it with streaming media? "
527 lines
16 KiB
C++
527 lines
16 KiB
C++
/*
|
|
* 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("")
|
|
{
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------------------------------------
|
|
// 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) {
|
|
//hmm...not good, pascal string limit is 255!
|
|
//do some error handling maybe?!
|
|
throw QTImportExportException(0, "filename length limit exceeded");
|
|
}
|
|
CopyCStringToPascal(filename.c_str(), pstr);
|
|
|
|
// 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 = 0;
|
|
|
|
// 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 bytesPerPixel = depth / 8;
|
|
unsigned int glpixelFormat;
|
|
switch(bytesPerPixel) {
|
|
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, bytesPerPixel, xsize, ysize);
|
|
|
|
delete[] imageData;
|
|
|
|
osg::Image* image = new osg::Image();
|
|
image->setFileName(fileTypeHint.c_str());
|
|
image->setImage(xsize,ysize,1,
|
|
bytesPerPixel,
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|