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