IMG_ImageIO.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 29 Nov 2014 12:06:05 -0800
changeset 466 55801d955e3a
parent 368 8a61842d00ce
child 530 181ef57f85b4
permissions -rw-r--r--
Fixed bug 2788 - color space fix for ImageIO backend (OS X, iOS)

Jeffrey Carpenter

Test case resources (should be able to be replaced with your own as long as the pixel format and file encoding matches):
test.bmp RGB8888 (24-bit, BMP)
test.png ARGB8888 (32-bit, PNG)

This patch reverts [revision 292](https://hg.libsdl.org/SDL_image/log?rev=292) back to the original author's implementation (Eric Wing). The comments made in this revision seem to contradict what is intended; see CGColorSpaceCreateCalibratedRGB [1] and CGColorSpaceCreateDeviceRGB [2] for Apple's API notes. Also note that the revision comments by "Albert" use a test case that is for use with SDL1 -- how this may change things is unknown to me.

1. https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGColorSpace/index.html#//apple_ref/c/func/CGColorSpaceCreateCalibratedRGB
2. https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGColorSpace/index.html#//apple_ref/c/func/CGColorSpaceCreateDeviceRGB

Additional notes
* Tested on Mac OS X 10.10 "Yosemite".
* Has not been tested against iOS, SDL1 or images that have been processed with pre-multiplied alpha.

Build test case:
clang++ sdl2_image-colorspace-fix.cpp -o sdl2_image-colorspace-fix -framework SDL2 -framework SDL2_image

Run test case to generate my existing data (my baseline / reference):
(output in diag_main.txt, using test.bmp and test.png)

bitmap(0, 0) = ff6e8fbd
png(0, 0) = ff6e8fbd
sdlbmp(0, 0) = 6e90be

bitmap(31, 0) = ff6e8fbd
png(31, 0) = ff6e8fbd
sdlbmp(31, 0) = 6e90be

<snip remaining debug info>

NOTE: ff6e8fbd = R(110) G(143) B(189)
The actual pixel value is 110, 144, 190. SDL_LoadBMP gets it right in both
cases, whereas IMG_Load (ImageIO backend) gets it wrong on both counts.

Apply revert-rev-292.patch:
hg import revert-rev-292.patch

Rebuild test case, ensuring that we compile and **run** with the proper runtime
search paths for our patched SDL2_image library (I built mine using Xcode project files):

```sh
# man dyld (1)
DYLD_FRAMEWORK_PATH=<my_local_prefix_lib_path> ./sdl2_image-colorspace-fix
```

Comparison output of reference data with patched library:
(output in diag_main.txt, using test.bmp and test.png)

bitmap(0, 0) = ff6e90be
png(0, 0) = ff6e90be
sdlbmp(0, 0) = 6e90be

bitmap(31, 0) = ff6e90be
png(31, 0) = ff6e90be
sdlbmp(31, 0) = 6e90be

<snip remaining debug info>

NOTE: ff6e90be = R(110) G(144) B(190)
The actual pixel values match using both SDL_LoadBMP and IMG_Load. This is the
expected behavior.

References
1. [Introduction of ImageIO backend](https://forums.libsdl.org/viewtopic.php?p=14777)
2. [Pixel bug in Mac OS X](https://forums.libsdl.org/viewtopic.php?p=30317)
3. [Perhaps again? Pixel bug in Mac OS X](https://forums.libsdl.org/viewtopic.php?t=10013&sid=34e8ef7cf74074039495037c1a077025)
     1 /*
     2  *  IMG_ImageIO.c
     3  *  SDL_image
     4  *
     5  *  Created by Eric Wing on 1/1/09.
     6  *  Copyright 2009 __MyCompanyName__. All rights reserved.
     7  *
     8  */
     9 
    10 #if defined(__APPLE__) && !defined(SDL_IMAGE_USE_COMMON_BACKEND)
    11 
    12 #include "SDL_image.h"
    13 
    14 // Used because CGDataProviderCreate became deprecated in 10.5
    15 #include <AvailabilityMacros.h>
    16 #include <TargetConditionals.h>
    17 #include <Foundation/Foundation.h>
    18 
    19 #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
    20 #ifdef ALLOW_UIIMAGE_FALLBACK
    21 #define USE_UIIMAGE_BACKEND() ([UIImage instancesRespondToSelector:@selector(initWithCGImage:scale:orientation:)] == NO)
    22 #else
    23 #define USE_UIIMAGE_BACKEND() (Internal_checkImageIOisAvailable())
    24 #endif
    25 #import <MobileCoreServices/MobileCoreServices.h> // for UTCoreTypes.h
    26 #import <ImageIO/ImageIO.h>
    27 #import <UIKit/UIImage.h>
    28 #else
    29 // For ImageIO framework and also LaunchServices framework (for UTIs)
    30 #include <ApplicationServices/ApplicationServices.h>
    31 #endif
    32 
    33 /**************************************************************
    34  ***** Begin Callback functions for block reading *************
    35  **************************************************************/
    36 
    37 // This callback reads some bytes from an SDL_rwops and copies it
    38 // to a Quartz buffer (supplied by Apple framework).
    39 static size_t MyProviderGetBytesCallback(void* rwops_userdata, void* quartz_buffer, size_t the_count)
    40 {
    41     return (size_t)SDL_RWread((struct SDL_RWops *)rwops_userdata, quartz_buffer, 1, the_count);
    42 }
    43 
    44 // This callback is triggered when the data provider is released
    45 // so you can clean up any resources.
    46 static void MyProviderReleaseInfoCallback(void* rwops_userdata)
    47 {
    48     // What should I put here?
    49     // I think the user and SDL_RWops controls closing, so I don't do anything.
    50 }
    51 
    52 static void MyProviderRewindCallback(void* rwops_userdata)
    53 {
    54     SDL_RWseek((struct SDL_RWops *)rwops_userdata, 0, RW_SEEK_SET);
    55 }
    56 
    57 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated
    58 off_t MyProviderSkipForwardBytesCallback(void* rwops_userdata, off_t the_count)
    59 {
    60     off_t start_position = SDL_RWtell((struct SDL_RWops *)rwops_userdata);
    61     SDL_RWseek((struct SDL_RWops *)rwops_userdata, the_count, RW_SEEK_CUR);
    62     off_t end_position = SDL_RWtell((struct SDL_RWops *)rwops_userdata);
    63     return (end_position - start_position);
    64 }
    65 #else // CGDataProviderCreate was deprecated in 10.5
    66 static void MyProviderSkipBytesCallback(void* rwops_userdata, size_t the_count)
    67 {
    68     SDL_RWseek((struct SDL_RWops *)rwops_userdata, the_count, RW_SEEK_CUR);
    69 }
    70 #endif
    71 
    72 /**************************************************************
    73  ***** End Callback functions for block reading ***************
    74  **************************************************************/
    75 
    76 // This creates a CGImageSourceRef which is a handle to an image that can be used to examine information
    77 // about the image or load the actual image data.
    78 static CGImageSourceRef CreateCGImageSourceFromRWops(SDL_RWops* rw_ops, CFDictionaryRef hints_and_options)
    79 {
    80     CGImageSourceRef source_ref;
    81 
    82     // Similar to SDL_RWops, Apple has their own callbacks for dealing with data streams.
    83 
    84 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated
    85     CGDataProviderSequentialCallbacks provider_callbacks =
    86     {
    87         0,
    88         MyProviderGetBytesCallback,
    89         MyProviderSkipForwardBytesCallback,
    90         MyProviderRewindCallback,
    91         MyProviderReleaseInfoCallback
    92     };
    93 
    94     CGDataProviderRef data_provider = CGDataProviderCreateSequential(rw_ops, &provider_callbacks);
    95 
    96 
    97 #else // CGDataProviderCreate was deprecated in 10.5
    98 
    99     CGDataProviderCallbacks provider_callbacks =
   100     {
   101         MyProviderGetBytesCallback,
   102         MyProviderSkipBytesCallback,
   103         MyProviderRewindCallback,
   104         MyProviderReleaseInfoCallback
   105     };
   106 
   107     CGDataProviderRef data_provider = CGDataProviderCreate(rw_ops, &provider_callbacks);
   108 #endif
   109     // Get the CGImageSourceRef.
   110     // The dictionary can be NULL or contain hints to help ImageIO figure out the image type.
   111     source_ref = CGImageSourceCreateWithDataProvider(data_provider, hints_and_options);
   112     CGDataProviderRelease(data_provider);
   113     return source_ref;
   114 }
   115 
   116 /* Create a CGImageSourceRef from a file. */
   117 /* Remember to CFRelease the created source when done. */
   118 static CGImageSourceRef CreateCGImageSourceFromFile(const char* the_path)
   119 {
   120     CFURLRef the_url = NULL;
   121     CGImageSourceRef source_ref = NULL;
   122     CFStringRef cf_string = NULL;
   123 
   124     /* Create a CFString from a C string */
   125     cf_string = CFStringCreateWithCString(NULL, the_path, kCFStringEncodingUTF8);
   126     if (!cf_string) {
   127         return NULL;
   128     }
   129 
   130     /* Create a CFURL from a CFString */
   131     the_url = CFURLCreateWithFileSystemPath(NULL, cf_string, kCFURLPOSIXPathStyle, false);
   132 
   133     /* Don't need the CFString any more (error or not) */
   134     CFRelease(cf_string);
   135 
   136     if(!the_url)
   137     {
   138         return NULL;
   139     }
   140 
   141 
   142     source_ref = CGImageSourceCreateWithURL(the_url, NULL);
   143     /* Don't need the URL any more (error or not) */
   144     CFRelease(the_url);
   145 
   146     return source_ref;
   147 }
   148 
   149 static CGImageRef CreateCGImageFromCGImageSource(CGImageSourceRef image_source)
   150 {
   151     CGImageRef image_ref = NULL;
   152 
   153     if(NULL == image_source)
   154     {
   155         return NULL;
   156     }
   157 
   158     // Get the first item in the image source (some image formats may
   159     // contain multiple items).
   160     image_ref = CGImageSourceCreateImageAtIndex(image_source, 0, NULL);
   161     if(NULL == image_ref)
   162     {
   163         IMG_SetError("CGImageSourceCreateImageAtIndex() failed");
   164     }
   165     return image_ref;
   166 }
   167 
   168 static CFDictionaryRef CreateHintDictionary(CFStringRef uti_string_hint)
   169 {
   170     CFDictionaryRef hint_dictionary = NULL;
   171 
   172     if(uti_string_hint != NULL)
   173     {
   174         // Do a bunch of work to setup a CFDictionary containing the jpeg compression properties.
   175         CFStringRef the_keys[1];
   176         CFStringRef the_values[1];
   177 
   178         the_keys[0] = kCGImageSourceTypeIdentifierHint;
   179         the_values[0] = uti_string_hint;
   180 
   181         // kCFTypeDictionaryKeyCallBacks or kCFCopyStringDictionaryKeyCallBacks?
   182         hint_dictionary = CFDictionaryCreate(NULL, (const void**)&the_keys, (const void**)&the_values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   183     }
   184     return hint_dictionary;
   185 }
   186 
   187 // Once we have our image, we need to get it into an SDL_Surface
   188 static SDL_Surface* Create_SDL_Surface_From_CGImage_RGB(CGImageRef image_ref)
   189 {
   190     /* This code is adapted from Apple's Documentation found here:
   191      * http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/index.html
   192      * Listing 9-4††Using a Quartz image as a texture source.
   193      * Unfortunately, this guide doesn't show what to do about
   194      * non-RGBA image formats so I'm making the rest up.
   195      * All this code should be scrutinized.
   196      */
   197 
   198     size_t w = CGImageGetWidth(image_ref);
   199     size_t h = CGImageGetHeight(image_ref);
   200     CGRect rect = {{0, 0}, {w, h}};
   201 
   202     CGImageAlphaInfo alpha = CGImageGetAlphaInfo(image_ref);
   203     //size_t bits_per_pixel = CGImageGetBitsPerPixel(image_ref);
   204     size_t bits_per_component = 8;
   205 
   206     SDL_Surface* surface;
   207     Uint32 Amask;
   208     Uint32 Rmask;
   209     Uint32 Gmask;
   210     Uint32 Bmask;
   211 
   212     CGContextRef bitmap_context;
   213     CGBitmapInfo bitmap_info;
   214     CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
   215 
   216     if (alpha == kCGImageAlphaNone ||
   217         alpha == kCGImageAlphaNoneSkipFirst ||
   218         alpha == kCGImageAlphaNoneSkipLast) {
   219         bitmap_info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; /* XRGB */
   220         Amask = 0x00000000;
   221     } else {
   222         /* kCGImageAlphaFirst isn't supported */
   223         //bitmap_info = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; /* ARGB */
   224         bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; /* ARGB */
   225         Amask = 0xFF000000;
   226     }
   227 
   228     Rmask = 0x00FF0000;
   229     Gmask = 0x0000FF00;
   230     Bmask = 0x000000FF;
   231 
   232     surface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, Rmask, Gmask, Bmask, Amask);
   233     if (surface)
   234     {
   235         // Sets up a context to be drawn to with surface->pixels as the area to be drawn to
   236         bitmap_context = CGBitmapContextCreate(
   237                                                surface->pixels,
   238                                                surface->w,
   239                                                surface->h,
   240                                                bits_per_component,
   241                                                surface->pitch,
   242                                                color_space,
   243                                                bitmap_info
   244                                                );
   245 
   246         // Draws the image into the context's image_data
   247         CGContextDrawImage(bitmap_context, rect, image_ref);
   248 
   249         CGContextRelease(bitmap_context);
   250 
   251         // FIXME: Reverse the premultiplied alpha
   252         if ((bitmap_info & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst) {
   253             int i, j;
   254             Uint8 *p = (Uint8 *)surface->pixels;
   255             for (i = surface->h * surface->pitch/4; i--; ) {
   256 #if __LITTLE_ENDIAN__
   257                 Uint8 A = p[3];
   258                 if (A) {
   259                     for (j = 0; j < 3; ++j) {
   260                         p[j] = (p[j] * 255) / A;
   261                     }
   262                 }
   263 #else
   264                 Uint8 A = p[0];
   265                 if (A) {
   266                     for (j = 1; j < 4; ++j) {
   267                         p[j] = (p[j] * 255) / A;
   268                     }
   269                 }
   270 #endif /* ENDIAN */
   271                 p += 4;
   272             }
   273         }
   274     }
   275 
   276     if (color_space)
   277     {
   278         CGColorSpaceRelease(color_space);
   279     }
   280 
   281     return surface;
   282 }
   283 static SDL_Surface* Create_SDL_Surface_From_CGImage_Index(CGImageRef image_ref)
   284 {
   285     size_t w = CGImageGetWidth(image_ref);
   286     size_t h = CGImageGetHeight(image_ref);
   287     size_t bits_per_pixel = CGImageGetBitsPerPixel(image_ref);
   288     size_t bytes_per_row = CGImageGetBytesPerRow(image_ref);
   289 
   290     SDL_Surface* surface;
   291     SDL_Palette* palette;
   292     CGColorSpaceRef color_space = CGImageGetColorSpace(image_ref);
   293     CGColorSpaceRef base_color_space = CGColorSpaceGetBaseColorSpace(color_space);
   294     size_t num_components = CGColorSpaceGetNumberOfComponents(base_color_space);
   295     size_t num_entries = CGColorSpaceGetColorTableCount(color_space);
   296     uint8_t *entry, entries[num_components * num_entries];
   297 
   298     /* What do we do if it's not RGB? */
   299     if (num_components != 3) {
   300         SDL_SetError("Unknown colorspace components %lu", num_components);
   301         return NULL;
   302     }
   303     if (bits_per_pixel != 8) {
   304         SDL_SetError("Unknown bits_per_pixel %lu", bits_per_pixel);
   305         return NULL;
   306     }
   307 
   308     CGColorSpaceGetColorTable(color_space, entries);
   309     surface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bits_per_pixel, 0, 0, 0, 0);
   310     if (surface) {
   311         uint8_t* pixels = (uint8_t*)surface->pixels;
   312         CGDataProviderRef provider = CGImageGetDataProvider(image_ref);
   313         NSData* data = (id)CGDataProviderCopyData(provider);
   314         [data autorelease];
   315         const uint8_t* bytes = [data bytes];
   316         size_t i;
   317 
   318         palette = surface->format->palette;
   319         for (i = 0, entry = entries; i < num_entries; ++i) {
   320             palette->colors[i].r = entry[0];
   321             palette->colors[i].g = entry[1];
   322             palette->colors[i].b = entry[2];
   323             entry += num_components;
   324         }
   325 
   326         for (i = 0; i < h; ++i) {
   327             SDL_memcpy(pixels, bytes, w);
   328             pixels += surface->pitch;
   329             bytes += bytes_per_row;
   330         }
   331     }
   332     return surface;
   333 }
   334 static SDL_Surface* Create_SDL_Surface_From_CGImage(CGImageRef image_ref)
   335 {
   336     CGColorSpaceRef color_space = CGImageGetColorSpace(image_ref);
   337     if (CGColorSpaceGetModel(color_space) == kCGColorSpaceModelIndexed) {
   338         return Create_SDL_Surface_From_CGImage_Index(image_ref);
   339     } else {
   340         return Create_SDL_Surface_From_CGImage_RGB(image_ref);
   341     }
   342 }
   343 
   344 
   345 #pragma mark -
   346 #pragma mark IMG_Init stubs
   347 #if !defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1))
   348 static int Internal_checkImageIOisAvailable() {
   349     // just check if we are running on ios 4 or more, else throw exception
   350     if ([UIImage instancesRespondToSelector:@selector(initWithCGImage:scale:orientation:)])
   351         return 0;
   352     [NSException raise:@"UIImage fallback not enabled at compile time"
   353                 format:@"ImageIO is not available on your platform, please recompile SDL_Image with ALLOW_UIIMAGE_FALLBACK."];
   354     return -1;
   355 }
   356 #endif
   357 
   358 int IMG_InitJPG()
   359 {
   360     return 0;
   361 }
   362 
   363 void IMG_QuitJPG()
   364 {
   365 }
   366 
   367 int IMG_InitPNG()
   368 {
   369     return 0;
   370 }
   371 
   372 void IMG_QuitPNG()
   373 {
   374 }
   375 
   376 int IMG_InitTIF()
   377 {
   378     return 0;
   379 }
   380 
   381 void IMG_QuitTIF()
   382 {
   383 }
   384 
   385 #pragma mark -
   386 #pragma mark Get type of image
   387 static int Internal_isType_UIImage (SDL_RWops *rw_ops, CFStringRef uti_string_to_test)
   388 {
   389     int is_type = 0;
   390 
   391 #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1))
   392     int start = SDL_RWtell(rw_ops);
   393     if ((0 == CFStringCompare(uti_string_to_test, kUTTypeICO, 0)) ||
   394         (0 == CFStringCompare(uti_string_to_test, CFSTR("com.microsoft.cur"), 0))) {
   395 
   396         // The Win32 ICO file header (14 bytes)
   397         Uint16 bfReserved;
   398         Uint16 bfType;
   399         Uint16 bfCount;
   400         int type = (0 == CFStringCompare(uti_string_to_test, kUTTypeICO, 0)) ? 1 : 2;
   401 
   402         bfReserved = SDL_ReadLE16(rw_ops);
   403         bfType = SDL_ReadLE16(rw_ops);
   404         bfCount = SDL_ReadLE16(rw_ops);
   405         if ((bfReserved == 0) && (bfType == type) && (bfCount != 0))
   406             is_type = 1;
   407     } else if (0 == CFStringCompare(uti_string_to_test, kUTTypeBMP, 0)) {
   408         char magic[2];
   409 
   410         if ( SDL_RWread(rw_ops, magic, sizeof(magic), 1) ) {
   411             if ( strncmp(magic, "BM", 2) == 0 ) {
   412                 is_type = 1;
   413             }
   414         }
   415     } else if (0 == CFStringCompare(uti_string_to_test, kUTTypeGIF, 0)) {
   416         char magic[6];
   417 
   418         if ( SDL_RWread(rw_ops, magic, sizeof(magic), 1) ) {
   419             if ( (strncmp(magic, "GIF", 3) == 0) &&
   420                 ((memcmp(magic + 3, "87a", 3) == 0) ||
   421                  (memcmp(magic + 3, "89a", 3) == 0)) ) {
   422                     is_type = 1;
   423                 }
   424         }
   425     } else if (0 == CFStringCompare(uti_string_to_test, kUTTypeJPEG, 0)) {
   426         int in_scan = 0;
   427         Uint8 magic[4];
   428 
   429         // This detection code is by Steaphan Greene <stea@cs.binghamton.edu>
   430         // Blame me, not Sam, if this doesn't work right. */
   431         // And don't forget to report the problem to the the sdl list too! */
   432 
   433         if ( SDL_RWread(rw_ops, magic, 2, 1) ) {
   434             if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
   435                 is_type = 1;
   436                 while (is_type == 1) {
   437                     if(SDL_RWread(rw_ops, magic, 1, 2) != 2) {
   438                         is_type = 0;
   439                     } else if( (magic[0] != 0xFF) && (in_scan == 0) ) {
   440                         is_type = 0;
   441                     } else if( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
   442                         /* Extra padding in JPEG (legal) */
   443                         /* or this is data and we are scanning */
   444                         SDL_RWseek(rw_ops, -1, SEEK_CUR);
   445                     } else if(magic[1] == 0xD9) {
   446                         /* Got to end of good JPEG */
   447                         break;
   448                     } else if( (in_scan == 1) && (magic[1] == 0x00) ) {
   449                         /* This is an encoded 0xFF within the data */
   450                     } else if( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
   451                         /* These have nothing else */
   452                     } else if(SDL_RWread(rw_ops, magic+2, 1, 2) != 2) {
   453                         is_type = 0;
   454                     } else {
   455                         /* Yes, it's big-endian */
   456                         Uint32 start;
   457                         Uint32 size;
   458                         Uint32 end;
   459                         start = SDL_RWtell(rw_ops);
   460                         size = (magic[2] << 8) + magic[3];
   461                         end = SDL_RWseek(rw_ops, size-2, SEEK_CUR);
   462                         if ( end != start + size - 2 ) is_type = 0;
   463                         if ( magic[1] == 0xDA ) {
   464                             /* Now comes the actual JPEG meat */
   465 #ifdef  FAST_IS_JPEG
   466                             /* Ok, I'm convinced.  It is a JPEG. */
   467                             break;
   468 #else
   469                             /* I'm not convinced.  Prove it! */
   470                             in_scan = 1;
   471 #endif
   472                         }
   473                     }
   474                 }
   475             }
   476         }
   477     } else if (0 == CFStringCompare(uti_string_to_test, kUTTypePNG, 0)) {
   478         Uint8 magic[4];
   479 
   480         if ( SDL_RWread(rw_ops, magic, 1, sizeof(magic)) == sizeof(magic) ) {
   481             if ( magic[0] == 0x89 &&
   482                 magic[1] == 'P' &&
   483                 magic[2] == 'N' &&
   484                 magic[3] == 'G' ) {
   485                 is_type = 1;
   486             }
   487         }
   488     } else if (0 == CFStringCompare(uti_string_to_test, CFSTR("com.truevision.tga-image"), 0)) {
   489         //TODO: fill me!
   490     } else if (0 == CFStringCompare(uti_string_to_test, kUTTypeTIFF, 0)) {
   491         Uint8 magic[4];
   492 
   493         if ( SDL_RWread(rw_ops, magic, 1, sizeof(magic)) == sizeof(magic) ) {
   494             if ( (magic[0] == 'I' &&
   495                   magic[1] == 'I' &&
   496                   magic[2] == 0x2a &&
   497                   magic[3] == 0x00) ||
   498                 (magic[0] == 'M' &&
   499                  magic[1] == 'M' &&
   500                  magic[2] == 0x00 &&
   501                  magic[3] == 0x2a) ) {
   502                     is_type = 1;
   503                 }
   504         }
   505     }
   506 
   507     // reset the file descption pointer
   508     SDL_RWseek(rw_ops, start, SEEK_SET);
   509 
   510 #endif  /* #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)) */
   511     return is_type;
   512 }
   513 
   514 static int Internal_isType_ImageIO (SDL_RWops *rw_ops, CFStringRef uti_string_to_test)
   515 {
   516     int is_type = 0;
   517 
   518     CFDictionaryRef hint_dictionary = CreateHintDictionary(uti_string_to_test);
   519     CGImageSourceRef image_source = CreateCGImageSourceFromRWops(rw_ops, hint_dictionary);
   520 
   521     if (hint_dictionary != NULL) {
   522         CFRelease(hint_dictionary);
   523     }
   524 
   525     if (NULL == image_source) {
   526         return 0;
   527     }
   528 
   529     // This will get the UTI of the container, not the image itself.
   530     // Under most cases, this won't be a problem.
   531     // But if a person passes an icon file which contains a bmp,
   532     // the format will be of the icon file.
   533     // But I think the main SDL_image codebase has this same problem so I'm not going to worry about it.
   534     CFStringRef uti_type = CGImageSourceGetType(image_source);
   535     //  CFShow(uti_type);
   536 
   537     // Unsure if we really want conformance or equality
   538     is_type = (int)UTTypeConformsTo(uti_string_to_test, uti_type);
   539 
   540     CFRelease(image_source);
   541     return is_type;
   542 }
   543 
   544 static int Internal_isType (SDL_RWops *rw_ops, CFStringRef uti_string_to_test)
   545 {
   546     if (rw_ops == NULL)
   547         return 0;
   548 
   549 #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
   550     if (USE_UIIMAGE_BACKEND())
   551         return Internal_isType_UIImage(rw_ops, uti_string_to_test);
   552     else
   553 #endif
   554         return Internal_isType_ImageIO(rw_ops, uti_string_to_test);
   555 }
   556 
   557 #ifdef BMP_USES_IMAGEIO
   558 
   559 int IMG_isCUR(SDL_RWops *src)
   560 {
   561     /* FIXME: Is this a supported type? */
   562     return Internal_isType(src, CFSTR("com.microsoft.cur"));
   563 }
   564 
   565 int IMG_isICO(SDL_RWops *src)
   566 {
   567     return Internal_isType(src, kUTTypeICO);
   568 }
   569 
   570 int IMG_isBMP(SDL_RWops *src)
   571 {
   572     return Internal_isType(src, kUTTypeBMP);
   573 }
   574 
   575 #endif /* BMP_USES_IMAGEIO */
   576 
   577 int IMG_isGIF(SDL_RWops *src)
   578 {
   579     return Internal_isType(src, kUTTypeGIF);
   580 }
   581 
   582 // Note: JPEG 2000 is kUTTypeJPEG2000
   583 int IMG_isJPG(SDL_RWops *src)
   584 {
   585     return Internal_isType(src, kUTTypeJPEG);
   586 }
   587 
   588 int IMG_isPNG(SDL_RWops *src)
   589 {
   590     return Internal_isType(src, kUTTypePNG);
   591 }
   592 
   593 // This isn't a public API function. Apple seems to be able to identify tga's.
   594 int IMG_isTGA(SDL_RWops *src)
   595 {
   596     return Internal_isType(src, CFSTR("com.truevision.tga-image"));
   597 }
   598 
   599 int IMG_isTIF(SDL_RWops *src)
   600 {
   601     return Internal_isType(src, kUTTypeTIFF);
   602 }
   603 
   604 #pragma mark -
   605 #pragma mark Load image engine
   606 static SDL_Surface *LoadImageFromRWops_UIImage (SDL_RWops* rw_ops, CFStringRef uti_string_hint)
   607 {
   608     SDL_Surface *sdl_surface = NULL;
   609 
   610 #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1))
   611     NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
   612     UIImage *ui_image;
   613     int bytes_read = 0;
   614     // I don't know what a good size is.
   615     // Max recommended texture size is 1024x1024 on iPhone so maybe base it on that?
   616     const int block_size = 1024*4;
   617     char temp_buffer[block_size];
   618 
   619     NSMutableData* ns_data = [[NSMutableData alloc] initWithCapacity:1024*1024*4];
   620     do {
   621         bytes_read = SDL_RWread(rw_ops, temp_buffer, 1, block_size);
   622         [ns_data appendBytes:temp_buffer length:bytes_read];
   623     } while (bytes_read > 0);
   624 
   625     ui_image = [[UIImage alloc] initWithData:ns_data];
   626     if (ui_image != nil)
   627         sdl_surface = Create_SDL_Surface_From_CGImage([ui_image CGImage]);
   628     [ui_image release];
   629     [ns_data release];
   630     [autorelease_pool drain];
   631 
   632 #endif  /* #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)) */
   633     return sdl_surface;
   634 }
   635 
   636 static SDL_Surface *LoadImageFromRWops_ImageIO (SDL_RWops *rw_ops, CFStringRef uti_string_hint)
   637 {
   638     CFDictionaryRef hint_dictionary = CreateHintDictionary(uti_string_hint);
   639     CGImageSourceRef image_source = CreateCGImageSourceFromRWops(rw_ops, hint_dictionary);
   640 
   641     if (hint_dictionary != NULL)
   642         CFRelease(hint_dictionary);
   643 
   644     if (NULL == image_source)
   645         return NULL;
   646 
   647     CGImageRef image_ref = CreateCGImageFromCGImageSource(image_source);
   648     CFRelease(image_source);
   649 
   650     if (NULL == image_ref)
   651         return NULL;
   652     SDL_Surface *sdl_surface = Create_SDL_Surface_From_CGImage(image_ref);
   653     CFRelease(image_ref);
   654 
   655     return sdl_surface;
   656 }
   657 
   658 static SDL_Surface *LoadImageFromRWops (SDL_RWops *rw_ops, CFStringRef uti_string_hint)
   659 {
   660 #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
   661     if (USE_UIIMAGE_BACKEND())
   662         return LoadImageFromRWops_UIImage(rw_ops, uti_string_hint);
   663     else
   664 #endif
   665         return LoadImageFromRWops_ImageIO(rw_ops, uti_string_hint);
   666 }
   667 
   668 static SDL_Surface* LoadImageFromFile_UIImage (const char *file)
   669 {
   670     SDL_Surface *sdl_surface = NULL;
   671 
   672 #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1))
   673     NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
   674     NSString *ns_string = [[NSString alloc] initWithUTF8String:file];
   675     UIImage *ui_image = [[UIImage alloc] initWithContentsOfFile:ns_string];
   676     if (ui_image != nil)
   677         sdl_surface = Create_SDL_Surface_From_CGImage([ui_image CGImage]);
   678     [ui_image release];
   679     [ns_string release];
   680     [autorelease_pool drain];
   681 
   682 #endif  /* #if defined(ALLOW_UIIMAGE_FALLBACK) && ((TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)) */
   683     return sdl_surface;
   684 }
   685 
   686 static SDL_Surface* LoadImageFromFile_ImageIO (const char *file)
   687 {
   688     CGImageSourceRef image_source = NULL;
   689 
   690     image_source = CreateCGImageSourceFromFile(file);
   691 
   692     if(NULL == image_source)
   693         return NULL;
   694 
   695     CGImageRef image_ref = CreateCGImageFromCGImageSource(image_source);
   696     CFRelease(image_source);
   697 
   698     if (NULL == image_ref)
   699         return NULL;
   700     SDL_Surface *sdl_surface = Create_SDL_Surface_From_CGImage(image_ref);
   701     CFRelease(image_ref);
   702     return sdl_surface;
   703 }
   704 
   705 static SDL_Surface* LoadImageFromFile (const char *file)
   706 {
   707 #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1)
   708     if (USE_UIIMAGE_BACKEND())
   709         return LoadImageFromFile_UIImage(file);
   710     else
   711 #endif
   712         return LoadImageFromFile_ImageIO(file);
   713 }
   714 
   715 #ifdef BMP_USES_IMAGEIO
   716 
   717 SDL_Surface* IMG_LoadCUR_RW (SDL_RWops *src)
   718 {
   719     /* FIXME: Is this a supported type? */
   720     return LoadImageFromRWops(src, CFSTR("com.microsoft.cur"));
   721 }
   722 
   723 SDL_Surface* IMG_LoadICO_RW (SDL_RWops *src)
   724 {
   725     return LoadImageFromRWops(src, kUTTypeICO);
   726 }
   727 
   728 SDL_Surface* IMG_LoadBMP_RW (SDL_RWops *src)
   729 {
   730     return LoadImageFromRWops(src, kUTTypeBMP);
   731 }
   732 
   733 #endif /* BMP_USES_IMAGEIO */
   734 
   735 SDL_Surface* IMG_LoadGIF_RW (SDL_RWops *src)
   736 {
   737     return LoadImageFromRWops (src, kUTTypeGIF);
   738 }
   739 
   740 SDL_Surface* IMG_LoadJPG_RW (SDL_RWops *src)
   741 {
   742     return LoadImageFromRWops (src, kUTTypeJPEG);
   743 }
   744 
   745 SDL_Surface* IMG_LoadPNG_RW (SDL_RWops *src)
   746 {
   747     return LoadImageFromRWops (src, kUTTypePNG);
   748 }
   749 
   750 SDL_Surface* IMG_LoadTGA_RW (SDL_RWops *src)
   751 {
   752     return LoadImageFromRWops(src, CFSTR("com.truevision.tga-image"));
   753 }
   754 
   755 SDL_Surface* IMG_LoadTIF_RW (SDL_RWops *src)
   756 {
   757     return LoadImageFromRWops(src, kUTTypeTIFF);
   758 }
   759 
   760 // Since UIImage doesn't really support streams well, we should optimize for the file case.
   761 // Apple provides both stream and file loading functions in ImageIO.
   762 // Potentially, Apple can optimize for either case.
   763 SDL_Surface* IMG_Load (const char *file)
   764 {
   765     SDL_Surface* sdl_surface = NULL;
   766 
   767     sdl_surface = LoadImageFromFile(file);
   768     if(NULL == sdl_surface)
   769     {
   770         // Either the file doesn't exist or ImageIO doesn't understand the format.
   771         // For the latter case, fallback to the native SDL_image handlers.
   772         SDL_RWops *src = SDL_RWFromFile(file, "rb");
   773         char *ext = strrchr(file, '.');
   774         if (ext) {
   775             ext++;
   776         }
   777         if (!src) {
   778             /* The error message has been set in SDL_RWFromFile */
   779             return NULL;
   780         }
   781         sdl_surface = IMG_LoadTyped_RW(src, 1, ext);
   782     }
   783     return sdl_surface;
   784 }
   785 
   786 #endif /* defined(__APPLE__) && !defined(SDL_IMAGE_USE_COMMON_BACKEND) */