diff --git a/src/osgDB/CMakeLists.txt b/src/osgDB/CMakeLists.txt index 80ce119d1..300bce174 100644 --- a/src/osgDB/CMakeLists.txt +++ b/src/osgDB/CMakeLists.txt @@ -75,7 +75,16 @@ ADD_LIBRARY(${LIB_NAME} IF(APPLE) # Needs CoreFoundation calls and a Carbon function SET(OSGDB_PLATFORM_SPECIFIC_LIBRARIES ${CARBON_LIBRARY}) - ADD_DEFINITIONS(-DDARWIN_QUICKTIME) + + SET(OSG_DEFAULT_IMAGE_PLUGIN_FOR_OSX "quicktime" CACHE STRING "standard image plugin for os x, options are quicktime, imageio") + + + IF(${OSG_DEFAULT_IMAGE_PLUGIN_FOR_OSX} STREQUAL "quicktime") + ADD_DEFINITIONS(-DDARWIN_QUICKTIME) + ELSE(${OSG_DEFAULT_IMAGE_PLUGIN_FOR_OSX} STREQUAL "quicktime") + ADD_DEFINITIONS(-DDARWIN_IMAGEIO) + ENDIF(${OSG_DEFAULT_IMAGE_PLUGIN_FOR_OSX} STREQUAL "quicktime") + ENDIF(APPLE) IF(QUICKTIME_FOUND) diff --git a/src/osgDB/Registry.cpp b/src/osgDB/Registry.cpp index d675aa8c2..2a3077d80 100644 --- a/src/osgDB/Registry.cpp +++ b/src/osgDB/Registry.cpp @@ -239,6 +239,19 @@ Registry::Registry() addFileExtensionAlias("vert", "glsl"); addFileExtensionAlias("frag", "glsl"); + +#if defined(DARWIN_IMAGEIO) + addFileExtensionAlias("jpg", "imageio"); + addFileExtensionAlias("jpe", "imageio"); + addFileExtensionAlias("jpeg", "imageio"); + addFileExtensionAlias("tif", "imageio"); + addFileExtensionAlias("tiff", "imageio"); + addFileExtensionAlias("gif", "imageio"); + addFileExtensionAlias("png", "imageio"); + addFileExtensionAlias("psd", "imageio"); + addFileExtensionAlias("tga", "imageio"); +#endif + #if defined(DARWIN_QUICKTIME) addFileExtensionAlias("jpg", "qt"); addFileExtensionAlias("jpe", "qt"); diff --git a/src/osgPlugins/CMakeLists.txt b/src/osgPlugins/CMakeLists.txt index e12316bdd..e182f175b 100644 --- a/src/osgPlugins/CMakeLists.txt +++ b/src/osgPlugins/CMakeLists.txt @@ -200,6 +200,10 @@ IF(XINE_FOUND) ADD_SUBDIRECTORY(xine) ENDIF(XINE_FOUND) +IF(APPLE) + ADD_SUBDIRECTORY(imageio) +ENDIF(APPLE) + IF(QUICKTIME_FOUND) ADD_SUBDIRECTORY(quicktime) ENDIF(QUICKTIME_FOUND) diff --git a/src/osgPlugins/imageio/CMakeLists.txt b/src/osgPlugins/imageio/CMakeLists.txt new file mode 100644 index 000000000..31f9cafca --- /dev/null +++ b/src/osgPlugins/imageio/CMakeLists.txt @@ -0,0 +1,9 @@ +SET(TARGET_SRC ReaderWriterImageIO.cpp ) + +SET(TARGET_ADDED_LIBRARIES) +SET(TARGET_LIBRARIES_VARS IMAGEIO_LIBRARY ) +SET(TARGET_EXTERNAL_LIBRARIES "/System/Library/Frameworks/Accelerate.framework" ) + +#### end var setup ### +SETUP_PLUGIN(imageio) + diff --git a/src/osgPlugins/imageio/ReaderWriterImageIO.cpp b/src/osgPlugins/imageio/ReaderWriterImageIO.cpp new file mode 100644 index 000000000..05fa58e96 --- /dev/null +++ b/src/osgPlugins/imageio/ReaderWriterImageIO.cpp @@ -0,0 +1,1292 @@ +// Copyright Eric Wing +// This plugin is the bridge to OS X's ImageIO framework +// which provides access to all of Apple's supported image types. +// This plugin plus the QTKit plugin obsoletes the old QuickTime plugin. +// This requires 10.4+. (The old QuickTime plugin will not support 64-bit.) + + +// Needs testing, especially in: +// 8-bits per pixel (256 color vs GL_ALPHA, and what about GL_LUMINANCE)? +// 16-bits per pixel (is GL_LUMINANCE_ALPHA a safe assumption?) +// Non-power-of-two textures (especially odd sizes) +// istream code path +// ostream code path +// write image, especially GL_LUMINANCE and GL_ALPHA paths and image formats other than PNG/JPEG + +// Enhancements needed: +// Way to provide image type hint to ImageIO calls (via CFDictionary), +// probably especially important for istream which lacks extension information. +// Is there information we can use in the OSG options parameter? + +// For ImageIO framework and also LaunchServices framework (for UTIs) +#include +// For the vImage framework (part of the Accerlate framework) +#include + +// Used because CGDataProviderCreate became deprecated in 10.5 +#include + +#include +#include +#include + +#include +#include +#include + +#include // for istream +#include // for ios:: + + +/************************************************************** + ***** 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); +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated +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); +} +#else // CGDataProviderCreate was deprecated in 10.5 +void MyProviderSkipBytesCallback(void* istream_userdata, size_t the_count) +{ + std::istream* the_istream = (std::istream*)istream_userdata; + the_istream->seekg(the_count, std::ios::cur); +} +#endif + +/************************************************************** +***** 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); +*/ + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated + CGDataProviderSequentialCallbacks provider_callbacks = + { + 0, + MyProviderGetBytesCallback, + MyProviderSkipForwardBytesCallback, + MyProviderRewindCallback, + MyProviderReleaseInfoCallback + }; + + CGDataProviderRef data_provider = CGDataProviderCreateSequential(&fin, &provider_callbacks); + + +#else // CGDataProviderCreate was deprecated in 10.5 + + CGDataProviderCallbacks provider_callbacks = + { + MyProviderGetBytesCallback, + MyProviderSkipBytesCallback, + MyProviderRewindCallback, + MyProviderReleaseInfoCallback + }; + + CGDataProviderRef data_provider = CGDataProviderCreate(&fin, &provider_callbacks); +#endif + // 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; +} + + +/* Create a CGImageSourceRef from a file. */ +/* Remember to CFRelease the created image when done. */ +CGImageRef CreateCGImageFromFile(const char* the_path) +{ + CFURLRef the_url = NULL; + CGImageRef image_ref = NULL; + CGImageSourceRef source_ref = NULL; + CFStringRef cf_string = NULL; + + /* Create a CFString from a C string */ + cf_string = CFStringCreateWithCString( + NULL, + the_path, + kCFStringEncodingUTF8 + ); + if(!cf_string) + { + osg::notify(osg::WARN) << "CreateCGImageFromFile :: could not create CCFSTring" << std::endl; + return NULL; + } + + /* Create a CFURL from a CFString */ + the_url = CFURLCreateWithFileSystemPath( + NULL, + cf_string, + kCFURLPOSIXPathStyle, + false + ); + + /* Don't need the CFString any more (error or not) */ + CFRelease(cf_string); + + if(!the_url) + { + osg::notify(osg::WARN) << "CreateCGImageFromFile :: could not create CFUrl" << std::endl; + return NULL; + } + + + source_ref = CGImageSourceCreateWithURL(the_url, NULL); + /* Don't need the URL any more (error or not) */ + CFRelease(the_url); + + if(!source_ref) + { + osg::notify(osg::WARN) << "CreateCGImageFromFile :: could not create ImageSource" << std::endl; + return NULL; + } + + // Get the first item in the image source (some image formats may + // contain multiple items). + image_ref = CGImageSourceCreateImageAtIndex(source_ref, 0, NULL); + if (!image_ref) { + osg::notify(osg::WARN) << "CreateCGImageFromFile :: could not get Image" << std::endl; + } + + /* Don't need the SourceRef any more (error or not) */ + CFRelease(source_ref); + + return image_ref; +} + +/* Once we have our image (CGImageRef), we need to get it into an osg::Image */ +osg::Image* CreateOSGImageFromCGImage(CGImageRef image_ref) +{ + /* This code is adapted from Apple's Documentation found here: + * http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/index.html + * Listing 9-4††Using a Quartz image as a texture source. + * Unfortunately, this guide doesn't show what to do about + * non-RGBA image formats so I'm making the rest up + * (and it's probably all wrong). + */ + + size_t the_width = CGImageGetWidth(image_ref); + size_t the_height = CGImageGetHeight(image_ref); + CGRect the_rect = {{0, 0}, {the_width, the_height}}; + + size_t bits_per_pixel = CGImageGetBitsPerPixel(image_ref); + size_t bytes_per_row = CGImageGetBytesPerRow(image_ref); +// size_t bits_per_component = CGImageGetBitsPerComponent(image_ref); + size_t bits_per_component = 8; + + CGImageAlphaInfo alpha_info = CGImageGetAlphaInfo(image_ref); + + GLint internal_format; + GLenum pixel_format; + GLenum data_type; + + void* image_data = calloc(the_width * 4, the_height); + + CGColorSpaceRef color_space; + CGBitmapInfo bitmap_info = CGImageGetBitmapInfo(image_ref); + + switch(bits_per_pixel) + { + // Drat, if 8-bit, how do you distinguish + // between a 256 color GIF, a LUMINANCE map + // or an ALPHA map? + case 8: + { + // I probably did the formats all wrong for this case, + // especially the ALPHA case. + if(kCGImageAlphaNone == alpha_info) + { + /* + internal_format = GL_LUMINANCE; + pixel_format = GL_LUMINANCE; + */ + internal_format = GL_RGBA8; + pixel_format = GL_BGRA_EXT; + data_type = GL_UNSIGNED_INT_8_8_8_8_REV; + + bytes_per_row = the_width*4; +// color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + color_space = CGColorSpaceCreateDeviceRGB(); +// bitmap_info = kCGImageAlphaPremultipliedFirst; +#if __BIG_ENDIAN__ + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big; /* XRGB Big Endian */ +#else + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little; /* XRGB Little Endian */ +#endif + } + else + { + internal_format = GL_ALPHA; + pixel_format = GL_ALPHA; + data_type = GL_UNSIGNED_BYTE; + // bytes_per_row = the_width; +// color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); + color_space = CGColorSpaceCreateDeviceGray(); + } + + break; + } + case 24: + { + internal_format = GL_RGBA8; + pixel_format = GL_BGRA_EXT; + data_type = GL_UNSIGNED_INT_8_8_8_8_REV; + bytes_per_row = the_width*4; +// color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + color_space = CGColorSpaceCreateDeviceRGB(); +// bitmap_info = kCGImageAlphaNone; +#if __BIG_ENDIAN__ + bitmap_info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Big; /* XRGB Big Endian */ +#else + bitmap_info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little; /* XRGB Little Endian */ +#endif + break; + } + // + // Tatsuhiro Nishioka + // 16 bpp grayscale (8 bit white and 8 bit alpha) causes invalid argument combination + // in CGBitmapContextCreate. + // I guess it is safer to handle 16 bit grayscale image as 32-bit RGBA image. + // It works at least on FlightGear + // + case 16: + case 32: + { + + internal_format = GL_RGBA8; + pixel_format = GL_BGRA_EXT; + data_type = GL_UNSIGNED_INT_8_8_8_8_REV; + + bytes_per_row = the_width*4; +// color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + color_space = CGColorSpaceCreateDeviceRGB(); +// bitmap_info = kCGImageAlphaPremultipliedFirst; + +#if __BIG_ENDIAN__ + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big; /* XRGB Big Endian */ +#else + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little; /* XRGB Little Endian */ +#endif + break; + } + default: + { + // osg::notify(osg::WARN) << "Unknown file type in " << fileName.c_str() << " with " << origDepth << std::endl; + return NULL; + break; + } + + } + + // Sets up a context to be drawn to with image_data as the area to be drawn to + CGContextRef bitmap_context = CGBitmapContextCreate( + image_data, + the_width, + the_height, + bits_per_component, + bytes_per_row, + color_space, + bitmap_info + ); + + // Draws the image into the context's image_data + CGContextDrawImage(bitmap_context, the_rect, image_ref); + + CGContextRelease(bitmap_context); + + // + // 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 + // + if (bits_per_pixel > 8 && (bitmap_info & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst) { + int i, j; + GLubyte *pixels = (GLubyte *)image_data; + for (i = the_height * the_width; i--; ) { + GLuint *value = (GLuint *)pixels; +#if __BIG_ENDIAN__ + // + // swap endian of each pixel for avoiding weird colors on ppc macs + // by Tatsuhiro Nishioka + // FIXME: I've tried many combinations of pixel_format, internal_format, and data_type + // but none worked well. Therefore I tried endian swapping, which seems working with gif,png,tiff,tga,and psd. + // (for grayscaled tga and non-power-of-two tga, I can't guarantee since test images (made with Gimp) + // get corrupted on Preview.app ... + *value = ((*value) >> 24) | (((*value) << 8) & 0x00FF0000) | (((*value) >> 8) & 0x0000FF00) | ((*value) << 24); +#endif + GLubyte alpha = pixels[3]; + if (alpha) { + for (j = 0; j < 3; ++j) { + pixels[j] = (pixels[j] * 255) / alpha; + } + } + pixels += 4; + } + } + + // + // Workaround for ignored alpha channel + // by Tatsuhiro Nishioka + // FIXME: specifying GL_UNSIGNED_INT_8_8_8_8_REV or GL_UNSIGNED_INT_8_8_8_8 ignores the alpha channel. + // changing it to GL_UNSIGNED_BYTE seems working, but I'm not sure if this is a right way. + // + data_type = GL_UNSIGNED_BYTE; + osg::Image* osg_image = new osg::Image; + + osg_image->setImage( + the_width, + the_height, + 1, + internal_format, + pixel_format, + data_type, + (unsigned char*)image_data, + osg::Image::USE_MALLOC_FREE // Assumption: osg_image takes ownership of image_data and will free + ); + + osg_image->flipVertical(); + return osg_image; + + + +} +/************************************************************** +***** End Support functions for reading (stream and file) ***** +**************************************************************/ + + +/************************************************************** +***** Begin Support functions for writing (stream and file)**** +**************************************************************/ + +/* Create a CGImageRef from osg::Image. + * Code adapted from + * http://developer.apple.com/samplecode/OpenGLScreenSnapshot/listing2.html + */ +CGImageRef CreateCGImageFromOSGData(const osg::Image& osg_image) +{ + size_t image_width = osg_image.s(); + size_t image_height = osg_image.t(); + /* From Apple's header for CGBitmapContextCreate() + * Each row of the bitmap consists of `bytesPerRow' bytes, which must be at + * least `(width * bitsPerComponent * number of components + 7)/8' bytes. + */ + size_t target_bytes_per_row; + + CGColorSpaceRef color_space; + CGBitmapInfo bitmap_info; + /* From what I can figure out so far... + * We need to create a CGContext connected to the data we want to save + * and then call CGBitmapContextCreateImage() on that context to get + * a CGImageRef. + * However, OS X only allows 4-component image formats (e.g. RGBA) and not + * just RGB for the RGB-based CGContext. So for a 24-bit image coming in, + * we need to expand the data to 32-bit. + * The easiest and fastest way to do that is through the vImage framework + * which is part of the Accelerate framework. + * Also, the osg::Image data coming in is inverted from what we want, so + * we need to invert the image too. Since the osg::Image is const, + * we don't want to touch the data, so again we turn to the vImage framework + * and invert the data. + */ + vImage_Buffer vimage_buffer_in = + { + (void*)osg_image.data(), // need to override const, but we don't modify the data so it's safe + image_height, + image_width, + osg_image.getRowSizeInBytes() + }; + + void* out_image_data; + vImage_Buffer vimage_buffer_out = + { + NULL, // will fill-in in switch + image_height, + image_width, + 0 // will fill-in in switch + }; + vImage_Error vimage_error_flag; + + // FIXME: Do I want to use format, type, or internalFormat? + switch(osg_image.getPixelFormat()) + { + case GL_LUMINANCE: + { + bitmap_info = kCGImageAlphaNone; + target_bytes_per_row = (image_width * 8 + 7)/8; + color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); + if(NULL == color_space) + { + return NULL; + } + + // out_image_data = calloc(target_bytes_per_row, image_height); + out_image_data = malloc(target_bytes_per_row * image_height); + if(NULL == out_image_data) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, malloc failed" << std::endl; + CGColorSpaceRelease(color_space); + return NULL; + } + + vimage_buffer_out.data = out_image_data; + vimage_buffer_out.rowBytes = target_bytes_per_row; + + // Now invert the image + vimage_error_flag = vImageVerticalReflect_Planar8( + &vimage_buffer_in, // since the osg_image is const... + &vimage_buffer_out, // don't reuse the buffer + kvImageNoFlags + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData for GL_LUMINANCE, vImageVerticalReflect_Planar8 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + CGColorSpaceRelease(color_space); + return NULL; + } + + + break; + } + case GL_ALPHA: + { + bitmap_info = kCGImageAlphaOnly; + target_bytes_per_row = (image_width * 8 + 7)/8; + // According to: + // http://developer.apple.com/qa/qa2001/qa1037.html + // colorSpace=NULL is for alpha only + color_space = NULL; + + // out_image_data = calloc(target_bytes_per_row, image_height); + out_image_data = malloc(target_bytes_per_row * image_height); + if(NULL == out_image_data) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, malloc failed" << std::endl; + return NULL; + } + + vimage_buffer_out.data = out_image_data; + vimage_buffer_out.rowBytes = target_bytes_per_row; + + // Now invert the image + vimage_error_flag = vImageVerticalReflect_Planar8( + &vimage_buffer_in, // since the osg_image is const... + &vimage_buffer_out, // don't reuse the buffer + kvImageNoFlags + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData for GL_ALPHA, vImageVerticalReflect_Planar8 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + return NULL; + } + + + break; + } +/* + case GL_LUMINANCE_ALPHA: + { + // I don't know if we can support this. + // The qa1037 doesn't show both gray+alpha. + break; + } +*/ + case GL_RGB: + { + bitmap_info = kCGImageAlphaNoneSkipFirst; + target_bytes_per_row = (image_width * 8 * 4 + 7)/8; + color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + if(NULL == color_space) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, CGColorSpaceCreateWithName failed" << std::endl; + return NULL; + } + + // out_image_data = calloc(target_bytes_per_row, image_height); + out_image_data = malloc(target_bytes_per_row * image_height); + if(NULL == out_image_data) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, malloc failed" << std::endl; + CGColorSpaceRelease(color_space); + return NULL; + } + + // Use vImage to get an RGB buffer into ARGB. + vimage_buffer_out.data = out_image_data; + vimage_buffer_out.rowBytes = target_bytes_per_row; + vimage_error_flag = vImageConvert_RGB888toARGB8888( + &vimage_buffer_in, + NULL, // we don't have a buffer containing alpha values + 255, // The alpha value we want given to all pixels since we don't have a buffer + &vimage_buffer_out, + 0, // premultiply? + kvImageNoFlags // Only responds to kvImageDoNotTile, but I think we want tiling/threading + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, vImageConvert_RGB888toARGB8888 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + CGColorSpaceRelease(color_space); + return NULL; + } + // Now invert the image + vimage_error_flag = vImageVerticalReflect_ARGB8888( + &vimage_buffer_out, + &vimage_buffer_out, // reuse the same buffer + kvImageNoFlags + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, vImageAffineWarp_ARGB8888 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + CGColorSpaceRelease(color_space); + return NULL; + } + + break; + } + case GL_RGBA: + { + bitmap_info = kCGImageAlphaPremultipliedLast; + target_bytes_per_row = osg_image.getRowSizeInBytes(); + color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + if(NULL == color_space) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, CGColorSpaceCreateWithName failed" << std::endl; + return NULL; + } + // out_image_data = calloc(target_bytes_per_row, image_height); + out_image_data = malloc(target_bytes_per_row * image_height); + if(NULL == out_image_data) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, malloc failed" << std::endl; + CGColorSpaceRelease(color_space); + return NULL; + } + vimage_buffer_out.data = out_image_data; + vimage_buffer_out.rowBytes = target_bytes_per_row; + // Invert the image + vimage_error_flag = vImageVerticalReflect_ARGB8888( + &vimage_buffer_in, // since the osg_image is const... + &vimage_buffer_out, // don't reuse the buffer + kvImageNoFlags + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, vImageAffineWarp_ARGB8888 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + CGColorSpaceRelease(color_space); + return NULL; + } + break; + } + case GL_BGRA: + { + if(GL_UNSIGNED_INT_8_8_8_8_REV == osg_image.getDataType()) + { +#if __BIG_ENDIAN__ + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big; /* XRGB Big Endian */ +#else + bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little; /* XRGB Little Endian */ +#endif + } + else + { + // FIXME: Don't know how to handle this case + bitmap_info = kCGImageAlphaPremultipliedLast; + } + + target_bytes_per_row = osg_image.getRowSizeInBytes(); + color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + if(NULL == color_space) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, CGColorSpaceCreateWithName failed" << std::endl; + return NULL; + } + // out_image_data = calloc(target_bytes_per_row, image_height); + out_image_data = malloc(target_bytes_per_row * image_height); + if(NULL == out_image_data) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, malloc failed" << std::endl; + CGColorSpaceRelease(color_space); + return NULL; + } + vimage_buffer_out.data = out_image_data; + vimage_buffer_out.rowBytes = target_bytes_per_row; + // Invert the image + vimage_error_flag = vImageVerticalReflect_ARGB8888( + &vimage_buffer_in, // since the osg_image is const... + &vimage_buffer_out, // don't reuse the buffer + kvImageNoFlags + ); + if(vimage_error_flag != kvImageNoError) + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData, vImageAffineWarp_ARGB8888 failed with vImage Error Code: " << vimage_error_flag << std::endl; + free(out_image_data); + CGColorSpaceRelease(color_space); + return NULL; + } + break; + } + // FIXME: Handle other cases. + // Use vImagePermuteChannels_ARGB8888 to swizzle bytes + default: + { + osg::notify(osg::WARN) << "In CreateCGImageFromOSGData: Sorry support for this format is not implemented." << std::endl; + return NULL; + break; + } + } + + CGContextRef bitmap_context = CGBitmapContextCreate( + vimage_buffer_out.data, + vimage_buffer_out.width, + vimage_buffer_out.height, + 8, + vimage_buffer_out.rowBytes, + color_space, + bitmap_info + ); + /* Done with color space */ + CGColorSpaceRelease(color_space); + + if(NULL == bitmap_context) + { + free(out_image_data); + return NULL; + } + + + /* Make an image out of our bitmap; does a cheap vm_copy of the bitmap */ + CGImageRef image_ref = CGBitmapContextCreateImage(bitmap_context); + + /* Done with data */ + free(out_image_data); + + /* Done with bitmap_context */ + CGContextRelease(bitmap_context); + + return image_ref; +} + + +/* Create a CGImageDestinationRef from a file. */ +/* Remember to CFRelease when done. */ +CGImageDestinationRef CreateCGImageDestinationFromFile(const char* the_path, const osgDB::ReaderWriter::Options* the_options) +{ + CFURLRef the_url = NULL; + CFStringRef cf_string = NULL; + CFStringRef uti_type = NULL; + CGImageDestinationRef dest_ref = NULL; + bool found_png_option = false; + bool found_jpeg_option = false; + float compression_quality = 1.0f; + + /* Create a CFString from a C string */ + cf_string = CFStringCreateWithCString( + NULL, + the_path, + kCFStringEncodingUTF8 + ); + if(!cf_string) + { + return NULL; + } + + /* Create a CFURL from a CFString */ + the_url = CFURLCreateWithFileSystemPath( + NULL, + cf_string, + kCFURLPOSIXPathStyle, + false + ); + + /* Don't need the CFString any more (error or not) */ + CFRelease(cf_string); + + if(!the_url) + { + return NULL; + } + + if(the_options) + { + std::istringstream iss(the_options->getOptionString()); + std::string opt; + while (iss >> opt) + { + // Not handled: The user could do something stupid and specify both PNG and JPEG options. + + if(opt=="PNG_COMPRESSION") + { + found_png_option = true; + // I don't see an option to set PNG compression levels in the API so this info is unused. + int level; + iss >> level; + + } + else if(opt=="JPEG_QUALITY") + { + found_jpeg_option = true; + // Chances are that people are specifying values in libjpeg ranges and not ImageIO ranges. + // ImageIO is normalized between 0.0 to 1.0 where 1.0 is lossless and 0 is max compression. + // I am uncertain what libjpeg's range is. I'm guessing 0-100. + int quality; + iss >> quality; + compression_quality = (float)quality/100.0f; + } + } + } + + + CFStringRef path_extension = CFURLCopyPathExtension(the_url); + if(NULL == path_extension) + { + if(found_jpeg_option) + { + uti_type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + CFSTR("jpg"), + kUTTypeImage // "public.image" + ); + } + else + { + uti_type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + CFSTR("png"), + kUTTypeImage // "public.image" + ); + } + } + else + { + uti_type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + path_extension, + kUTTypeImage // "public.image" + ); + CFRelease(path_extension); + } + + + dest_ref = CGImageDestinationCreateWithURL( + the_url, + uti_type, + 1, // image file will contain only one image + NULL + ); + + + CFRelease(uti_type); + CFRelease(the_url); + + + // Not handled: The user could do something stupid and specify both PNG and JPEG options. + if(found_jpeg_option) + { + // Do a bunch of work to setup a CFDictionary containing the jpeg compression properties. + CFStringRef the_keys[1]; + CFNumberRef the_values[1]; + CFDictionaryRef the_dict; + + the_keys[0] = kCGImageDestinationLossyCompressionQuality; + the_values[0] = CFNumberCreate( + NULL, + kCFNumberFloat32Type, + &compression_quality + ); + + the_dict = CFDictionaryCreate(NULL, (const void**)&the_keys, (const void**)&the_values, 1, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(the_values[0]); + + // Now that we have the dict, actually set the property. + CGImageDestinationSetProperties(dest_ref, the_dict); + + CFRelease(the_dict); + } + + return dest_ref; +} + + +/* Create a CGImageDestinationRef from a file. */ +/* Remember to CFRelease when done. */ +CGImageDestinationRef CreateCGImageDestinationFromDataStream(std::ostream& fout, const osgDB::ReaderWriter::Options* the_options) +{ + CFStringRef uti_type = NULL; + CGImageDestinationRef dest_ref = NULL; + bool found_png_option = false; + bool found_jpeg_option = false; + float compression_quality = 1.0f; + + CGDataConsumerCallbacks consumer_callbacks = + { + MyConsumerPutBytesCallback, + MyConsumerReleaseInfoCallback + }; + + CGDataConsumerRef data_consumer = CGDataConsumerCreate(&fout, &consumer_callbacks); + + if(the_options) + { + std::istringstream iss(the_options->getOptionString()); + std::string opt; + while (iss >> opt) + { + // Not handled: The user could do something stupid and specify both PNG and JPEG options. + + if(opt=="PNG_COMPRESSION") + { + found_png_option = true; + // I don't see an option to set PNG compression levels in the API so this info is unused. + int level; + iss >> level; + + } + else if(opt=="JPEG_QUALITY") + { + found_jpeg_option = true; + // Chances are that people are specifying values in libjpeg ranges and not ImageIO ranges. + // ImageIO is normalized between 0.0 to 1.0 where 1.0 is lossless and 0 is max compression. + // I am uncertain what libjpeg's range is. I'm guessing 0-100. + int quality; + iss >> quality; + compression_quality = (float)quality/100.0f; + } + } + } + + + if(found_jpeg_option) + { + uti_type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + CFSTR("jpg"), + kUTTypeImage // "public.image" + ); + } + else // default to png + { + uti_type = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, + CFSTR("png"), + kUTTypeImage // "public.image" + ); + } + + + // If we had a way of hinting at what the data type is, we could + // pass this hint in the second parameter. + dest_ref = CGImageDestinationCreateWithDataConsumer( + data_consumer, + uti_type, + 1, // image file will contain only one image + NULL + ); + + CGDataConsumerRelease(data_consumer); + CFRelease(uti_type); + + + // Not handled: The user could do something stupid and specify both PNG and JPEG options. + if(found_jpeg_option) + { + // Do a bunch of work to setup a CFDictionary containing the jpeg compression properties. + CFStringRef the_keys[1]; + CFNumberRef the_values[1]; + CFDictionaryRef the_dict; + + the_keys[0] = kCGImageDestinationLossyCompressionQuality; + the_values[0] = CFNumberCreate( + NULL, + kCFNumberFloat32Type, + &compression_quality + ); + + the_dict = CFDictionaryCreate(NULL, (const void**)&the_keys, (const void**)&the_values, 1, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(the_values[0]); + + // Now that we have the dict, actually set the property. + CGImageDestinationSetProperties(dest_ref, the_dict); + + CFRelease(the_dict); + } + + return dest_ref; +} +/************************************************************** +***** End Support functions for writing (stream and file) ***** +**************************************************************/ + + + +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; + + // Call ImageIO to load the image. + CGImageRef cg_image_ref = CreateCGImageFromFile(file_name.c_str()); + 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(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; + + CGImageDestinationRef cg_dest_ref = CreateCGImageDestinationFromDataStream(fout, the_options); + if (NULL == cg_dest_ref) return WriteResult::ERROR_IN_WRITING_FILE; + + CGImageRef cg_image_ref = CreateCGImageFromOSGData(osg_image); + if(NULL == cg_image_ref) + { + CFRelease(cg_dest_ref); + return WriteResult::ERROR_IN_WRITING_FILE; + } + + CGImageDestinationAddImage(cg_dest_ref, cg_image_ref, NULL); + if(CGImageDestinationFinalize(cg_dest_ref)) + { + ret_val = WriteResult::FILE_SAVED; + } + else + { + ret_val = WriteResult::ERROR_IN_WRITING_FILE; + } + + CFRelease(cg_image_ref); + CFRelease(cg_dest_ref); + + 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; + // Call ImageIO to load the image. + CGImageDestinationRef cg_dest_ref = CreateCGImageDestinationFromFile(full_file_name.c_str(), the_options); + if (NULL == cg_dest_ref) return WriteResult::ERROR_IN_WRITING_FILE; + + CGImageRef cg_image_ref = CreateCGImageFromOSGData(osg_image); + if(NULL == cg_image_ref) + { + CFRelease(cg_dest_ref); + return WriteResult::ERROR_IN_WRITING_FILE; + } + + CGImageDestinationAddImage(cg_dest_ref, cg_image_ref, NULL); + if(CGImageDestinationFinalize(cg_dest_ref)) + { + ret_val = WriteResult::FILE_SAVED; + } + else + { + ret_val = WriteResult::ERROR_IN_WRITING_FILE; + } + + CFRelease(cg_image_ref); + CFRelease(cg_dest_ref); + + 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. + +osgDB::RegisterReaderWriterProxy g_readerWriter_ImageIO_Proxy; + + + +