Files
OpenSceneGraph/src/osgPlugins/quicktime/ReaderWriterQT.cpp
Robert Osfield 35ec6cee40 From Riccardo Corsi, "this should be the final version of the qt-plugin ported to windows and cleaned up to avoid crashes on exit.
Stephan and I tested it on Mac and Win.
Just a couple of notes:

    * the plugin is able to load both regular 2D images and movie stream. There is a #define QT_HANDLE_IMAGES_ALSO mechanism to indicate whether to use it for images or not. This is defined by default on Mac only, to avoid conflicts with the standard windows plugins. See comments on QTUtils.h

    * de-initialization of quicktime before exit is now left to an observer, which calls exitQuicktime() when last media loaded with the plugin is released. This prevent a crash on exit without need of any extra call external to the plugin."
2007-01-12 22:07:33 +00:00

463 lines
15 KiB
C++

#include "osg/Image"
#include "osg/Notify"
#include <osg/Geode>
#include <osg/observer_ptr>
#include "osg/GL"
#include "osgDB/FileNameUtils"
#include "osgDB/Registry"
#include "osgDB/FileUtils"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef SEEK_SET
# define SEEK_SET 0
#endif
#include "QTUtils.h"
#include "QTtexture.h"
#include "QuicktimeImageStream.h"
using namespace osg;
// This class is used as a helper to de-initialize
// properly quicktime, when the last media loaded
// with the quicktime plugin is released.
// All loaded media must be added to the observer
// (see ReaderWriterQT::readImage() function)
class QuicktimeExitObserver : public osg::Observer
{
public:
QuicktimeExitObserver () : _instanceCount(0){}
virtual ~QuicktimeExitObserver(){};
void addMedia(Image* ptr)
{
ptr->addObserver(this);
++ _instanceCount;
}
virtual void objectDeleted(void*)
{
-- _instanceCount;
if(_instanceCount== 0)
exitQuicktime();
}
private:
unsigned int _instanceCount;
};
class ReaderWriterQT : public osgDB::ReaderWriter
{
public:
virtual const char* className() const { return "Default Quicktime Image Reader/Writer"; }
virtual bool acceptsMovieExtension(const std::string& extension) const
{
return osgDB::equalCaseInsensitive(extension,"mov") ||
osgDB::equalCaseInsensitive(extension,"mpg") ||
osgDB::equalCaseInsensitive(extension,"mpv") ||
osgDB::equalCaseInsensitive(extension,"mp4") ||
osgDB::equalCaseInsensitive(extension,"m4v") ||
osgDB::equalCaseInsensitive(extension,"dv") ||
osgDB::equalCaseInsensitive(extension,"avi") ||
osgDB::equalCaseInsensitive(extension,"swf");
}
virtual bool acceptsExtension(const std::string& extension) const
{
// this should be the only image importer required on the Mac
// dont know what else it supports, but these will do
return
#ifdef QT_HANDLE_IMAGES_ALSO
osgDB::equalCaseInsensitive(extension,"rgb") ||
osgDB::equalCaseInsensitive(extension,"rgba") ||
osgDB::equalCaseInsensitive(extension,"jpg") ||
osgDB::equalCaseInsensitive(extension,"jpeg") ||
osgDB::equalCaseInsensitive(extension,"tif") ||
osgDB::equalCaseInsensitive(extension,"tiff") ||
osgDB::equalCaseInsensitive(extension,"gif") ||
osgDB::equalCaseInsensitive(extension,"png") ||
osgDB::equalCaseInsensitive(extension,"pict") ||
osgDB::equalCaseInsensitive(extension,"pct") ||
osgDB::equalCaseInsensitive(extension,"tga") ||
osgDB::equalCaseInsensitive(extension,"psd") ||
#endif
acceptsMovieExtension(extension);
}
virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
std::string ext = osgDB::getLowerCaseFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
std::string fileName = osgDB::findDataFile( file, options);
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
// Note from Riccardo Corsi
// Quicktime initialization is done here, when a media is found
// and before any image or movie is loaded.
// After the first call the function does nothing.
// The cleaning up is left to the QuicktimeExitObserver (see below)
initQuicktime();
// if the file is a movie file then load as an ImageStream.
if (acceptsMovieExtension(ext))
{
// note from Robert Osfield when integrating, we should probably have so
// error handling mechanism here. Possibly move the load from
// the constructor to a seperate load method, and have a valid
// state on the ImageStream... will integrated as is right now
// to get things off the ground.
QuicktimeImageStream* moov = new QuicktimeImageStream(fileName);
// moov->play();
// add the media to the observer for proper clean up on exit
_qtExitObserver.addMedia(moov);
return moov;
}
long origWidth, origHeight,buffWidth,buffHeight,buffDepth,origDepth;
// 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.
unsigned char *pixels = LoadBufferFromDarwinPath ( fileName.c_str(), &origWidth,&origHeight,&origDepth,
&buffWidth,&buffHeight,
&buffDepth);
// IMPORTANT -
// origDepth in BYTES, buffDepth in BITS
if (pixels == 0)
{
osg::notify(osg::WARN) << "LoadBufferFromDarwinPath failed " << fileName.c_str() << QTfailureMessage() << std::endl;
return 0;
}
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)
{
/*
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;
}
}
}
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 );
notify(DEBUG_INFO) << "image read ok "<<buffWidth<<" "<<buffHeight<<std::endl;
// add the media to the observer for proper clean up on exit
_qtExitObserver.addMedia(image);
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())
return WriteResult::FILE_NOT_HANDLED;
OSType desiredType = cur->second;
GraphicsExportComponent geComp = NULL;
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;
}
}
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;
}
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;
};
// now register with Registry to instantiate the above
// reader/writer.
osgDB::RegisterReaderWriterProxy<ReaderWriterQT> g_readerWriter_QT_Proxy;