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 */
56typedef enum {
57 STATE_READY,
58 STATE_DATA,
59 STATE_OTHER
60} QTIFState;
61
62/* QTIF Atom Header */
63typedef struct {
64 guint32 length;
65 guint32 tag;
66} QtHeader;
67
68/* QTIF loader context */
69typedef 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 prepared_func;
80 GdkPixbufModuleUpdatedFunc updated_func;
81 gint cb_prepare_count;
82 gint cb_update_count;
83} QTIFContext;
84
85/***
86 * Local function prototypes
87 */
88static GdkPixbuf *gdk_pixbuf__qtif_image_load (FILE *f, GError **error);
89static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
90 GdkPixbufModulePreparedFunc prepared_func,
91 GdkPixbufModuleUpdatedFunc updated_func,
92 gpointer user_data,
93 GError **error);
94static gboolean gdk_pixbuf__qtif_image_stop_load (gpointer context, GError **error);
95static gboolean gdk_pixbuf__qtif_image_load_increment(gpointer context,
96 const guchar *buf, guint size,
97 GError **error);
98static gboolean gdk_pixbuf__qtif_image_create_loader (QTIFContext *context, GError **error);
99static gboolean gdk_pixbuf__qtif_image_free_loader (QTIFContext *context, GError **error);
100
101static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
102 gint width,
103 gint height,
104 gpointer user_data);
105static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data);
106static 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. */
118static 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 (err: error, GDK_PIXBUF_ERROR,
125 code: 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(ptr: &hdr, size: 1, n: sizeof(QtHeader), stream: f);
137 if(rd != sizeof(QtHeader))
138 {
139 g_set_error_literal(err: error, GDK_PIXBUF_ERROR,
140 code: 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(err: error, GDK_PIXBUF_ERROR,
149 code: 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(err: error, GDK_PIXBUF_ERROR,
172 code: 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(err: error, GDK_PIXBUF_ERROR,
186 code: 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(ptr: buf, size: 1, n: rd, stream: f) != rd)
198 {
199 g_set_error(err: error, GDK_PIXBUF_ERROR,
200 code: 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, count: rd, error: &tmp))
206 {
207 g_propagate_error (dest: error, src: tmp);
208 break;
209 }
210 hdr.length -= rd;
211 }
212
213clean_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(object: loader);
224 }
225 if(buf != NULL)
226 {
227 g_free(mem: buf);
228 }
229 return pixbuf;
230 }
231
232 default:
233 /* Skip any other types of atom. */
234 if(!fseek(stream: f, off: hdr.length, SEEK_CUR))
235 {
236 g_set_error(err: error, GDK_PIXBUF_ERROR,
237 code: 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. */
251static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
252 GdkPixbufModulePreparedFunc prepared_func,
253 GdkPixbufModuleUpdatedFunc updated_func,
254 gpointer user_data,
255 GError **error)
256{
257 QTIFContext *context;
258
259 g_assert (size_func != NULL);
260 g_assert (prepared_func != NULL);
261 g_assert (updated_func != NULL);
262
263 /* Create context struct. */
264 context = g_new0(QTIFContext, 1);
265 if(context == NULL)
266 {
267 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
268 code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
269 _("Failed to allocate QTIF context structure."));
270 return NULL;
271 }
272
273 /* Fill context parameters. */
274 context->loader = NULL;
275 context->user_data = user_data;
276 context->state = STATE_READY;
277 context->run_length = 0u;
278 context->atom_count = QTIF_ATOM_COUNT_MAX;
279 context->size_func = size_func;
280 context->prepared_func = prepared_func;
281 context->updated_func = updated_func;
282
283 return context;
284}
285
286/* Incremental load clean up. */
287static gboolean gdk_pixbuf__qtif_image_stop_load (gpointer data, GError **error)
288{
289 QTIFContext *context = (QTIFContext *)data;
290 gboolean ret = TRUE;
291
292 if(context->loader != NULL)
293 {
294 GError *tmp = NULL;
295
296 ret = gdk_pixbuf__qtif_image_free_loader(context, error: &tmp);
297 if(!ret)
298 {
299 g_propagate_error (dest: error, src: tmp);
300 }
301 }
302 g_free(mem: context);
303
304 return ret;
305}
306
307/* Create a new GdkPixbufLoader and connect to its signals. */
308static gboolean gdk_pixbuf__qtif_image_create_loader (QTIFContext *context, GError **error)
309{
310 GError *tmp = NULL;
311
312 if(context == NULL)
313 {
314 return FALSE;
315 }
316
317 /* Free existing loader. */
318 if(context->loader != NULL)
319 {
320 gdk_pixbuf__qtif_image_free_loader(context, error: &tmp);
321 }
322
323 /* Create GdkPixbufLoader object. */
324 context->loader = gdk_pixbuf_loader_new();
325 if(context->loader == NULL)
326 {
327 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
328 code: GDK_PIXBUF_ERROR_FAILED,
329 _("Failed to create GdkPixbufLoader object."));
330 return FALSE;
331 }
332
333 /* Connect signals. */
334 context->cb_prepare_count = 0;
335 context->cb_update_count = 0;
336 g_signal_connect(context->loader, "size-prepared",
337 G_CALLBACK(gdk_pixbuf__qtif_cb_size_prepared),
338 context);
339 g_signal_connect(context->loader, "area-prepared",
340 G_CALLBACK(gdk_pixbuf__qtif_cb_area_prepared),
341 context);
342 g_signal_connect(context->loader, "area-updated",
343 G_CALLBACK(gdk_pixbuf__qtif_cb_area_updated),
344 context);
345 return TRUE;
346}
347
348/* Free the GdkPixbufLoader and perform callback if haven't done so. */
349static gboolean gdk_pixbuf__qtif_image_free_loader (QTIFContext *context, GError **error)
350{
351 GdkPixbuf *pixbuf;
352 GError *tmp = NULL;
353 gboolean ret;
354
355 if((context == NULL) || (context->loader == NULL))
356 {
357 return FALSE;
358 }
359
360 /* Close GdkPixbufLoader. */
361 ret = gdk_pixbuf_loader_close(loader: context->loader, error: &tmp);
362 if(!ret)
363 {
364 g_propagate_error (dest: error, src: tmp);
365 }
366
367
368 /* Get GdkPixbuf from GdkPixbufLoader. */
369 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader: context->loader);
370 if(pixbuf != NULL)
371 {
372 g_object_ref(pixbuf);
373 }
374
375 /* Free GdkPixbufLoader. */
376 g_object_ref(context->loader);
377 context->loader = NULL;
378
379 if(pixbuf != NULL)
380 {
381 /* Callback functions should be called for at least once. */
382 if(context->cb_prepare_count == 0)
383 {
384 (context->prepared_func)(pixbuf, NULL, context->user_data);
385 }
386
387 if(context->cb_update_count == 0)
388 {
389 gint width;
390 gint height;
391
392 width = gdk_pixbuf_get_width(pixbuf);
393 height = gdk_pixbuf_get_height(pixbuf);
394 (context->updated_func)(pixbuf, 0, 0, width, height, context->user_data);
395 }
396
397 /* Free GdkPixbuf (callback function should ref it). */
398 g_object_ref(pixbuf);
399 }
400
401 return ret;
402}
403
404
405/* Incrementally load the next chunk of data. */
406static gboolean gdk_pixbuf__qtif_image_load_increment (gpointer data,
407 const guchar *buf, guint size,
408 GError **error)
409{
410 QTIFContext *context = (QTIFContext *)data;
411 GError *tmp = NULL;
412 gboolean ret = TRUE; /* Return TRUE for insufficient data. */
413
414 while(ret && (size != 0u))
415 {
416 switch(context->state)
417 {
418 case STATE_READY:
419 /* Abort if we have seen too many atoms. */
420 if(context->atom_count == 0u)
421 {
422 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
423 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
424 _("Failed to find an image data atom."));
425 return FALSE;
426 }
427 context->atom_count--;
428
429 /* Copy to header buffer in context, in case supplied data is not enough. */
430 while (context->run_length < sizeof(QtHeader) && size > 0u)
431 {
432 context->header_buffer[context->run_length] = *buf;
433 context->run_length++;
434 buf++;
435 size--;
436 }
437
438 /* Parse buffer as QT header. */
439 if(context->run_length == sizeof(QtHeader))
440 {
441 QtHeader *hdr = (QtHeader *)context->header_buffer;
442 context->run_length = GUINT32_FROM_BE(hdr->length) - sizeof(QtHeader);
443
444 /* Atom max size check. */
445 if(context->run_length > ATOM_SIZE_MAX)
446 {
447 g_set_error(err: error, GDK_PIXBUF_ERROR,
448 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
449 ngettext ( "QTIF atom size too large (%d byte)",
450 "QTIF atom size too large (%d bytes)",
451 hdr->length),
452 hdr->length);
453 return FALSE;
454 }
455
456 /* Set state according to atom type. */
457 if(GUINT32_FROM_BE(hdr->tag) == QTIF_TAG_IDATA)
458 {
459 GError *tmp = NULL;
460
461 context->state = STATE_DATA;
462
463 /* Create GdkPixbufLoader for this image data. */
464 ret = gdk_pixbuf__qtif_image_create_loader(context, error: &tmp);
465 if(!ret)
466 {
467 g_propagate_error (dest: error, src: tmp);
468 }
469 }
470 else
471 {
472 context->state = STATE_OTHER;
473 }
474 }
475 break;
476
477 default: /* Both STATE_DATA and STATE_OTHER will come here. */
478 /* Check for atom boundary. */
479 if(context->run_length > size)
480 {
481 /* Supply image data to GdkPixbufLoader if in STATE_DATA. */
482 if(context->state == STATE_DATA)
483 {
484 tmp = NULL;
485 ret = gdk_pixbuf_loader_write(loader: context->loader, buf, count: size, error: &tmp);
486 if(!ret && (error != NULL) && (*error == NULL))
487 {
488 g_propagate_error (dest: error, src: tmp);
489 }
490 }
491 context->run_length -= size;
492 size = 0u;
493 }
494 else
495 {
496 /* Supply image data to GdkPixbufLoader if in STATE_DATA. */
497 if(context->state == STATE_DATA)
498 {
499 gboolean r;
500
501 /* Here we should have concluded a complete image atom. */
502 tmp = NULL;
503 ret = gdk_pixbuf_loader_write(loader: context->loader, buf, count: context->run_length, error: &tmp);
504 if(!ret && (error != NULL) && (*error == NULL))
505 {
506 g_propagate_error (dest: error, src: tmp);
507 }
508
509 /* Free GdkPixbufLoader and handle callback. */
510 tmp = NULL;
511 r = gdk_pixbuf__qtif_image_free_loader(context, error: &tmp);
512 if(!r)
513 {
514 if((error != NULL) && (*error == NULL))
515 {
516 g_propagate_error (dest: error, src: tmp);
517 }
518 ret = FALSE;
519 }
520 }
521 buf = &buf[context->run_length];
522 size -= context->run_length;
523 context->run_length = 0u;
524 context->state = STATE_READY;
525 }
526 break;
527 }
528 }
529
530 return ret;
531}
532
533/* Event handlers */
534static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
535 gint width,
536 gint height,
537 gpointer user_data)
538{
539 QTIFContext *context = (QTIFContext *)user_data;
540 (context->size_func)(&width, &height, context->user_data);
541 context->cb_prepare_count++;
542}
543
544static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data)
545{
546 QTIFContext *context = (QTIFContext *)user_data;
547 GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader: context->loader);
548 (context->prepared_func)(pixbuf, NULL, context->user_data);
549 context->cb_update_count++;
550}
551
552static void gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader *loader,
553 gint x,
554 gint y,
555 gint width,
556 gint height,
557 gpointer user_data)
558{
559 QTIFContext *context = (QTIFContext *)user_data;
560 GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader: context->loader);
561 (context->updated_func)(pixbuf, x, y, width, height, context->user_data);
562}
563
564
565#ifndef INCLUDE_qtif
566#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
567#else
568#define MODULE_ENTRY(function) void _gdk_pixbuf__qtif_ ## function
569#endif
570
571MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
572{
573 module->load = gdk_pixbuf__qtif_image_load;
574 module->begin_load = gdk_pixbuf__qtif_image_begin_load;
575 module->stop_load = gdk_pixbuf__qtif_image_stop_load;
576 module->load_increment = gdk_pixbuf__qtif_image_load_increment;
577}
578
579MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
580{
581 static const GdkPixbufModulePattern signature[] = {
582 { "abcdidsc", "xxxx ", 100 },
583 { "abcdidat", "xxxx ", 100 },
584 { NULL, NULL, 0 }
585 };
586 static const gchar *mime_types[] = {
587 "image/x-quicktime",
588 "image/qtif",
589 NULL
590 };
591 static const gchar *extensions[] = {
592 "qtif",
593 "qif",
594 NULL
595 };
596
597 info->name = "qtif";
598 info->signature = (GdkPixbufModulePattern *) signature;
599 info->description = NC_("image format", "QuickTime");
600 info->mime_types = (gchar **) mime_types;
601 info->extensions = (gchar **) extensions;
602 info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
603 info->license = "LGPL";
604}
605
606

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-qtif.c