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."
463 lines
15 KiB
C++
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;
|