1 /* -*- mode: C; c-file-style: "linux" -*- */
2 /* GdkPixbuf library - QTIF image loader
3  *
4  * This module extracts image data from QTIF format and uses
5  * other GDK pixbuf modules to decode the image data.
6  *
7  * Copyright (C) 2008 Kevin Peng
8  *
9  * Authors: Kevin Peng <kevin@zycomtech.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 
26 #include "config.h"
27 #include <errno.h>
28 #include <libintl.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <setjmp.h>
33 #include <glib/gi18n-lib.h>
34 #include "gdk-pixbuf.h"
35 
36 /***
37  * Definitions
38  */
39 /* Read buffer size */
40 #define READ_BUFFER_SIZE    8192
41 
42 /* Only allow atom of size up to 10MB. */
43 #define ATOM_SIZE_MAX       100000000
44 
45 /* Aborts after going to through this many atoms. */
46 #define QTIF_ATOM_COUNT_MAX 10u
47 
48 /* QTIF static image data tag "idat". */
49 #define QTIF_TAG_IDATA      0x69646174u
50 
51 
52 /***
53  * Types
54  */
55 /* QTIF State */
56 typedef enum {
57     STATE_READY,
58     STATE_DATA,
59     STATE_OTHER
60 } QTIFState;
61 
62 /* QTIF Atom Header */
63 typedef struct {
64     guint32 length;
65     guint32 tag;
66 } QtHeader;
67 
68 /* QTIF loader context */
69 typedef struct {
70     GdkPixbufLoader *loader;
71     gpointer        user_data;
72     QTIFState       state;
73     guint32         run_length;
74     gint            atom_count;
75 
76     guchar          header_buffer[sizeof(QtHeader)];
77 
78     GdkPixbufModuleSizeFunc     size_func;
79     GdkPixbufModulePreparedFunc prepare_func;
80     GdkPixbufModuleUpdatedFunc  update_func;
81     gint            cb_prepare_count;
82     gint            cb_update_count;
83 } QTIFContext;
84 
85 /***
86  * Local function prototypes
87  */
88 static GdkPixbuf *gdk_pixbuf__qtif_image_load (FILE *f, GError **error);
89 static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
90                                                    GdkPixbufModulePreparedFunc prepare_func,
91                                                    GdkPixbufModuleUpdatedFunc update_func,
92                                                    gpointer user_data,
93                                                    GError **error);
94 static gboolean gdk_pixbuf__qtif_image_stop_load (gpointer context, GError **error);
95 static gboolean gdk_pixbuf__qtif_image_load_increment(gpointer context,
96                                                       const guchar *buf, guint size,
97                                                       GError **error);
98 static gboolean gdk_pixbuf__qtif_image_create_loader (QTIFContext *context, GError **error);
99 static gboolean gdk_pixbuf__qtif_image_free_loader (QTIFContext *context, GError **error);
100 
101 static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
102                                               gint width,
103                                               gint height,
104                                               gpointer user_data);
105 static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data);
106 static void gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader *loader,
107                                              gint x,
108                                              gint y,
109                                              gint width,
110                                              gint height,
111                                              gpointer user_data);
112 
113 /***
114  * Function definitions.
115  */
116 
117 /* Load QTIF from a file handler. */
gdk_pixbuf__qtif_image_load(FILE * f,GError ** error)118 static GdkPixbuf *gdk_pixbuf__qtif_image_load (FILE *f, GError **error)
119 {
120     guint count;
121 
122     if(f == NULL)
123     {
124         g_set_error_literal (error, GDK_PIXBUF_ERROR,
125                              GDK_PIXBUF_ERROR_BAD_OPTION,
126                              _("Input file descriptor is NULL."));
127         return NULL;
128     }
129 
130     for(count = QTIF_ATOM_COUNT_MAX; count != 0u; count--)
131     {
132         QtHeader hdr;
133         size_t rd;
134 
135         /* Read QtHeader. */
136         rd = fread(&hdr, 1, sizeof(QtHeader), f);
137         if(rd != sizeof(QtHeader))
138         {
139             g_set_error_literal(error, GDK_PIXBUF_ERROR,
140                                 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
141                                 _("Failed to read QTIF header"));
142             return NULL;
143         }
144 
145         hdr.length = GUINT32_FROM_BE(hdr.length) - sizeof(QtHeader);
146         if(hdr.length > ATOM_SIZE_MAX)
147         {
148             g_set_error(error, GDK_PIXBUF_ERROR,
149                         GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
150                         ngettext (  "QTIF atom size too large (%d byte)",
151                                     "QTIF atom size too large (%d bytes)",
152                                     hdr.length),
153                         hdr.length);
154             return NULL;
155         }
156 
157         switch(GUINT32_FROM_BE(hdr.tag))
158         {
159         case QTIF_TAG_IDATA: /* "idat" data atom. */
160             {
161                 /* Load image using GdkPixbufLoader. */
162                 guchar *buf;
163                 GdkPixbufLoader *loader;
164                 GdkPixbuf *pixbuf = NULL;
165                 GError *tmp = NULL;
166 
167                 /* Allocate read buffer. */
168                 buf = g_try_malloc(READ_BUFFER_SIZE);
169                 if(buf == NULL)
170                 {
171                     g_set_error(error, GDK_PIXBUF_ERROR,
172                                 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
173                                 ngettext ( "Failed to allocate %d byte for file read buffer",
174                                            "Failed to allocate %d bytes for file read buffer",
175                                            READ_BUFFER_SIZE
176                                 ),
177                                 READ_BUFFER_SIZE);
178                     return NULL;
179                 }
180 
181                 /* Create GdkPixbufLoader. */
182                 loader = gdk_pixbuf_loader_new();
183                 if(loader == NULL)
184                 {
185                     g_set_error(error, GDK_PIXBUF_ERROR,
186                                 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
187                                 ngettext (  "QTIF atom size too large (%d byte)",
188                                             "QTIF atom size too large (%d bytes)",
189                                             hdr.length),
190                                 hdr.length);
191                     goto clean_up;
192                 }
193 
194                 /* Read atom data. */
195                 while(hdr.length != 0u)
196                 {
197                     if(fread(buf, 1, rd, f) != rd)
198                     {
199                         g_set_error(error, GDK_PIXBUF_ERROR,
200                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
201                                     _("File error when reading QTIF atom: %s"), g_strerror(errno));
202                         break;
203                     }
204 
205                     if(!gdk_pixbuf_loader_write(loader, buf, rd, &tmp))
206                     {
207                         g_propagate_error (error, tmp);
208                         break;
209                     }
210                     hdr.length -= rd;
211                 }
212 
213 clean_up:
214                 /* Release loader */
215                 if(loader != NULL)
216                 {
217                     gdk_pixbuf_loader_close(loader, NULL);
218                     pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
219                     if(pixbuf != NULL)
220                     {
221                         g_object_ref(pixbuf);
222                     }
223                     g_object_unref(loader);
224                 }
225                 if(buf != NULL)
226                 {
227                     g_free(buf);
228                 }
229                 return pixbuf;
230             }
231 
232         default:
233             /* Skip any other types of atom. */
234             if(!fseek(f, hdr.length, SEEK_CUR))
235             {
236                 g_set_error(error, GDK_PIXBUF_ERROR,
237                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
238                             ngettext (  "Failed to skip the next %d byte with seek().",
239                                         "Failed to skip the next %d bytes with seek().",
240                                         hdr.length),
241                             hdr.length);
242                 return NULL;
243             }
244             break;
245         }
246     }
247     return NULL;
248 }
249 
250 /* Incremental load begin. */
gdk_pixbuf__qtif_image_begin_load(GdkPixbufModuleSizeFunc size_func,GdkPixbufModulePreparedFunc prepare_func,GdkPixbufModuleUpdatedFunc update_func,gpointer user_data,GError ** error)251 static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
252                                                    GdkPixbufModulePreparedFunc prepare_func,
253                                                    GdkPixbufModuleUpdatedFunc update_func,
254                                                    gpointer user_data,
255                                                    GError **error)
256 {
257     QTIFContext *context;
258 
259     /* Create context struct. */
260     context = g_new0(QTIFContext, 1);
261     if(context == NULL)
262     {
263         g_set_error_literal (error, GDK_PIXBUF_ERROR,
264                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
265                              _("Failed to allocate QTIF context structure."));
266         return NULL;
267     }
268 
269     /* Fill context parameters. */
270     context->loader = NULL;
271     context->user_data = user_data;
272     context->state = STATE_READY;
273     context->run_length = 0u;
274     context->atom_count = QTIF_ATOM_COUNT_MAX;
275     context->size_func = size_func;
276     context->prepare_func = prepare_func;
277     context->update_func = update_func;
278 
279     return context;
280 }
281 
282 /* Incremental load clean up. */
gdk_pixbuf__qtif_image_stop_load(gpointer data,GError ** error)283 static gboolean gdk_pixbuf__qtif_image_stop_load (gpointer data, GError **error)
284 {
285     QTIFContext *context = (QTIFContext *)data;
286     gboolean ret = TRUE;
287 
288     if(context->loader != NULL)
289     {
290         GError *tmp = NULL;
291 
292         ret = gdk_pixbuf__qtif_image_free_loader(context, &tmp);
293         if(!ret)
294         {
295             g_propagate_error (error, tmp);
296         }
297     }
298     g_free(context);
299 
300     return ret;
301 }
302 
303 /* Create a new GdkPixbufLoader and connect to its signals. */
gdk_pixbuf__qtif_image_create_loader(QTIFContext * context,GError ** error)304 static gboolean gdk_pixbuf__qtif_image_create_loader (QTIFContext *context, GError **error)
305 {
306     GError *tmp = NULL;
307 
308     if(context == NULL)
309     {
310         return FALSE;
311     }
312 
313     /* Free existing loader. */
314     if(context->loader != NULL)
315     {
316         gdk_pixbuf__qtif_image_free_loader(context, &tmp);
317     }
318 
319     /* Create GdkPixbufLoader object. */
320     context->loader = gdk_pixbuf_loader_new();
321     if(context->loader == NULL)
322     {
323         g_set_error_literal (error, GDK_PIXBUF_ERROR,
324                              GDK_PIXBUF_ERROR_FAILED,
325                              _("Failed to create GdkPixbufLoader object."));
326         return FALSE;
327     }
328 
329     /* Connect signals. */
330     context->cb_prepare_count = 0;
331     context->cb_update_count = 0;
332     if(context->size_func != NULL)
333     {
334         g_signal_connect(context->loader, "size-prepared",
335                          G_CALLBACK(gdk_pixbuf__qtif_cb_size_prepared),
336                          context);
337     }
338     if(context->prepare_func != NULL)
339     {
340         g_signal_connect(context->loader, "area-prepared",
341                          G_CALLBACK(gdk_pixbuf__qtif_cb_area_prepared),
342                          context);
343     }
344     if(context->update_func != NULL)
345     {
346         g_signal_connect(context->loader, "area-updated",
347                          G_CALLBACK(gdk_pixbuf__qtif_cb_area_updated),
348                          context);
349     }
350     return TRUE;
351 }
352 
353 /* Free the GdkPixbufLoader and perform callback if haven't done so. */
gdk_pixbuf__qtif_image_free_loader(QTIFContext * context,GError ** error)354 static gboolean gdk_pixbuf__qtif_image_free_loader (QTIFContext *context, GError **error)
355 {
356     GdkPixbuf *pixbuf;
357     GError *tmp = NULL;
358     gboolean ret;
359 
360     if((context == NULL) || (context->loader == NULL))
361     {
362         return FALSE;
363     }
364 
365     /* Close GdkPixbufLoader. */
366     ret = gdk_pixbuf_loader_close(context->loader, &tmp);
367     if(!ret)
368     {
369         g_propagate_error (error, tmp);
370     }
371 
372 
373     /* Get GdkPixbuf from GdkPixbufLoader. */
374     pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
375     if(pixbuf != NULL)
376     {
377         g_object_ref(pixbuf);
378     }
379 
380     /* Free GdkPixbufLoader. */
381     g_object_ref(context->loader);
382     context->loader = NULL;
383 
384     if(pixbuf != NULL)
385     {
386         /* Callback functions should be called for at least once. */
387         if((context->prepare_func != NULL) && (context->cb_prepare_count == 0))
388         {
389             (context->prepare_func)(pixbuf, NULL, context->user_data);
390         }
391 
392         if((context->update_func != NULL) && (context->cb_update_count == 0))
393         {
394             gint width;
395             gint height;
396 
397             width = gdk_pixbuf_get_width(pixbuf);
398             height = gdk_pixbuf_get_height(pixbuf);
399             (context->update_func)(pixbuf, 0, 0, width, height, context->user_data);
400         }
401 
402         /* Free GdkPixbuf (callback function should ref it). */
403         g_object_ref(pixbuf);
404     }
405 
406     return ret;
407 }
408 
409 
410 /* Incrementally load the next chunk of data. */
gdk_pixbuf__qtif_image_load_increment(gpointer data,const guchar * buf,guint size,GError ** error)411 static gboolean gdk_pixbuf__qtif_image_load_increment (gpointer data,
412                                                        const guchar *buf, guint size,
413                                                        GError **error)
414 {
415     QTIFContext *context = (QTIFContext *)data;
416     GError *tmp = NULL;
417     gboolean ret = TRUE; /* Return TRUE for insufficient data. */
418 
419     while(ret && (size != 0u))
420     {
421         switch(context->state)
422         {
423         case STATE_READY:
424             /* Abort if we have seen too many atoms. */
425             if(context->atom_count == 0u)
426             {
427                 g_set_error_literal (error, GDK_PIXBUF_ERROR,
428                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
429                                      _("Failed to find an image data atom."));
430                 return FALSE;
431             }
432             context->atom_count--;
433 
434             /* Copy to header buffer in context, in case supplied data is not enough. */
435             while (context->run_length < sizeof(QtHeader) && size > 0u)
436             {
437                 context->header_buffer[context->run_length] = *buf;
438                 context->run_length++;
439                 buf++;
440                 size--;
441             }
442 
443             /* Parse buffer as QT header. */
444             if(context->run_length == sizeof(QtHeader))
445             {
446                 QtHeader *hdr = (QtHeader *)context->header_buffer;
447                 context->run_length = GUINT32_FROM_BE(hdr->length) - sizeof(QtHeader);
448 
449                 /* Atom max size check. */
450                 if(context->run_length > ATOM_SIZE_MAX)
451                 {
452                     g_set_error(error, GDK_PIXBUF_ERROR,
453                                        GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
454                                        ngettext (  "QTIF atom size too large (%d byte)",
455                                                    "QTIF atom size too large (%d bytes)",
456                                                     hdr->length),
457                                        hdr->length);
458                     return FALSE;
459                 }
460 
461                 /* Set state according to atom type. */
462                 if(GUINT32_FROM_BE(hdr->tag) == QTIF_TAG_IDATA)
463                 {
464                     GError *tmp = NULL;
465 
466                     context->state = STATE_DATA;
467 
468                     /* Create GdkPixbufLoader for this image data. */
469                     ret = gdk_pixbuf__qtif_image_create_loader(context, &tmp);
470                     if(!ret)
471                     {
472                         g_propagate_error (error, tmp);
473                     }
474                 }
475                 else
476                 {
477                     context->state = STATE_OTHER;
478                 }
479             }
480             break;
481 
482         default: /* Both STATE_DATA and STATE_OTHER will come here. */
483             /* Check for atom boundary. */
484             if(context->run_length > size)
485             {
486                 /* Supply image data to GdkPixbufLoader if in STATE_DATA. */
487                 if(context->state == STATE_DATA)
488                 {
489                     tmp = NULL;
490                     ret = gdk_pixbuf_loader_write(context->loader, buf, size, &tmp);
491                     if(!ret && (error != NULL) && (*error == NULL))
492                     {
493                         g_propagate_error (error, tmp);
494                     }
495                 }
496                 context->run_length -= size;
497                 size = 0u;
498             }
499             else
500             {
501                 /* Supply image data to GdkPixbufLoader if in STATE_DATA. */
502                 if(context->state == STATE_DATA)
503                 {
504                     gboolean r;
505 
506                     /* Here we should have concluded a complete image atom. */
507                     tmp = NULL;
508                     ret = gdk_pixbuf_loader_write(context->loader, buf, context->run_length, &tmp);
509                     if(!ret && (error != NULL) && (*error == NULL))
510                     {
511                         g_propagate_error (error, tmp);
512                     }
513 
514                     /* Free GdkPixbufLoader and handle callback. */
515                     tmp = NULL;
516                     r = gdk_pixbuf__qtif_image_free_loader(context, &tmp);
517                     if(!r)
518                     {
519                         if((error != NULL) && (*error == NULL))
520                         {
521                             g_propagate_error (error, tmp);
522                         }
523                         ret = FALSE;
524                     }
525                 }
526                 buf = &buf[context->run_length];
527                 size -= context->run_length;
528                 context->run_length = 0u;
529                 context->state = STATE_READY;
530             }
531             break;
532         }
533     }
534 
535     return ret;
536 }
537 
538 /* Event handlers */
gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader * loader,gint width,gint height,gpointer user_data)539 static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
540                                               gint width,
541                                               gint height,
542                                               gpointer user_data)
543 {
544     QTIFContext *context = (QTIFContext *)user_data;
545     if((context != NULL) && (context->size_func != NULL))
546     {
547         (context->size_func)(&width, &height, context->user_data);
548         context->cb_prepare_count++;
549     }
550 }
551 
gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader * loader,gpointer user_data)552 static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data)
553 {
554     QTIFContext *context = (QTIFContext *)user_data;
555     if((loader != NULL) && (context != NULL) && (context->prepare_func != NULL))
556     {
557         GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
558         (context->prepare_func)(pixbuf, NULL, context->user_data);
559         context->cb_update_count++;
560     }
561 }
562 
gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader * loader,gint x,gint y,gint width,gint height,gpointer user_data)563 static void gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader *loader,
564                                              gint x,
565                                              gint y,
566                                              gint width,
567                                              gint height,
568                                              gpointer user_data)
569 {
570     QTIFContext *context = (QTIFContext *)user_data;
571     if((loader != NULL) && (context != NULL) && (context->update_func != NULL))
572     {
573         GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
574         (context->update_func)(pixbuf, x, y, width, height, context->user_data);
575     }
576 }
577 
578 
579 #ifndef INCLUDE_qtif
580 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
581 #else
582 #define MODULE_ENTRY(function) void _gdk_pixbuf__qtif_ ## function
583 #endif
584 
MODULE_ENTRY(fill_vtable)585 MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
586 {
587     module->load = gdk_pixbuf__qtif_image_load;
588     module->begin_load = gdk_pixbuf__qtif_image_begin_load;
589     module->stop_load = gdk_pixbuf__qtif_image_stop_load;
590     module->load_increment = gdk_pixbuf__qtif_image_load_increment;
591 }
592 
MODULE_ENTRY(fill_info)593 MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
594 {
595     static const GdkPixbufModulePattern signature[] = {
596         { "abcdidsc", "xxxx    ", 100 },
597         { "abcdidat", "xxxx    ", 100 },
598         { NULL, NULL, 0 }
599     };
600     static const gchar *mime_types[] = {
601         "image/x-quicktime",
602         "image/qtif",
603         NULL
604     };
605     static const gchar *extensions[] = {
606         "qtif",
607         "qif",
608         NULL
609     };
610 
611     info->name = "qtif";
612     info->signature = (GdkPixbufModulePattern *) signature;
613     info->description = NC_("image format", "QuickTime");
614     info->mime_types = (gchar **) mime_types;
615     info->extensions = (gchar **) extensions;
616     info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
617     info->license = "LGPL";
618 }
619 
620