1292 lines
48 KiB
C++
1292 lines
48 KiB
C++
// 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 <ApplicationServices/ApplicationServices.h>
|
|
// For the vImage framework (part of the Accerlate framework)
|
|
#include <Accelerate/Accelerate.h>
|
|
|
|
// Used because CGDataProviderCreate became deprecated in 10.5
|
|
#include <AvailabilityMacros.h>
|
|
|
|
#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::
|
|
|
|
|
|
/**************************************************************
|
|
***** 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.
|
|
osgDB::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.
|
|
osgDB::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(imagio, ReaderWriterImageIO)
|
|
|
|
|
|
|
|
|