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