Files
OpenSceneGraph/src/osgPlugins/imageio/ReaderWriterImageIO_IOS.cpp
Robert Osfield c0d2ccc017 From Philip Lamp, Merged from svn/trunk revision 12683. "Modify the ReaderWriteImageIO_IOS to correctly handle relative paths
when reading from a file. This means that no longer do all image files
have to live at the root of the Resources folder inside the app bundle,
but can now be organized inside folders. Also improved error handling
and fixed a potential leak of a CGColorSpaceRef each once per image load."
2011-07-15 08:37:15 +00:00

573 lines
21 KiB
C++

#include <osg/GL>
#include <osg/Notify>
#include <osg/Image>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <sstream> // for istream
#include <iostream> // for ios::
#import <UIKit/UIImage.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <ImageIO/CGImageSource.h>
/**************************************************************
***** Begin Callback functions for istream block reading *****
**************************************************************/
// This callback reads some bytes from an istream and copies it
// to a Quartz buffer (supplied by Apple framework).
size_t MyProviderGetBytesCallback(void* istream_userdata, void* quartz_buffer, size_t the_count)
{
std::istream* the_istream = (std::istream*)istream_userdata;
the_istream->read((char*)quartz_buffer, the_count);
return the_istream->gcount(); // return the actual number of bytes read
}
// This callback is triggered when the data provider is released
// so you can clean up any resources.
void MyProviderReleaseInfoCallback(void* istream_userdata)
{
// What should I put here? Do I need to close the istream?
// The png and tga don't seem to.
// std::istream* the_istream = (std::istream*)istream_userdata;
}
void MyProviderRewindCallback(void* istream_userdata)
{
std::istream* the_istream = (std::istream*)istream_userdata;
the_istream->seekg(0, std::ios::beg);
}
off_t MyProviderSkipForwardBytesCallback(void* istream_userdata, off_t the_count)
{
std::istream* the_istream = (std::istream*)istream_userdata;
off_t start_position = the_istream->tellg();
the_istream->seekg(the_count, std::ios::cur);
off_t end_position = the_istream->tellg();
return (end_position - start_position);
}
/**************************************************************
***** End Callback functions for istream block reading ********
**************************************************************/
/**************************************************************
***** Begin Callback functions for ostream block writing ******
**************************************************************/
size_t MyConsumerPutBytesCallback(void* ostream_userdata, const void* quartz_buffer, size_t the_count)
{
std::ostream* the_ostream = (std::ostream*)ostream_userdata;
the_ostream->write((char*)quartz_buffer, the_count);
// Don't know how to get number of bytes actually written, so
// just returning the_count.
return the_count;
}
void MyConsumerReleaseInfoCallback(void* ostream_userdata)
{
std::ostream* the_ostream = (std::ostream*)ostream_userdata;
the_ostream->flush();
}
/**************************************************************
***** End Callback functions for ostream block writing ********
**************************************************************/
/**************************************************************
***** Begin Support functions for reading (stream and file) ***
**************************************************************/
/* Create a CGImageSourceRef from raw data */
CGImageRef CreateCGImageFromDataStream(std::istream& fin)
{
CGImageRef image_ref = NULL;
CGImageSourceRef source_ref;
/* The easy way would be to use CGImageSourceCreateWithData,
* but this presumes you have a known fixed-length buffer of data.
* The istream makes this harder to know, so we use the ProviderCallbacks APIs
CFDataRef the_cf_data = CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault,
(const UInt8*)the_data,
CFIndex length,
kCFAllocatorNull // do not free data buffer, must do it yourself
);
source_ref = CGImageSourceCreateWithData(the_cf_data, NULL);
*/
CGDataProviderSequentialCallbacks provider_callbacks =
{
0,
MyProviderGetBytesCallback,
MyProviderSkipForwardBytesCallback,
MyProviderRewindCallback,
MyProviderReleaseInfoCallback
};
CGDataProviderRef data_provider = CGDataProviderCreateSequential(&fin, &provider_callbacks);
// If we had a way of hinting at what the data type is, we could
// pass this hint in the second parameter.
source_ref = CGImageSourceCreateWithDataProvider(data_provider, NULL);
CGDataProviderRelease(data_provider);
if(!source_ref)
{
return NULL;
}
image_ref = CGImageSourceCreateImageAtIndex(source_ref, 0, NULL);
/* Don't need the SourceRef any more (error or not) */
CFRelease(source_ref);
return image_ref;
}
static NSString* toNSString(const std::string& text, NSStringEncoding nsse)
{
NSString* nstr = nil;
if (!text.empty())
{
nstr = [NSString stringWithCString:text.c_str() encoding:nsse];
//nstr = [NSString stringWithUTF8String:text.c_str()];// encoding:nsse]
}
if (nstr == nil)
{
nstr = @"";
}
return nstr;
}
// std::string to NSString with the UTF8 encoding
static NSString* toNSString(const std::string& text)
{
return toNSString(text, NSUTF8StringEncoding);
}
//
//really basic image io for IOS
//
osg::Image* ReadCoreGraphicsImageFromFile(std::string file)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//chop the extension off
//std::string strExt = osgDB::getFileExtension(file);
//std::string strPath = osgDB::getFilePath(file);
//std::string strName = osgDB::getStrippedName(file);
//std::string strFile = strPath + "/" + strName;
//NSString* path = [NSString stringWithCString:strName.c_str() encoding:NSUTF8StringEncoding];
//NSString* ext = [NSString stringWithCString:strExt.c_str() encoding:NSUTF8StringEncoding];
//CGImageRef textureImage = [UIImage imageNamed:path].CGImage;
//CGImageRef textureImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:ext]].CGImage;
NSString* path = [NSString stringWithCString:file.c_str() encoding:NSUTF8StringEncoding];
//NSLog(@"imageio: About to open %@.\n", path);
UIImage *img = [UIImage imageWithContentsOfFile:path];
if (!img) {
NSLog(@"imageio: failed to load UIImage image '%@'.\n", path);
[pool release];
return NULL;
}
CGImageRef textureImage = img.CGImage;
if (!textureImage) {
NSLog(@"imageio: failed to create CGImageRef.\n");
[pool release];
return NULL;
}
size_t texWidth = CGImageGetWidth(textureImage);
size_t texHeight = CGImageGetHeight(textureImage);
GLubyte *textureData = (GLubyte *)malloc(texWidth * texHeight * 4);
if (!textureData) {
NSLog(@"imageio: out of memory.\n");
[pool release];
return NULL;
}
CGColorSpaceRef csref = CGColorSpaceCreateDeviceRGB();
if (!csref) {
NSLog(@"imageio: failed to create CGColorSpaceRef.\n");
free(textureData);
[pool release];
return NULL;
}
CGContextRef textureContext = CGBitmapContextCreate(textureData,
texWidth, texHeight,
8, texWidth * 4,
csref,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(csref);
if (!textureContext) {
NSLog(@"imageio: failed to create CGContextRef.\n");
free(textureData);
[pool release];
return NULL;
}
//copy into texturedata
CGContextDrawImage(textureContext,
CGRectMake(0.0f, 0.0f, (float)texWidth, (float)texHeight),
textureImage);
CGContextRelease(textureContext);
//create the osg image
int s = texWidth;
int t = texHeight;
osg::Image* image = new osg::Image();
image->setImage(s, t, 1,
GL_RGBA,
GL_RGBA,
GL_UNSIGNED_BYTE,
textureData,
osg::Image::USE_MALLOC_FREE);
//flip vertical
image->flipVertical();
//
// Reverse the premultiplied alpha for avoiding unexpected darker edges
// by Tatsuhiro Nishioka (based on SDL's workaround on the similar issue)
// http://bugzilla.libsdl.org/show_bug.cgi?id=868
//
int i, j;
GLubyte *pixels = (GLubyte *)image->data();
for (i = image->t() * image->s(); i--; ) {
GLubyte alpha = pixels[3];
if (alpha && (alpha < 255)) {
for (j = 0; j < 3; ++j) {
pixels[j] = (static_cast<int>(pixels[j]) * 255) / alpha;
}
}
pixels += 4;
}
[pool release];
return image;
}
osg::Image* CreateOSGImageFromCGImage(CGImageRef textureImage)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
if (textureImage == nil) {
[pool release];
NSLog(@"imageio: failed to load CGImageRef image");
return NULL;
}
size_t texWidth = CGImageGetWidth(textureImage);
size_t texHeight = CGImageGetHeight(textureImage);
GLubyte *textureData = (GLubyte *)malloc(texWidth * texHeight * 4);
if (!textureData) {
NSLog(@"imageio: out of memory.\n");
[pool release];
return NULL;
}
CGColorSpaceRef csref = CGColorSpaceCreateDeviceRGB();
if (!csref) {
NSLog(@"imageio: failed to create CGColorSpaceRef.\n");
free(textureData);
[pool release];
return NULL;
}
CGContextRef textureContext = CGBitmapContextCreate(textureData,
texWidth, texHeight,
8, texWidth * 4,
csref,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(csref);
if (!textureContext) {
NSLog(@"imageio: failed to create CGContextRef.\n");
free(textureData);
[pool release];
return NULL;
}
//copy into texturedata
CGContextDrawImage(textureContext,
CGRectMake(0.0f, 0.0f, (float)texWidth, (float)texHeight),
textureImage);
CGContextFlush(textureContext);
CGContextRelease(textureContext);
//create the osg image
int s = texWidth;
int t = texHeight;
osg::Image* image = new osg::Image();
image->setImage(s, t, 1,
GL_RGBA,
GL_RGBA,
GL_UNSIGNED_BYTE,
textureData,
osg::Image::USE_MALLOC_FREE);
//flip vertical
image->flipVertical();
//
// Reverse the premultiplied alpha for avoiding unexpected darker edges
// by Tatsuhiro Nishioka (based on SDL's workaround on the similar issue)
// http://bugzilla.libsdl.org/show_bug.cgi?id=868
//
int i, j;
GLubyte *pixels = (GLubyte *)image->data();
for (i = image->t() * image->s(); i--; ) {
GLubyte alpha = pixels[3];
if (alpha && (alpha < 255)) {
for (j = 0; j < 3; ++j) {
pixels[j] = (static_cast<int>(pixels[j]) * 255) / alpha;
}
}
pixels += 4;
}
[pool release];
return image;
}
class ReaderWriterImageIO : public osgDB::ReaderWriter
{
public:
ReaderWriterImageIO()
{
supportsExtension("jpg", "jpg image file");
supportsExtension("jpeg", "jpeg image file");
supportsExtension("jpe", "jpe image file");
supportsExtension("jp2", "jp2 image file");
supportsExtension("tiff", "tiff image file");
supportsExtension("tif", "tif image file");
supportsExtension("gif", "gif image file");
supportsExtension("png", "png image file");
supportsExtension("pict", "pict image file");
supportsExtension("pct", "pct image file");
supportsExtension("pic", "pic image file");
supportsExtension("bmp", "bmp image file");
supportsExtension("BMPf", "BMPf image file");
supportsExtension("ico", "ico image file");
supportsExtension("icns", "icns image file");
supportsExtension("tga", "tga image file");
supportsExtension("targa", "targa image file");
supportsExtension("psd", "psd image file");
supportsExtension("pdf", "pdf image file");
supportsExtension("eps", "eps image file");
supportsExtension("epi", "epi image file");
supportsExtension("epsf", "epsf image file");
supportsExtension("epsi", "epsi image file");
supportsExtension("ps", "postscript image file");
supportsExtension("dng", "dng image file");
supportsExtension("cr2", "cr2 image file");
supportsExtension("crw", "crw image file");
supportsExtension("fpx", "fpx image file");
supportsExtension("fpxi", "fpxi image file");
supportsExtension("raf", "raf image file");
supportsExtension("dcr", "dcr image file");
supportsExtension("ptng", "ptng image file");
supportsExtension("pnt", "pnt image file");
supportsExtension("mac", "mac image file");
supportsExtension("mrw", "mrw image file");
supportsExtension("nef", "nef image file");
supportsExtension("orf", "orf image file");
supportsExtension("exr", "exr image file");
supportsExtension("qti", "qti image file");
supportsExtension("qtif", "qtif image file");
supportsExtension("hdr", "hdr image file");
supportsExtension("sgi", "sgi image file");
supportsExtension("srf", "srf image file");
supportsExtension("cur", "cur image file");
supportsExtension("xbm", "xbm image file");
supportsExtension("raw", "raw image file");
}
virtual const char* className() const { return "Mac OS X ImageIO based Image Reader/Writer"; }
virtual bool acceptsExtension(const std::string& extension) const
{
// ImageIO speaks in UTIs.
// http://developer.apple.com/graphicsimaging/workingwithimageio.html
// The Cocoa drawing guide lists these and says to use the
// imageFileTypes class method of NSImage to get a complete
// list of extensions. But remember ImageIO may support more formats
// than Cocoa.
// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Images/chapter_7_section_3.html
// Apple's UTI guide:
// http://developer.apple.com/documentation/Carbon/Conceptual/understanding_utis/utilist/chapter_4_section_1.html
return
osgDB::equalCaseInsensitive(extension,"jpg") ||
osgDB::equalCaseInsensitive(extension,"jpeg") ||
osgDB::equalCaseInsensitive(extension,"jpe") ||
osgDB::equalCaseInsensitive(extension,"jp2") ||
osgDB::equalCaseInsensitive(extension,"tiff") ||
osgDB::equalCaseInsensitive(extension,"tif") ||
osgDB::equalCaseInsensitive(extension,"gif") ||
osgDB::equalCaseInsensitive(extension,"png") ||
osgDB::equalCaseInsensitive(extension,"pict") ||
osgDB::equalCaseInsensitive(extension,"pct") ||
osgDB::equalCaseInsensitive(extension,"pic") ||
osgDB::equalCaseInsensitive(extension,"bmp") ||
osgDB::equalCaseInsensitive(extension,"BMPf") ||
osgDB::equalCaseInsensitive(extension,"ico") ||
osgDB::equalCaseInsensitive(extension,"icns") ||
osgDB::equalCaseInsensitive(extension,"tga") ||
osgDB::equalCaseInsensitive(extension,"targa") ||
osgDB::equalCaseInsensitive(extension,"psd") ||
osgDB::equalCaseInsensitive(extension,"pdf") ||
osgDB::equalCaseInsensitive(extension,"eps") ||
osgDB::equalCaseInsensitive(extension,"epi") ||
osgDB::equalCaseInsensitive(extension,"epsf") ||
osgDB::equalCaseInsensitive(extension,"epsi") ||
osgDB::equalCaseInsensitive(extension,"ps") ||
osgDB::equalCaseInsensitive(extension,"dng") ||
osgDB::equalCaseInsensitive(extension,"cr2") ||
osgDB::equalCaseInsensitive(extension,"crw") ||
osgDB::equalCaseInsensitive(extension,"fpx") ||
osgDB::equalCaseInsensitive(extension,"fpxi") ||
osgDB::equalCaseInsensitive(extension,"raf") ||
osgDB::equalCaseInsensitive(extension,"dcr") ||
osgDB::equalCaseInsensitive(extension,"ptng") ||
osgDB::equalCaseInsensitive(extension,"pnt") ||
osgDB::equalCaseInsensitive(extension,"mac") ||
osgDB::equalCaseInsensitive(extension,"mrw") ||
osgDB::equalCaseInsensitive(extension,"nef") ||
osgDB::equalCaseInsensitive(extension,"orf") ||
osgDB::equalCaseInsensitive(extension,"exr") ||
osgDB::equalCaseInsensitive(extension,"qti") ||
osgDB::equalCaseInsensitive(extension,"qtif") ||
osgDB::equalCaseInsensitive(extension,"hdr") ||
osgDB::equalCaseInsensitive(extension,"sgi") ||
osgDB::equalCaseInsensitive(extension,"srf") ||
osgDB::equalCaseInsensitive(extension,"cur") ||
osgDB::equalCaseInsensitive(extension,"xbm") ||
osgDB::equalCaseInsensitive(extension,"raw");
}
ReadResult readImageStream(std::istream& fin) const
{
// Call ImageIO to load the image.
CGImageRef cg_image_ref = CreateCGImageFromDataStream(fin);
if (NULL == cg_image_ref) return ReadResult::FILE_NOT_FOUND;
// Create an osg::Image from the CGImageRef.
osg::Image* osg_image = CreateOSGImageFromCGImage(cg_image_ref);
CFRelease(cg_image_ref);
return osg_image;
}
virtual ReadResult readImage(std::istream& fin, const osgDB::ReaderWriter::Options* the_options = NULL) const
{
ReadResult read_result = readImageStream(fin);
return read_result;
}
ReadResult readImageFile(const std::string& file_name) const
{
//osg::notify(osg::INFO) << "imageio readImageFile: " << file_name << std::endl;
// Create an osg::Image from the CGImageRef.
osg::Image* osg_image = ReadCoreGraphicsImageFromFile(file_name);
return osg_image;
}
virtual ReadResult readImage(const std::string& file_name, const osgDB::ReaderWriter::Options* the_options) const
{
std::string ext = osgDB::getLowerCaseFileExtension(file_name);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
std::string full_file_name = osgDB::findDataFile( file_name, the_options );
if (full_file_name.empty()) return ReadResult::FILE_NOT_FOUND;
#if 1
ReadResult read_result = readImageFile(full_file_name);
#else
// Only here to help test istream backend. The file version is better because
// the filenname.extension could potentially be used by ImageIO to hint what the format type is.
std::ifstream istream(full_file_name.c_str(), std::ios::in | std::ios::binary);
if(!istream) return ReadResult::FILE_NOT_HANDLED;
ReadResult read_result = readImage(istream);
#endif
if(read_result.validImage())
{
read_result.getImage()->setFileName(full_file_name);
}
return read_result;
}
WriteResult writeImageStream(const osg::Image& osg_image, std::ostream& fout, const osgDB::ReaderWriter::Options* the_options) const
{
WriteResult ret_val = WriteResult::ERROR_IN_WRITING_FILE;
return WriteResult::FILE_SAVED;
}
virtual WriteResult writeImage(const osg::Image& osg_image, std::ostream& fout, const osgDB::ReaderWriter::Options* the_options) const
{
WriteResult write_result = writeImageStream(osg_image, fout, the_options);
return write_result;
}
WriteResult writeImageFile(const osg::Image& osg_image, const std::string& full_file_name, const osgDB::ReaderWriter::Options* the_options) const
{
WriteResult ret_val = WriteResult::ERROR_IN_WRITING_FILE;
return WriteResult::FILE_SAVED;
}
virtual WriteResult writeImage(const osg::Image& osg_image, const std::string& file_name, const osgDB::ReaderWriter::Options* the_options) const
{
std::string ext = osgDB::getFileExtension(file_name);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
#if 1
// FIXME: Something may need to provide a proper writable location for the files.
std::string full_file_name;
full_file_name = file_name;
return writeImageFile(osg_image, full_file_name, the_options);
#else
// Only here to help test ostream backend. The file version is better because
// the filenname.extension could potentially be used by ImageIO to hint what the format type is.
std::ofstream fout(file_name.c_str(), std::ios::out | std::ios::binary);
if(!fout) return WriteResult::ERROR_IN_WRITING_FILE;
return writeImage(osg_image, fout, the_options);
#endif
}
};
// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(imageio, ReaderWriterImageIO)