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