Hi

In the attached file I have changed the colour space used for writing images to match the one used for reading images. Also this color space does not make subtle changes to the original colours which i think is probably what most people intend when writing an image and is more consistent with other plugins.

The effect is that an image that is written does not have a noticeably different colour when it is read back.

Hope that helps.

Gill


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

#if __BIG_ENDIAN__
            // That value is a temporary one and only needed for endianess 
conversion
            GLuint *value = (GLuint *)pixels;
            //
            // 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);
            color_space = CGColorSpaceCreateDeviceGray();
            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_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_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_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_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);
            color_space = CGColorSpaceCreateDeviceRGB();
             if(NULL == color_space)
            {
                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_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_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_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);
            color_space = CGColorSpaceCreateDeviceRGB();
            if(NULL == color_space)
            {
                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_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_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);
            color_space = CGColorSpaceCreateDeviceRGB();
            if(NULL == color_space)
            {
                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_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_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_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_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
    {
        if (!osg_image.isDataContiguous())
        {
            return WriteResult::FILE_NOT_HANDLED;
        }

        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
    {
        if (!osg_image.isDataContiguous())
        {
            return WriteResult::FILE_NOT_HANDLED;
        }

        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 (!osg_image.isDataContiguous())
        {
            return WriteResult::FILE_NOT_HANDLED;
        }

        WriteResult ret_val = WriteResult::ERROR_IN_WRITING_FILE;
#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(imageio, ReaderWriterImageIO)
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to