]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-png.c
Fix comments to not claim that all three of these files are the JPEG image
[~andy/gtk] / gdk-pixbuf / io-png.c
1 /* GdkPixbuf library - PNG image loader
2  *
3  * Copyright (C) 1999 Mark Crichton
4  * Copyright (C) 1999 The Free Software Foundation
5  *
6  * Authors: Mark Crichton <crichton@gimp.org>
7  *          Federico Mena-Quintero <federico@gimp.org>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include <config.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <png.h>
29 #include "gdk-pixbuf-private.h"
30 #include "gdk-pixbuf-io.h"
31
32 \f
33
34 static void
35 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
36                           gboolean *fatal_error_occurred,
37                           png_uint_32* width_p, png_uint_32* height_p,
38                           int* color_type_p)
39 {
40         png_uint_32 width, height;
41         int bit_depth, color_type, interlace_type, compression_type, filter_type;
42         int channels;
43         
44         /* Get the image info */
45
46         png_get_IHDR (png_read_ptr, png_info_ptr,
47                       &width, &height,
48                       &bit_depth,
49                       &color_type,
50                       &interlace_type,
51                       &compression_type,
52                       &filter_type);
53
54         /* set_expand() basically needs to be called unless
55            we are already in RGB/RGBA mode
56         */
57         if (color_type == PNG_COLOR_TYPE_PALETTE &&
58             bit_depth <= 8) {
59
60                 /* Convert indexed images to RGB */
61                 png_set_expand (png_read_ptr);
62
63         } else if (color_type == PNG_COLOR_TYPE_GRAY &&
64                    bit_depth < 8) {
65
66                 /* Convert grayscale to RGB */
67                 png_set_expand (png_read_ptr);
68
69         } else if (png_get_valid (png_read_ptr, 
70                                   png_info_ptr, PNG_INFO_tRNS)) {
71
72                 /* If we have transparency header, convert it to alpha
73                    channel */
74                 png_set_expand(png_read_ptr);
75                 
76         } else if (bit_depth < 8) {
77
78                 /* If we have < 8 scale it up to 8 */
79                 png_set_expand(png_read_ptr);
80
81
82                 /* Conceivably, png_set_packing() is a better idea;
83                  * God only knows how libpng works
84                  */
85         }
86
87         /* If we are 16-bit, convert to 8-bit */
88         if (bit_depth == 16) {
89                 png_set_strip_16(png_read_ptr);
90         }
91
92         /* If gray scale, convert to RGB */
93         if (color_type == PNG_COLOR_TYPE_GRAY ||
94             color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
95                 png_set_gray_to_rgb(png_read_ptr);
96         }
97         
98         /* If interlaced, handle that */
99         if (interlace_type != PNG_INTERLACE_NONE) {
100                 png_set_interlace_handling(png_read_ptr);
101         }
102         
103         /* Update the info the reflect our transformations */
104         png_read_update_info(png_read_ptr, png_info_ptr);
105         
106         png_get_IHDR (png_read_ptr, png_info_ptr,
107                       &width, &height,
108                       &bit_depth,
109                       &color_type,
110                       &interlace_type,
111                       &compression_type,
112                       &filter_type);
113
114         *width_p = width;
115         *height_p = height;
116         *color_type_p = color_type;
117         
118 #ifndef G_DISABLE_CHECKS
119         /* Check that the new info is what we want */
120         
121         if (bit_depth != 8) {
122                 g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
123                 *fatal_error_occurred = TRUE;
124                 return;
125         }
126
127         if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
128                 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
129                 g_warning("Transformed PNG not RGB or RGBA.");
130                 *fatal_error_occurred = TRUE;
131                 return;
132         }
133
134         channels = png_get_channels(png_read_ptr, png_info_ptr);
135         if ( ! (channels == 3 || channels == 4) ) {
136                 g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
137                 *fatal_error_occurred = TRUE;
138                 return;
139         }
140 #endif
141 }
142
143 /* Destroy notification function for the pixbuf */
144 static void
145 free_buffer (guchar *pixels, gpointer data)
146 {
147         free (pixels);
148 }
149
150 /* Shared library entry point */
151 GdkPixbuf *
152 gdk_pixbuf__png_image_load (FILE *f)
153 {
154         png_structp png_ptr;
155         png_infop info_ptr, end_info;
156         gboolean failed = FALSE;
157         gint i, ctype, bpp;
158         png_uint_32 w, h;
159         png_bytepp rows;
160         guchar *pixels;
161
162         png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
163         if (!png_ptr)
164                 return NULL;
165
166         info_ptr = png_create_info_struct (png_ptr);
167         if (!info_ptr) {
168                 png_destroy_read_struct (&png_ptr, NULL, NULL);
169                 return NULL;
170         }
171
172         end_info = png_create_info_struct (png_ptr);
173         if (!end_info) {
174                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
175                 return NULL;
176         }
177
178         if (setjmp (png_ptr->jmpbuf)) {
179                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
180                 return NULL;
181         }
182
183         png_init_io (png_ptr, f);
184         png_read_info (png_ptr, info_ptr);
185
186         setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
187
188         if (failed) {
189                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
190                 return NULL;
191         }
192         
193         if (ctype & PNG_COLOR_MASK_ALPHA)
194                 bpp = 4;
195         else
196                 bpp = 3;
197
198         pixels = malloc (w * h * bpp);
199         if (!pixels) {
200                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
201                 return NULL;
202         }
203
204         rows = g_new (png_bytep, h);
205
206         for (i = 0; i < h; i++)
207                 rows[i] = pixels + i * w * bpp;
208
209         png_read_image (png_ptr, rows);
210         png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
211         g_free (rows);
212
213         if (ctype & PNG_COLOR_MASK_ALPHA)
214                 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
215                                                  w, h, w * 4,
216                                                  free_buffer, NULL);
217         else
218                 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
219                                                  w, h, w * 3,
220                                                  free_buffer, NULL);
221 }
222
223 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
224    just allow you to change the error reporting. */
225 static void png_error_callback  (png_structp png_read_ptr,
226                                  png_const_charp error_msg);
227
228 static void png_warning_callback(png_structp png_read_ptr,
229                                  png_const_charp warning_msg);
230
231 /* Called at the start of the progressive load */
232 static void png_info_callback   (png_structp png_read_ptr,
233                                  png_infop   png_info_ptr);
234
235 /* Called for each row; note that you will get duplicate row numbers
236    for interlaced PNGs */
237 static void png_row_callback   (png_structp png_read_ptr,
238                                 png_bytep   new_row,
239                                 png_uint_32 row_num,
240                                 int pass_num);
241
242 /* Called after reading the entire image */
243 static void png_end_callback   (png_structp png_read_ptr,
244                                 png_infop   png_info_ptr);
245
246 typedef struct _LoadContext LoadContext;
247
248 struct _LoadContext {
249         png_structp png_read_ptr;
250         png_infop   png_info_ptr;
251
252         ModulePreparedNotifyFunc prepare_func;
253         ModuleUpdatedNotifyFunc update_func;
254         gpointer notify_user_data;
255
256         GdkPixbuf* pixbuf;
257
258         /* row number of first row seen, or -1 if none yet seen */
259
260         gint first_row_seen_in_chunk;
261
262         /* pass number for the first row seen */
263
264         gint first_pass_seen_in_chunk;
265         
266         /* row number of last row seen */
267         gint last_row_seen_in_chunk;
268
269         gint last_pass_seen_in_chunk;
270
271         /* highest row number seen */
272         gint max_row_seen_in_chunk;
273         
274         guint fatal_error_occurred : 1;
275
276 };
277
278 gpointer
279 gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
280                                   ModuleUpdatedNotifyFunc update_func,
281                                   ModuleFrameDoneNotifyFunc frame_done_func,
282                                   ModuleAnimationDoneNotifyFunc anim_done_func,
283                                   gpointer user_data)
284 {
285         LoadContext* lc;
286         
287         lc = g_new0(LoadContext, 1);
288         
289         lc->fatal_error_occurred = FALSE;
290
291         lc->prepare_func = prepare_func;
292         lc->update_func = update_func;
293         lc->notify_user_data = user_data;
294
295         lc->first_row_seen_in_chunk = -1;
296         lc->last_row_seen_in_chunk = -1;
297         lc->first_pass_seen_in_chunk = -1;
298         lc->last_pass_seen_in_chunk = -1;
299         lc->max_row_seen_in_chunk = -1;
300         
301         /* Create the main PNG context struct */
302
303                 
304         lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
305                                                   lc, /* error/warning callback data */
306                                                   png_error_callback,
307                                                   png_warning_callback);
308
309         if (lc->png_read_ptr == NULL) {
310                 g_free(lc);
311                 return NULL;
312         }
313
314         if (setjmp (lc->png_read_ptr->jmpbuf)) {
315                 if (lc->png_info_ptr)
316                         png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
317                 g_free(lc);
318                 return NULL;
319         }
320
321         /* Create the auxiliary context struct */
322
323         lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
324
325         if (lc->png_info_ptr == NULL) {
326                 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
327                 g_free(lc);
328                 return NULL;
329         }
330
331         png_set_progressive_read_fn(lc->png_read_ptr,
332                                     lc, /* callback data */
333                                     png_info_callback,
334                                     png_row_callback,
335                                     png_end_callback);
336         
337
338         return lc;
339 }
340
341 void
342 gdk_pixbuf__png_image_stop_load (gpointer context)
343 {
344         LoadContext* lc = context;
345
346         g_return_if_fail(lc != NULL);
347
348         gdk_pixbuf_unref(lc->pixbuf);
349         
350         png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
351         g_free(lc);
352 }
353
354 gboolean
355 gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size)
356 {
357         LoadContext* lc = context;
358
359         g_return_val_if_fail(lc != NULL, FALSE);
360
361         /* reset */
362         lc->first_row_seen_in_chunk = -1;
363         lc->last_row_seen_in_chunk = -1;
364         lc->first_pass_seen_in_chunk = -1;
365         lc->last_pass_seen_in_chunk = -1;
366         lc->max_row_seen_in_chunk = -1;
367         
368         /* Invokes our callbacks as needed */
369         if (setjmp (lc->png_read_ptr->jmpbuf)) {
370                 return FALSE;
371         } else {
372                 png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size);
373         }
374
375         if (lc->fatal_error_occurred)
376                 return FALSE;
377         else {
378                 if (lc->first_row_seen_in_chunk >= 0) {
379                         /* We saw at least one row */
380                         gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
381                         
382                         g_assert(pass_diff >= 0);
383                         
384                         if (pass_diff == 0) {
385                                 /* start and end row were in the same pass */
386                                 (lc->update_func)(lc->pixbuf, 0,
387                                                   lc->first_row_seen_in_chunk,
388                                                   lc->pixbuf->width,
389                                                   (lc->last_row_seen_in_chunk -
390                                                    lc->first_row_seen_in_chunk) + 1,
391                                                   lc->notify_user_data);
392                         } else if (pass_diff == 1) {
393                                 /* We have from the first row seen to
394                                    the end of the image (max row
395                                    seen), then from the top of the
396                                    image to the last row seen */
397                                 /* first row to end */
398                                 (lc->update_func)(lc->pixbuf, 0,
399                                                   lc->first_row_seen_in_chunk,
400                                                   lc->pixbuf->width,
401                                                   (lc->max_row_seen_in_chunk -
402                                                    lc->first_row_seen_in_chunk) + 1,
403                                                   lc->notify_user_data);
404                                 /* top to last row */
405                                 (lc->update_func)(lc->pixbuf,
406                                                   0, 0, 
407                                                   lc->pixbuf->width,
408                                                   lc->last_row_seen_in_chunk + 1,
409                                                   lc->notify_user_data);
410                         } else {
411                                 /* We made at least one entire pass, so update the
412                                    whole image */
413                                 (lc->update_func)(lc->pixbuf,
414                                                   0, 0, 
415                                                   lc->pixbuf->width,
416                                                   lc->max_row_seen_in_chunk + 1,
417                                                   lc->notify_user_data);
418                         }
419                 }
420                 
421                 return TRUE;
422         }
423 }
424
425 /* Called at the start of the progressive load, once we have image info */
426 static void
427 png_info_callback   (png_structp png_read_ptr,
428                      png_infop   png_info_ptr)
429 {
430         LoadContext* lc;
431         png_uint_32 width, height;
432         int color_type;
433         gboolean have_alpha = FALSE;
434         gboolean failed = FALSE;
435         
436         lc = png_get_progressive_ptr(png_read_ptr);
437
438         if (lc->fatal_error_occurred)
439                 return;
440         
441
442         setup_png_transformations(lc->png_read_ptr,
443                                   lc->png_info_ptr,
444                                   &failed,
445                                   &width, &height, &color_type);
446
447         if (failed) {
448                 lc->fatal_error_occurred = TRUE;
449                 return;
450         }
451
452         /* If we have alpha, set a flag */
453         if (color_type & PNG_COLOR_MASK_ALPHA)
454                 have_alpha = TRUE;
455         
456         lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
457
458         if (lc->pixbuf == NULL) {
459                 /* Failed to allocate memory */
460                 lc->fatal_error_occurred = TRUE;
461                 return;
462         }
463         
464         /* Notify the client that we are ready to go */
465
466         if (lc->prepare_func)
467                 (* lc->prepare_func) (lc->pixbuf, lc->notify_user_data);
468         
469         return;
470 }
471
472 /* Called for each row; note that you will get duplicate row numbers
473    for interlaced PNGs */
474 static void
475 png_row_callback   (png_structp png_read_ptr,
476                     png_bytep   new_row,
477                     png_uint_32 row_num,
478                     int pass_num)
479 {
480         LoadContext* lc;
481         guchar* old_row = NULL;
482
483         lc = png_get_progressive_ptr(png_read_ptr);
484
485         if (lc->fatal_error_occurred)
486                 return;
487
488         if (lc->first_row_seen_in_chunk < 0) {
489                 lc->first_row_seen_in_chunk = row_num;
490                 lc->first_pass_seen_in_chunk = pass_num;
491         }
492
493         lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
494         lc->last_row_seen_in_chunk = row_num;
495         lc->last_pass_seen_in_chunk = pass_num;
496         
497         old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
498
499         png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
500 }
501
502 /* Called after reading the entire image */
503 static void
504 png_end_callback   (png_structp png_read_ptr,
505                     png_infop   png_info_ptr)
506 {
507         LoadContext* lc;
508
509         lc = png_get_progressive_ptr(png_read_ptr);
510
511         if (lc->fatal_error_occurred)
512                 return;
513 }
514
515 static void
516 png_error_callback(png_structp png_read_ptr,
517                    png_const_charp error_msg)
518 {
519         LoadContext* lc;
520         
521         lc = png_get_error_ptr(png_read_ptr);
522         
523         lc->fatal_error_occurred = TRUE;
524         
525         fprintf(stderr, "Fatal error loading PNG: %s\n", error_msg);
526 }
527
528 static void
529 png_warning_callback(png_structp png_read_ptr,
530                      png_const_charp warning_msg)
531 {
532         LoadContext* lc;
533         
534         lc = png_get_error_ptr(png_read_ptr);
535         
536         fprintf(stderr, "Warning loading PNG: %s\n", warning_msg);
537 }
538
539
540
541