/* * QTImportExport.cpp * cefix * * Created by Stephan Huber on 07.02.08. * Copyright 2008 __MyCompanyName__. All rights reserved. * */ #include #include #include "QTImportExport.h" #include "QTUtils.h" #include /** 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(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(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(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(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 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::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); } }