1 #include <simage_quicktime.h>
2 
3 #include <Carbon/Carbon.h>
4 #include <ApplicationServices/ApplicationServices.h>
5 #include <QuickTime/ImageCompression.h>    /* for image loading */
6 #include <QuickTime/QuickTimeComponents.h> /* for file type support */
7 
8 #include <stdlib.h>
9 #include <sys/param.h>  /* for MAXPATHLEN */
10 
11 #define ERR_NO_ERROR    0   /* no error */
12 #define ERR_OPEN        1   /* could not open file */
13 #define ERR_CG          2   /* internal CG error */
14 #define ERR_WRITE       3   /* error writing file */
15 #define ERR_UNSUPPORTED 4   /* unsupported write format */
16 #define ERR_BAD_DEPTH   5   /* unsupported bit depth  */
17 #define ERR_MEM         6   /* out of memory */
18 
19 typedef struct {
20   size_t width;
21   size_t height;
22   size_t numcomponents;
23   size_t bitsPerPixel;
24   size_t bytesPerRow;
25   size_t size;
26   unsigned char * data;
27 } BitmapInfo;
28 
29 static int quicktimeerror = ERR_NO_ERROR;
30 
31 /* FIXME: Currently, all images are handled as 32bit (i.e. converted
32    on import). That seems to be the way to do it for 24bit and 32 bit
33    images, but should be done differently for 8bit images (though, note,
34    it works perfectly okay is it is.) For some reason, using
35    k8IndexedPixelFormat as an argument when creating the GWorld for
36    importing does not work. Investigate. kyrah 20030210
37 */
38 
39 /* Run-time endianness check - needed for ARGB <=> RGBA conversion in a
40    Universal Binary world.
41 
42    FIXME: This is duplicated from tidbits.c... if we ever get around
43    to moving shared code (e.g. dlopen abstraction, OpenGL checks etc)
44    into a separate library , this should go there as well. 20060221
45    kyrah.
46 */
47 
system_is_bigendian(void)48 static bool system_is_bigendian(void)
49 {
50   union temptype {
51     uint32_t value;
52     uint8_t  bytes[4];
53   } temp;
54 
55   temp.bytes[0] = 0x00;
56   temp.bytes[1] = 0x01;
57   temp.bytes[2] = 0x02;
58   temp.bytes[3] = 0x03;
59   switch (temp.value) {
60   case 0x03020100: return false;
61   case 0x00010203: return true;
62   default:
63     assert(0 && "system has unknown endianness");
64     return false;
65   }
66 }
67 
68 /* Mac OS 10.1 doesn't have basename() and dirname(), so we
69    have to use our own.
70    FIXME #1: Same problem exists in Coin, so this should be
71    shared code between Coin and simage.
72    FIXME #2: Use system dirname() and basename() on Mac OS 10.2
73    kyrah 20030723
74 */
75 
76 static char *
cc_basename(const char * path)77 cc_basename(const char * path)
78 {
79   static char base[MAXPATHLEN];
80   const char * sptr;
81   const char * eptr;
82 
83   if (path == NULL || *path == '\0') return NULL;
84 
85   /* Get rid of trailing '/'s */
86   eptr = path + strlen(path) - 1;
87   while (*eptr == '/' && path <= eptr) eptr--;
88 
89   if (eptr == path && *eptr == '/') {
90     strcpy(base, "/");
91     return(base);
92   }
93 
94   /* Go to beginning of base */
95   sptr = eptr;
96   while (sptr > path && *(sptr - 1) != '/') sptr--;
97 
98   if (eptr - sptr + 1 > sizeof(base)) {
99     return(NULL);
100   }
101 
102   strncpy(base, sptr, eptr - sptr + 1);
103   base[eptr - sptr + 1] = '\0';
104   return(base);
105 }
106 
107 
108 static char *
cc_dirname(const char * path)109 cc_dirname(const char * path)
110 {
111   static char dirpath [MAXPATHLEN];
112   const char * ptr;
113 
114   if (path == NULL || *path == '\0') return NULL;
115 
116   /* Get rid of trailing '/'s */
117   ptr = path + strlen(path) - 1;
118   while (*ptr == '/' && path <= ptr) ptr--;
119 
120   /* Skip last element in path */
121   while (*ptr != '/' && path <= ptr) ptr--;
122 
123   /* Path is only '/' */
124   if (ptr == path && *ptr == '/') {
125     strcpy(dirpath, "/");
126     return(dirpath);
127   }
128 
129   /* No slashes in path... */
130   if (ptr == path) {
131     strcpy(dirpath, ".");
132     return(dirpath);
133   }
134 
135   if ((unsigned int)(ptr - path + 1) > sizeof(dirpath)) {
136     return(NULL);
137   }
138 
139   strncpy(dirpath, path, ptr - path + 1);
140   dirpath[ptr - path + 1] = '\0';
141   return(dirpath);
142 }
143 
144 
145 /* Check if there is a valid QuickTime importer that can read file.
146    The following will be tried:
147     - matching Mac OS file type
148     - matching the file name suffix
149     - matching the MIME type
150     - query each graphics importer component
151    Returns 1 if an importer was found, and 0 otherwise.
152 */
153 static int
get_importer(const char * filename,GraphicsImportComponent * c)154 get_importer(const char * filename, GraphicsImportComponent * c)
155 {
156   FSSpec fss;
157   FSRef path;
158   FSRef file;
159   char fullpath [MAXPATHLEN];
160   CFStringRef cfstr;
161   UniChar * ustr;
162   int len;
163   int e = noErr;
164 
165   realpath(filename, fullpath);
166   FSPathMakeRef((const UInt8 *)cc_dirname(fullpath), &path, false);
167 
168   /* convert char * to UniChar * */
169   cfstr = CFStringCreateWithCString(0, cc_basename(fullpath),
170           CFStringGetSystemEncoding());
171   len = CFStringGetLength(cfstr);
172   ustr = malloc(len * sizeof(UniChar));
173   CFStringGetCharacters(cfstr, CFRangeMake(0, len), ustr);
174 
175   FSMakeFSRefUnicode (&path, len, ustr, CFStringGetSystemEncoding(), &file);
176   e = FSGetCatalogInfo(&file, kFSCatInfoNone, NULL, NULL, &fss, NULL);
177   if (e != noErr) return 0;
178 
179   if (GetGraphicsImporterForFile(&fss, c) == noErr) return 1;
180   else return 0;
181 }
182 
183 /* Look for a valid graphics exporter component for the file extension
184    "fext," and if we find one, open it.
185  */
186 static void
open_exporter(const char * fext,GraphicsExportComponent * ge)187 open_exporter(const char * fext, GraphicsExportComponent * ge)
188 {
189   Component c = 0;
190   ComponentDescription cd;
191   cd.componentType = GraphicsExporterComponentType;
192   cd.componentSubType = 0;
193   cd.componentManufacturer = 0;
194   cd.componentFlags = 0;
195   cd.componentFlagsMask = graphicsExporterIsBaseExporter;
196 
197   if (!fext || !ge) return;
198 
199   while ((c = FindNextComponent (c, &cd)) != 0) {
200     char * cstr = malloc(5);
201     OSType * ext = malloc(sizeof(OSType));
202     GraphicsExportGetDefaultFileNameExtension((GraphicsExportComponent)c, ext);
203     if (!system_is_bigendian()) {
204       /* OSType is a big-endian four-character code => need to swap */
205       uint8_t tmp;
206       uint8_t * block = (uint8_t*)ext;
207       tmp = block[3];
208       block[3] = block[0];
209       block[0] = tmp;
210       tmp = block[2];
211       block[2] = block[1];
212       block[1] = tmp;
213     }
214     memcpy(cstr, ext, 4);
215     if (cstr[3] == ' ') cstr[3] = '\0';
216     else cstr[4] = '\0';
217     if (strcasecmp(fext, cstr) == 0) {
218       OpenAComponent(c, ge);
219       break;
220     }
221   }
222 }
223 
224 
225 /* Convert the OSType t to a C string and append it to str.
226    An OSType is really an unsigned long, intepreted as a four-character
227    constant.
228  */
229 static void
cfstring_append_ostype(CFMutableStringRef str,OSType * t)230 cfstring_append_ostype(CFMutableStringRef str, OSType * t)
231 {
232   char * cstr;
233   if (!t) return;
234   cstr = malloc(5);
235   memcpy(cstr, t, 4);
236   if (cstr[3] == ' ') cstr[3] = '\0';
237   else cstr[4] = '\0';
238   CFStringAppendCString(str, cstr, CFStringGetSystemEncoding());
239   free(cstr);
240 }
241 
242 
243 /* Create a new file named "file" in the current working directory.
244    If a file with this name already exists, it will be overwritten.
245    Returns 1 if file could be created successfully, and 0 otherwise.
246  */
247 static int
create_file(const char * filename,FSSpec * fss)248 create_file(const char * filename, FSSpec * fss)
249 {
250   FSRef path;
251   FSRef file;
252   CFStringRef cfstr;
253   UniChar * ustr;
254   int e = noErr;
255   CFIndex len;
256   char fullpath [MAXPATHLEN];
257 
258   realpath(filename, fullpath);
259   e = FSPathMakeRef((const UInt8 *)cc_dirname(fullpath), &path, false);
260   if (e != noErr) return 0;
261 
262   /* convert char * to UniChar * */
263   cfstr = CFStringCreateWithCString(0, cc_basename(fullpath),
264           CFStringGetSystemEncoding());
265   len = CFStringGetLength(cfstr);
266   ustr = malloc(len * sizeof(UniChar));
267   CFStringGetCharacters(cfstr, CFRangeMake(0, len), ustr);
268 
269   /* If you can create an FSRef, the file already exists -> delete it. */
270   e = FSMakeFSRefUnicode(&path, len, ustr, CFStringGetSystemEncoding(), &file);
271   if (e == noErr) FSDeleteObject(&file);
272 
273   e = FSCreateFileUnicode (&path, len, ustr, 0, NULL, NULL, fss);
274   free(ustr);
275 
276   if (e == noErr) return 1;
277   else return 0;
278 }
279 
280 
281 
282 /* Flip the image vertically. The flipped image will be returned in
283    newpx. Needed since Carbon has the origin in the upper left corner,
284    and OpenGL in the lower left corner.
285  */
286 static void
v_flip(const unsigned char * px,int width,int height,int numcomponents,unsigned char * newpx)287 v_flip(const unsigned char * px, int width, int height,
288        int numcomponents, unsigned char * newpx)
289 {
290   /* FIXME: We should really use QuickTime to do this (Altivec! :)).
291      For importing, it's straightforward, but I have no clue how to do
292      this for the export case. kyrah 20030210.
293   */
294   int i;
295   if (!newpx) return;
296   for (i = 0; i < height; i++) {
297     memcpy (newpx + (i * width * numcomponents),
298             px + (((height - i) - 1) * numcomponents * width),
299             numcomponents * width);
300   }
301 }
302 
303 
304 /* Convert the image data in px from ARGB to RGBA.
305    Needed since Mac OS X uses ARGB internally, but OpenGL expects RGBA.
306 */
307 static void
argb_to_rgba(uint32_t * px,int width,int height)308 argb_to_rgba(uint32_t * px, int width, int height)
309 {
310   uint32_t i;
311   if (system_is_bigendian()) {
312     for (i = 0; i < (height * width); i++)
313       *(px+i) = ((*(px+i) & 0x00FFFFFF ) << 8) | ((*(px+i) >> 24) & 0x000000FF);
314   } else {
315     for (i = 0; i < (height * width); i++)
316       *(px+i) = ((*(px+i) & 0x000000FF ) << 24) | ((*(px+i) >> 8) & 0x00FFFFFF);
317   }
318 }
319 
320 
321 /* Convert the image data in px from RGBA to ARGB.
322    Needed since Mac OS X uses ARGB internally, but OpenGL expects RGBA.
323 */
324 static void
rgba_to_argb(uint32_t * px,int width,int height)325 rgba_to_argb(uint32_t * px, int width, int height)
326 {
327   uint32_t i;
328   if (system_is_bigendian()) {
329     for (i = 0; i < (height * width); i++)
330       *(px+i) = ((*(px+i) & 0xFFFFFF00 ) >> 8) | ((*(px+i) << 24) & 0xFF000000);
331   } else {
332     for (i = 0; i < (height * width); i++)
333       *(px+i) = ((*(px+i) & 0xFF000000 ) >> 24) | ((*(px+i) << 8) & 0xFFFFFF00);
334   }
335 }
336 
337 
338 // -------------------- public functions ---------------------------
339 
340 
341 int
simage_quicktime_error(char * cstr,int buflen)342 simage_quicktime_error(char * cstr, int buflen)
343 {
344   switch (quicktimeerror) {
345   case ERR_OPEN:
346     strncpy(cstr, "QuickTime loader: Error opening file", buflen);
347     break;
348   case ERR_CG:
349     strncpy(cstr, "QuickTime loader: Internal graphics error", buflen);
350     break;
351   case ERR_WRITE:
352     strncpy(cstr, "QuickTime saver: Error writing file", buflen);
353     break;
354   case ERR_UNSUPPORTED:
355     strncpy(cstr, "QuickTime saver: Unsupported file format", buflen);
356     break;
357   case ERR_BAD_DEPTH:
358     strncpy(cstr, "QuickTime saver: Only 24 and 32 bit images supported",
359             buflen);
360     break;
361   case ERR_MEM:
362     strncpy(cstr, "QuickTime loader/saver: Out of memory", buflen);
363     break;
364   }
365   return quicktimeerror;
366 }
367 
368 
369 int
simage_quicktime_identify(const char * file,const unsigned char * header,int headerlen)370 simage_quicktime_identify(const char * file, const unsigned char * header,
371                           int headerlen)
372 {
373   GraphicsImportComponent c;
374   int ret = get_importer(file, &c);
375   CloseComponent(c);
376   return ret;
377 }
378 
379 
380 unsigned char *
simage_quicktime_load(const char * file,int * width,int * height,int * numcomponents)381 simage_quicktime_load(const char * file, int * width,
382                       int * height, int * numcomponents)
383 {
384   GraphicsImportComponent gi;
385   ImageDescriptionHandle desch = NULL;
386   ImageDescription * desc;
387   GWorldPtr gw;
388   unsigned char * newpx = NULL;
389   Rect r;
390   BitmapInfo bi;
391   int e = noErr;
392 
393   if (!get_importer(file, &gi)) {
394     quicktimeerror = ERR_OPEN;
395     return NULL;
396   }
397 
398   e = GraphicsImportGetImageDescription(gi, &desch);
399   if(e != noErr || desch == NULL) {
400     quicktimeerror = ERR_CG;
401     return NULL;
402   }
403 
404   desc = *desch;
405   bi.width = desc->width;
406   bi.height = desc->height;
407   bi.numcomponents = 4;
408   bi.bitsPerPixel = 32;     /* not 24! */
409   bi.bytesPerRow = (bi.bitsPerPixel * bi.width + 7)/8;
410   bi.size = bi.width * bi.height * bi.numcomponents;
411   bi.data = malloc(bi.size);
412   if(bi.data == NULL) {
413     quicktimeerror = ERR_MEM;
414     return NULL;
415   }
416 
417   r.top = 0;
418   r.left = 0;
419   r.bottom = bi.height;
420   r.right = bi.width;
421 
422   QTNewGWorldFromPtr(&gw, k32ARGBPixelFormat, &r, NULL,
423                      NULL, 0, bi.data, bi.bytesPerRow);
424   GraphicsImportSetGWorld(gi, gw, NULL);
425   e = GraphicsImportDraw(gi);
426   if (e != noErr) {
427     quicktimeerror = ERR_CG;
428     if(bi.data != NULL) {
429       free(bi.data);
430     }
431     return NULL;
432   }
433 
434   newpx = malloc(bi.width * bi.height * bi.numcomponents);
435   v_flip(bi.data, bi.width, bi.height, bi.numcomponents, newpx);
436 
437 #if 0 /* QuickTime flip code for reference. See FIXME note at v_flip. */
438   MatrixRecord mr;
439   GraphicsImportGetMatrix(gi, &mr);
440   ScaleMatrix(&mr, fixed1, Long2Fix(-1), 0, 0);
441   TranslateMatrix(&mr, 0, Long2Fix(r.bottom));
442   GraphicsImportSetMatrix(gi, &mr);
443 #endif
444 
445   argb_to_rgba((uint32_t *)newpx, bi.width, bi.height);
446 
447   DisposeHandle((Handle)desch);
448   DisposeGWorld(gw);
449   CloseComponent(gi);
450 
451   *width = bi.width;
452   *height = bi.height;
453   *numcomponents = bi.numcomponents;
454   if(bi.data != NULL) {
455     free(bi.data);
456   }
457   return newpx;
458 }
459 
460 
461 char *
simage_quicktime_get_savers(void)462 simage_quicktime_get_savers(void)
463 {
464   CFMutableStringRef ret = CFStringCreateMutable (NULL, 0);
465   Component c = 0;
466   bool firstext = true;
467   char * cstr = NULL;
468   const char * cret;
469   OSType * ext = malloc(sizeof(OSType));
470 
471   /* Search for all graphics exporters, except the base exporter. */
472   ComponentDescription cd;
473   cd.componentType = GraphicsExporterComponentType;
474   cd.componentSubType = 0;
475   cd.componentManufacturer = 0;
476   cd.componentFlags = 0;
477   cd.componentFlagsMask = graphicsExporterIsBaseExporter;
478 
479   while ((c = FindNextComponent (c, &cd)) != 0) {
480     /* put '," in front of all consecutive string entries */
481     if (firstext) firstext = false;
482     else CFStringAppendCString(ret, ",", CFStringGetSystemEncoding());
483     GraphicsExportGetDefaultFileNameExtension((GraphicsExportComponent)c, ext);
484     if (!system_is_bigendian()) {
485       /* OSType is a big-endian four-character code => need to swap */
486       uint8_t tmp;
487       uint8_t * block = (uint8_t*)ext;
488       tmp = block[3];
489       block[3] = block[0];
490       block[0] = tmp;
491       tmp = block[2];
492       block[2] = block[1];
493       block[1] = tmp;
494     }
495     cfstring_append_ostype(ret, ext);
496   }
497   free(ext);
498 
499   CFIndex length = CFStringGetLength(ret) + 1;
500   /* number of unicode characters in ret should never be < 0... */
501   assert(length > 0);
502   cstr = malloc(length);
503 
504   /* CFStringGetCString might return NULL due to encoding problems etc. */
505   if (!CFStringGetCString(ret, cstr, length, CFStringGetSystemEncoding())) {
506     cstr = NULL;
507   }
508 
509   return cstr;
510 }
511 
512 int
simage_quicktime_save(const char * filename,const unsigned char * px,int width,int height,int numcomponents,const char * filetypeext)513 simage_quicktime_save(const char * filename, const unsigned char * px,
514                        int width, int height, int numcomponents,
515                        const char * filetypeext)
516 {
517   GWorldPtr gw;
518   FSSpec fss;
519   GraphicsExportComponent ge;
520   int e = noErr;
521   Rect r = {0, 0, height, width};
522   unsigned char * newpx = malloc(width * height * numcomponents);
523 
524   v_flip(px, width, height, numcomponents, newpx);
525 
526   /* Note: We have to use QuickTime's QTNewGWorldFromPtr() here,
527      not Carbon's NewGWorldFromPtr(), since the latter does
528      not support 24 bit images.
529   */
530 
531   if (numcomponents == 4) {
532     rgba_to_argb((uint32_t *)newpx, width, height);
533     e = QTNewGWorldFromPtr(&gw, k32ARGBPixelFormat, &r, NULL,
534                            NULL, 0, newpx, width * numcomponents);
535   } else if (numcomponents == 3) {
536     e = QTNewGWorldFromPtr(&gw, k24RGBPixelFormat, &r, NULL,
537                          NULL, 0, newpx, width * numcomponents);
538   } else {
539     quicktimeerror = ERR_BAD_DEPTH;
540     return 0;
541   }
542 
543   if (e != noErr) {
544     quicktimeerror = ERR_CG;
545     return 0;
546   }
547 
548   if (!create_file(filename, &fss)) {
549     quicktimeerror = ERR_WRITE;
550     return 0;
551   }
552 
553   open_exporter(filetypeext, &ge);
554   if (!ge) {
555     quicktimeerror = ERR_UNSUPPORTED;
556     return 0;
557   }
558 
559   GraphicsExportSetInputGWorld(ge, gw);
560   GraphicsExportSetOutputFile(ge, &fss);
561   e = GraphicsExportDoExport(ge, NULL);
562   if (e != noErr) {
563     quicktimeerror = ERR_WRITE;
564     return 0;
565   }
566   CloseComponent(ge);
567 
568   return 1;
569 }
570 
571