528 lines
16 KiB
C++
528 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("")
|
|
{
|
|
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) {
|
|
//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);
|
|
|
|
}
|
|
|
|
}
|
|
|