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 ; |
65 | guint32 ; |
66 | } ; |
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 [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 | */ |
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 prepared_func, |
91 | GdkPixbufModuleUpdatedFunc updated_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. */ |
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 (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 | |
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(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. */ |
251 | static 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. */ |
287 | static 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. */ |
308 | static 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. */ |
349 | static 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. */ |
406 | static 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 */ |
534 | static 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 | |
544 | static 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 | |
552 | static 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 | |
571 | MODULE_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 | |
579 | MODULE_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 | |