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 // For ImageIO framework and also LaunchServices framework (for UTIs)
15 #include <ApplicationServices/ApplicationServices.h>
16 // Used because CGDataProviderCreate became deprecated in 10.5
17 #include <AvailabilityMacros.h>
18 
19 /**************************************************************
20  ***** Begin Callback functions for block reading *************
21  **************************************************************/
22 
23 // This callback reads some bytes from an SDL_rwops and copies it
24 // to a Quartz buffer (supplied by Apple framework).
MyProviderGetBytesCallback(void * rwops_userdata,void * quartz_buffer,size_t the_count)25 static size_t MyProviderGetBytesCallback(void* rwops_userdata, void* quartz_buffer, size_t the_count)
26 {
27  return (size_t)SDL_RWread((struct SDL_RWops *)rwops_userdata, quartz_buffer, 1, the_count);
28 }
29 
30 // This callback is triggered when the data provider is released
31 // so you can clean up any resources.
MyProviderReleaseInfoCallback(void * rwops_userdata)32 static void MyProviderReleaseInfoCallback(void* rwops_userdata)
33 {
34  // What should I put here?
35  // I think the user and SDL_RWops controls closing, so I don't do anything.
36 }
37 
MyProviderRewindCallback(void * rwops_userdata)38 static void MyProviderRewindCallback(void* rwops_userdata)
39 {
40  SDL_RWseek((struct SDL_RWops *)rwops_userdata, 0, RW_SEEK_SET);
41 }
42 
43 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated
MyProviderSkipForwardBytesCallback(void * rwops_userdata,off_t the_count)44 off_t MyProviderSkipForwardBytesCallback(void* rwops_userdata, off_t the_count)
45 {
46  off_t start_position = SDL_RWtell((struct SDL_RWops *)rwops_userdata);
47  SDL_RWseek((struct SDL_RWops *)rwops_userdata, the_count, RW_SEEK_CUR);
48     off_t end_position = SDL_RWtell((struct SDL_RWops *)rwops_userdata);
49     return (end_position - start_position);
50 }
51 #else // CGDataProviderCreate was deprecated in 10.5
MyProviderSkipBytesCallback(void * rwops_userdata,size_t the_count)52 static void MyProviderSkipBytesCallback(void* rwops_userdata, size_t the_count)
53 {
54  SDL_RWseek((struct SDL_RWops *)rwops_userdata, the_count, RW_SEEK_CUR);
55 }
56 #endif
57 
58 
59 /**************************************************************
60  ***** End Callback functions for block reading ***************
61  **************************************************************/
62 
63 // This creates a CGImageSourceRef which is a handle to an image that can be used to examine information
64 // about the image or load the actual image data.
CreateCGImageSourceFromRWops(SDL_RWops * rw_ops,CFDictionaryRef hints_and_options)65 static CGImageSourceRef CreateCGImageSourceFromRWops(SDL_RWops* rw_ops, CFDictionaryRef hints_and_options)
66 {
67  CGImageSourceRef source_ref;
68 
69  // Similar to SDL_RWops, Apple has their own callbacks for dealing with data streams.
70 
71 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 // CGDataProviderCreateSequential was introduced in 10.5; CGDataProviderCreate is deprecated
72  CGDataProviderSequentialCallbacks provider_callbacks =
73  {
74         0,
75    MyProviderGetBytesCallback,
76    MyProviderSkipForwardBytesCallback,
77    MyProviderRewindCallback,
78    MyProviderReleaseInfoCallback
79  };
80 
81  CGDataProviderRef data_provider = CGDataProviderCreateSequential(rw_ops, &provider_callbacks);
82 
83 
84 #else // CGDataProviderCreate was deprecated in 10.5
85 
86  CGDataProviderCallbacks provider_callbacks =
87  {
88    MyProviderGetBytesCallback,
89    MyProviderSkipBytesCallback,
90    MyProviderRewindCallback,
91    MyProviderReleaseInfoCallback
92  };
93 
94  CGDataProviderRef data_provider = CGDataProviderCreate(rw_ops, &provider_callbacks);
95 #endif
96  // Get the CGImageSourceRef.
97  // The dictionary can be NULL or contain hints to help ImageIO figure out the image type.
98  source_ref = CGImageSourceCreateWithDataProvider(data_provider, hints_and_options);
99  return source_ref;
100 }
101 
102 
103 /* Create a CGImageSourceRef from a file. */
104 /* Remember to CFRelease the created source when done. */
CreateCGImageSourceFromFile(const char * the_path)105 static CGImageSourceRef CreateCGImageSourceFromFile(const char* the_path)
106 {
107     CFURLRef the_url = NULL;
108     CGImageSourceRef source_ref = NULL;
109  CFStringRef cf_string = NULL;
110 
111  /* Create a CFString from a C string */
112  cf_string = CFStringCreateWithCString(
113                      NULL,
114                      the_path,
115                      kCFStringEncodingUTF8
116                      );
117  if(!cf_string)
118  {
119    return NULL;
120  }
121 
122  /* Create a CFURL from a CFString */
123     the_url = CFURLCreateWithFileSystemPath(
124                      NULL,
125                      cf_string,
126                      kCFURLPOSIXPathStyle,
127                      false
128                      );
129 
130  /* Don't need the CFString any more (error or not) */
131  CFRelease(cf_string);
132 
133  if(!the_url)
134  {
135    return NULL;
136  }
137 
138 
139     source_ref = CGImageSourceCreateWithURL(the_url, NULL);
140  /* Don't need the URL any more (error or not) */
141  CFRelease(the_url);
142 
143  return source_ref;
144 }
145 
146 
147 
CreateCGImageFromCGImageSource(CGImageSourceRef image_source)148 static CGImageRef CreateCGImageFromCGImageSource(CGImageSourceRef image_source)
149 {
150  CGImageRef image_ref = NULL;
151 
152     if(NULL == image_source)
153  {
154    return NULL;
155  }
156 
157  // Get the first item in the image source (some image formats may
158  // contain multiple items).
159  image_ref = CGImageSourceCreateImageAtIndex(image_source, 0, NULL);
160  return image_ref;
161 }
162 
CreateHintDictionary(CFStringRef uti_string_hint)163 static CFDictionaryRef CreateHintDictionary(CFStringRef uti_string_hint)
164 {
165  CFDictionaryRef hint_dictionary = NULL;
166 
167  if(uti_string_hint != NULL)
168  {
169    // Do a bunch of work to setup a CFDictionary containing the jpeg compression properties.
170    CFStringRef the_keys[1];
171    CFStringRef the_values[1];
172 
173    the_keys[0] = kCGImageSourceTypeIdentifierHint;
174    the_values[0] = uti_string_hint;
175 
176    // kCFTypeDictionaryKeyCallBacks or kCFCopyStringDictionaryKeyCallBacks?
177    hint_dictionary = CFDictionaryCreate(NULL, (const void**)&the_keys, (const void**)&the_values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
178  }
179  return hint_dictionary;
180 }
181 
182 
183 
184 
Internal_isType(SDL_RWops * rw_ops,CFStringRef uti_string_to_test)185 static int Internal_isType(SDL_RWops* rw_ops, CFStringRef uti_string_to_test)
186 {
187  CGImageSourceRef image_source;
188  CFStringRef uti_type;
189  Boolean is_type;
190 
191  CFDictionaryRef hint_dictionary = NULL;
192 
193  hint_dictionary = CreateHintDictionary(uti_string_to_test);
194  image_source = CreateCGImageSourceFromRWops(rw_ops, hint_dictionary);
195 
196  if(hint_dictionary != NULL)
197  {
198    CFRelease(hint_dictionary);
199  }
200 
201  if(NULL == image_source)
202  {
203    return 0;
204  }
205 
206  // This will get the UTI of the container, not the image itself.
207  // Under most cases, this won't be a problem.
208  // But if a person passes an icon file which contains a bmp,
209  // the format will be of the icon file.
210  // But I think the main SDL_image codebase has this same problem so I'm not going to worry about it.
211  uti_type = CGImageSourceGetType(image_source);
212  //  CFShow(uti_type);
213 
214  // Unsure if we really want conformance or equality
215  is_type = UTTypeConformsTo(uti_string_to_test, uti_type);
216 
217  CFRelease(image_source);
218 
219  return (int)is_type;
220 }
221 
222 // Once we have our image, we need to get it into an SDL_Surface
Create_SDL_Surface_From_CGImage(CGImageRef image_ref)223 static SDL_Surface* Create_SDL_Surface_From_CGImage(CGImageRef image_ref)
224 {
225  /* This code is adapted from Apple's Documentation found here:
226   * http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/index.html
227   * Listing 9-4††Using a Quartz image as a texture source.
228   * Unfortunately, this guide doesn't show what to do about
229   * non-RGBA image formats so I'm making the rest up.
230   * All this code should be scrutinized.
231   */
232 
233  size_t w = CGImageGetWidth(image_ref);
234  size_t h = CGImageGetHeight(image_ref);
235  CGRect rect = {{0, 0}, {w, h}};
236 
237  CGImageAlphaInfo alpha = CGImageGetAlphaInfo(image_ref);
238  //size_t bits_per_pixel = CGImageGetBitsPerPixel(image_ref);
239  size_t bits_per_component = 8;
240 
241  SDL_Surface* surface;
242  Uint32 Amask;
243  Uint32 Rmask;
244  Uint32 Gmask;
245  Uint32 Bmask;
246 
247  CGContextRef bitmap_context;
248  CGBitmapInfo bitmap_info;
249  CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
250 
251  if (alpha == kCGImageAlphaNone ||
252      alpha == kCGImageAlphaNoneSkipFirst ||
253      alpha == kCGImageAlphaNoneSkipLast) {
254    bitmap_info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; /* XRGB */
255    Amask = 0x00000000;
256  } else {
257    /* kCGImageAlphaFirst isn't supported */
258    //bitmap_info = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; /* ARGB */
259    bitmap_info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; /* ARGB */
260    Amask = 0xFF000000;
261  }
262 
263  Rmask = 0x00FF0000;
264  Gmask = 0x0000FF00;
265  Bmask = 0x000000FF;
266 
267  surface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, Rmask, Gmask, Bmask, Amask);
268  if (surface)
269  {
270    // Sets up a context to be drawn to with surface->pixels as the area to be drawn to
271    bitmap_context = CGBitmapContextCreate(
272                              surface->pixels,
273                              surface->w,
274                              surface->h,
275                              bits_per_component,
276                              surface->pitch,
277                              color_space,
278                              bitmap_info
279                              );
280 
281    // Draws the image into the context's image_data
282    CGContextDrawImage(bitmap_context, rect, image_ref);
283 
284    CGContextRelease(bitmap_context);
285 
286    // FIXME: Reverse the premultiplied alpha
287    if ((bitmap_info & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst) {
288      int i, j;
289      Uint8 *p = (Uint8 *)surface->pixels;
290      for (i = surface->h * surface->pitch/4; i--; ) {
291 #if __LITTLE_ENDIAN__
292        Uint8 A = p[3];
293        if (A) {
294          for (j = 0; j < 3; ++j) {
295            p[j] = (p[j] * 255) / A;
296          }
297        }
298 #else
299        Uint8 A = p[0];
300        if (A) {
301          for (j = 1; j < 4; ++j) {
302            p[j] = (p[j] * 255) / A;
303          }
304        }
305 #endif /* ENDIAN */
306        p += 4;
307      }
308    }
309  }
310 
311  if (color_space)
312  {
313    CGColorSpaceRelease(color_space);
314  }
315 
316  return surface;
317 }
318 
319 
LoadImageFromRWops(SDL_RWops * rw_ops,CFStringRef uti_string_hint)320 static SDL_Surface* LoadImageFromRWops(SDL_RWops* rw_ops, CFStringRef uti_string_hint)
321 {
322  SDL_Surface* sdl_surface;
323  CGImageSourceRef image_source;
324  CGImageRef image_ref = NULL;
325  CFDictionaryRef hint_dictionary = NULL;
326 
327  hint_dictionary = CreateHintDictionary(uti_string_hint);
328  image_source = CreateCGImageSourceFromRWops(rw_ops, hint_dictionary);
329 
330  if(hint_dictionary != NULL)
331  {
332    CFRelease(hint_dictionary);
333  }
334 
335  if(NULL == image_source)
336  {
337    return NULL;
338  }
339 
340  image_ref = CreateCGImageFromCGImageSource(image_source);
341  CFRelease(image_source);
342 
343  if(NULL == image_ref)
344  {
345    return NULL;
346  }
347 
348  sdl_surface = Create_SDL_Surface_From_CGImage(image_ref);
349  CFRelease(image_ref);
350  return sdl_surface;
351 
352 }
353 
354 
355 
LoadImageFromFile(const char * file)356 static SDL_Surface* LoadImageFromFile(const char* file)
357 {
358  SDL_Surface* sdl_surface = NULL;
359  CGImageSourceRef image_source = NULL;
360  CGImageRef image_ref = NULL;
361 
362  // First ImageIO
363  image_source = CreateCGImageSourceFromFile(file);
364 
365  if(NULL == image_source)
366  {
367    return NULL;
368  }
369 
370  image_ref = CreateCGImageFromCGImageSource(image_source);
371  CFRelease(image_source);
372 
373  if(NULL == image_ref)
374  {
375    return NULL;
376  }
377 
378  sdl_surface = Create_SDL_Surface_From_CGImage(image_ref);
379  CFRelease(image_ref);
380  return sdl_surface;
381 }
382 
IMG_InitJPG()383 int IMG_InitJPG()
384 {
385  return 0;
386 }
387 
IMG_QuitJPG()388 void IMG_QuitJPG()
389 {
390 }
391 
IMG_InitPNG()392 int IMG_InitPNG()
393 {
394  return 0;
395 }
396 
IMG_QuitPNG()397 void IMG_QuitPNG()
398 {
399 }
400 
IMG_InitTIF()401 int IMG_InitTIF()
402 {
403  return 0;
404 }
405 
IMG_QuitTIF()406 void IMG_QuitTIF()
407 {
408 }
409 
IMG_isCUR(SDL_RWops * src)410 int IMG_isCUR(SDL_RWops *src)
411 {
412  /* FIXME: Is this a supported type? */
413  return Internal_isType(src, CFSTR("com.microsoft.cur"));
414 }
415 
IMG_isICO(SDL_RWops * src)416 int IMG_isICO(SDL_RWops *src)
417 {
418  return Internal_isType(src, kUTTypeICO);
419 }
420 
IMG_isBMP(SDL_RWops * src)421 int IMG_isBMP(SDL_RWops *src)
422 {
423  return Internal_isType(src, kUTTypeBMP);
424 }
425 
IMG_isGIF(SDL_RWops * src)426 int IMG_isGIF(SDL_RWops *src)
427 {
428  return Internal_isType(src, kUTTypeGIF);
429 }
430 
431 // Note: JPEG 2000 is kUTTypeJPEG2000
IMG_isJPG(SDL_RWops * src)432 int IMG_isJPG(SDL_RWops *src)
433 {
434  return Internal_isType(src, kUTTypeJPEG);
435 }
436 
IMG_isPNG(SDL_RWops * src)437 int IMG_isPNG(SDL_RWops *src)
438 {
439  return Internal_isType(src, kUTTypePNG);
440 }
441 
442 // This isn't a public API function. Apple seems to be able to identify tga's.
IMG_isTGA(SDL_RWops * src)443 int IMG_isTGA(SDL_RWops *src)
444 {
445  return Internal_isType(src, CFSTR("com.truevision.tga-image"));
446 }
447 
IMG_isTIF(SDL_RWops * src)448 int IMG_isTIF(SDL_RWops *src)
449 {
450  return Internal_isType(src, kUTTypeTIFF);
451 }
452 
IMG_LoadCUR_RW(SDL_RWops * src)453 SDL_Surface* IMG_LoadCUR_RW(SDL_RWops *src)
454 {
455  /* FIXME: Is this a supported type? */
456  return LoadImageFromRWops(src, CFSTR("com.microsoft.cur"));
457 }
IMG_LoadICO_RW(SDL_RWops * src)458 SDL_Surface* IMG_LoadICO_RW(SDL_RWops *src)
459 {
460  return LoadImageFromRWops(src, kUTTypeICO);
461 }
IMG_LoadBMP_RW(SDL_RWops * src)462 SDL_Surface* IMG_LoadBMP_RW(SDL_RWops *src)
463 {
464  return LoadImageFromRWops(src, kUTTypeBMP);
465 }
IMG_LoadGIF_RW(SDL_RWops * src)466 SDL_Surface* IMG_LoadGIF_RW(SDL_RWops *src)
467 {
468  return LoadImageFromRWops(src, kUTTypeGIF);
469 }
IMG_LoadJPG_RW(SDL_RWops * src)470 SDL_Surface* IMG_LoadJPG_RW(SDL_RWops *src)
471 {
472  return LoadImageFromRWops(src, kUTTypeJPEG);
473 }
IMG_LoadPNG_RW(SDL_RWops * src)474 SDL_Surface* IMG_LoadPNG_RW(SDL_RWops *src)
475 {
476  return LoadImageFromRWops(src, kUTTypePNG);
477 }
IMG_LoadTGA_RW(SDL_RWops * src)478 SDL_Surface* IMG_LoadTGA_RW(SDL_RWops *src)
479 {
480  return LoadImageFromRWops(src, CFSTR("com.truevision.tga-image"));
481 }
IMG_LoadTIF_RW(SDL_RWops * src)482 SDL_Surface* IMG_LoadTIF_RW(SDL_RWops *src)
483 {
484  return LoadImageFromRWops(src, kUTTypeTIFF);
485 }
486 
487 // Apple provides both stream and file loading functions in ImageIO.
488 // Potentially, Apple can optimize for either case.
IMG_Load(const char * file)489 SDL_Surface* IMG_Load(const char *file)
490 {
491  SDL_Surface* sdl_surface = NULL;
492 
493  sdl_surface = LoadImageFromFile(file);
494  if(NULL == sdl_surface)
495  {
496    // Either the file doesn't exist or ImageIO doesn't understand the format.
497    // For the latter case, fallback to the native SDL_image handlers.
498    SDL_RWops *src = SDL_RWFromFile(file, "rb");
499    char *ext = strrchr(file, '.');
500    if(ext) {
501      ext++;
502    }
503    if(!src) {
504      /* The error message has been set in SDL_RWFromFile */
505      return NULL;
506    }
507    sdl_surface = IMG_LoadTyped_RW(src, 1, ext);
508  }
509  return sdl_surface;
510 }
511 
512 #endif /* defined(__APPLE__) && !defined(SDL_IMAGE_USE_COMMON_BACKEND) */
513