Robert Osfield wrote:
> Hi Eric,
> 
> On Thu, Feb 26, 2009 at 1:41 PM, Eric Sokolowsky <[email protected]> wrote:
>> This problem can also be solved on OSX by integrating Eric Wing's
>> ImageIO plugin to handle static images. I have the code; I just need to
>> find time to integrate the plugin. Unfortunately I haven't had much time
>> to devote to OSG and my OSG-based application lately.
> 
> Ahh more good news on the OSX front :-)
> 
> You could always post the code which would allow others to help out
> with the wrapping up as plugin.


This is my second attempt to send Eric Wing's plugin. This was actually
originally submitted to osg-submissions in October 2007, but nothing was
done with it. If anyone wants to work on integrating it, please go
ahead. My plate seems to always be full with other tasks...

-Eric S.

---------- Forwarded message ----------
From: "E. Wing"
Date: Fri, 26 Oct 2007 16:43:59 -0700
Subject: New image handling plugin for OS X (osgdb_ImageIO)
To: [email protected]

Yea! The Leopard NDA is finally lifted.

This is a submission for a new osg plugin, osgdb_ImageIO. This is the
first piece of the puzzle that we need to get 64-bit support on OS X.
This plugin is intended to partially replace the current osgdb_qt
(classic Quicktime) plugin. There are many issues with the current
osgdb_qt, but perhaps the biggest is that there are many APIs used in
it that have been marked deprecated for awhile now and will not make
it to 64-bit on OS X.

ImageIO is Apple's (semi-)new (as of 10.4 Tiger) fundamental image
framework that provides access to all image formats handled by the
platform. This new osgdb_ImageIO plugin intends to replace all of
osgdb_qt's image handling duties as well as introduce support to new
image formats as they become available to the platform (e.g. JPEG2000,
RAW, HDR, etc).

osgdb_ImageIO does not replace osgdb_qt's movie handling capabilities.
I envision that to be handled by a planned second plugin using Apple's
semi-new (10.4 Tiger) QuickTimeKit framework, tentatively osgdb_QTKit.
So this plugin is just the first step.

Would you please add this to src/osgPlugins/ImageIO?

Improvements over osgdb_qt plugin:
- Supports istream and ostream
- Supports a lot more image formats
- ImageIO framework should be well supported from 10.4 to the future
(which should include 64-bit and new/future image formats)
- Doesn't require explicit initialization/close-out
- (Hopefully) efficient...avoids the manual byte-by-byte manipulation
of the old QuickTime plugin. Calls Apple's Accelerate framework when
useful.
- Seems to fix/avoid AutoreleasePool related leak warnings which I
believe the current qt plugin triggers if not using Cocoa (i.e.
actually having an autorelease pool created).

Missing:
- No movie file support (planning/expecting a separate QTKit plugin to
handle that).
- Need to update osgDB::Registry for new plugin
- Need to update build system(s)

Additional Notes:
- The old Quicktime plugin will need to remain for both Windows users
(who happen to use it) and pre-10.4 OS X versions. It will also need
to remain for movies until we get a QTKit plugin written.
- This plugin probably could use additional testing for
16-bit/LUMINANCE/ALPHA stuff. I'm not terribly confident I understood what
needs to happen in these cases so behavior could be different/broken
compared to osgdb_qt.


Once I figure out all the build system details, I'm proposing that for
the next release of OSG, if building for OS X 10.4 or 10.5, the
ImageIO plugin gets built and set as the default image handling
plugin. For legacy 10.3 and Quicktime for Windows users, the existing
osgdb_qt plugin should remain available. For now though, I wanted to
make this piece of code available in case anybody has a pressing need
to get 64-bit going soon.


Thanks,
Eric

// 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)
	{
		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;
	}

	
    source_ref = CGImageSourceCreateWithURL(the_url, NULL);
	/* Don't need the URL any more (error or not) */
	CFRelease(the_url);

    if(!source_ref)
	{
		return NULL;
    }

	// Get the first item in the image source (some image formats may
	// contain multiple items).
	image_ref = CGImageSourceCreateImageAtIndex(source_ref, 0, NULL);
	
	/* 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 16:
		{
			// FIXME: Is this really a reasonable assumption: GL_LUMINANCE_ALPHA?
			internal_format = GL_LUMINANCE_ALPHA;
			pixel_format = GL_LUMINANCE_ALPHA;
			data_type = GL_UNSIGNED_BYTE;
//			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;
		}
		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);

	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) *****
**************************************************************/


#ifdef PRETEND_IS_QT // Pose as the old qt plugin for easier testing.
class ReaderWriterQT : public osgDB::ReaderWriter
#else
class ReaderWriterImageIO : public osgDB::ReaderWriter
#endif
{
#ifdef PRETEND_IS_QT
	virtual const char* className() const { return "Mac OS X ImageIO based Image Reader/Writer (posing as qt)"; }
#else
	virtual const char* className() const { return "Mac OS X ImageIO based Image Reader/Writer"; }
#endif

   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,"rgb") ||
         osgDB::equalCaseInsensitive(extension,"rgba") ||
         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
	{
		// 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.
#ifdef PRETEND_IS_QT
osgDB::RegisterReaderWriterProxy<ReaderWriterQT> g_readerWriter_QT_Proxy;
#else
osgDB::RegisterReaderWriterProxy<ReaderWriterImageIO> g_readerWriter_ImageIO_Proxy;
#endif



_______________________________________________
osg-users mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Reply via email to