]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-png.c
Display the progressive load
[~andy/gtk] / gdk-pixbuf / io-png.c
1 /* GdkPixbuf library - JPEG 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 Library 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  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library 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 <png.h>
28 #include "gdk-pixbuf.h"
29 #include "gdk-pixbuf-io.h"
30
31 \f
32
33 static void
34 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
35                           gboolean *fatal_error_occurred,
36                           png_uint_32* width_p, png_uint_32* height_p,
37                           int* color_type_p)
38 {
39         png_uint_32 width, height;
40         int bit_depth, color_type, interlace_type, compression_type, filter_type;
41         int channels;
42         
43         /* Get the image info */
44
45         png_get_IHDR (png_read_ptr, png_info_ptr,
46                       &width, &height,
47                       &bit_depth,
48                       &color_type,
49                       &interlace_type,
50                       &compression_type,
51                       &filter_type);
52
53         /* set_expand() basically needs to be called unless
54            we are already in RGB/RGBA mode
55         */
56         if (color_type == PNG_COLOR_TYPE_PALETTE &&
57             bit_depth <= 8) {
58
59                 /* Convert indexed images to RGB */
60                 png_set_expand (png_read_ptr);
61
62         } else if (color_type == PNG_COLOR_TYPE_GRAY &&
63                    bit_depth < 8) {
64
65                 /* Convert grayscale to RGB */
66                 png_set_expand (png_read_ptr);
67
68         } else if (png_get_valid (png_read_ptr, 
69                                   png_info_ptr, PNG_INFO_tRNS)) {
70
71                 /* If we have transparency header, convert it to alpha
72                    channel */
73                 png_set_expand(png_read_ptr);
74                 
75         } else if (bit_depth < 8) {
76
77                 /* If we have < 8 scale it up to 8 */
78                 png_set_expand(png_read_ptr);
79
80
81                 /* Conceivably, png_set_packing() is a better idea;
82                  * God only knows how libpng works
83                  */
84         }
85
86         /* If we are 16-bit, convert to 8-bit */
87         if (bit_depth == 16) {
88                 png_set_strip_16(png_read_ptr);
89         }
90
91         /* If gray scale, convert to RGB */
92         if (color_type == PNG_COLOR_TYPE_GRAY ||
93             color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
94                 png_set_gray_to_rgb(png_read_ptr);
95         }
96         
97         /* If interlaced, handle that */
98         if (interlace_type != PNG_INTERLACE_NONE) {
99                 png_set_interlace_handling(png_read_ptr);
100         }
101         
102         /* Update the info the reflect our transformations */
103         png_read_update_info(png_read_ptr, png_info_ptr);
104         
105         png_get_IHDR (png_read_ptr, png_info_ptr,
106                       &width, &height,
107                       &bit_depth,
108                       &color_type,
109                       &interlace_type,
110                       &compression_type,
111                       &filter_type);
112
113         *width_p = width;
114         *height_p = height;
115         *color_type_p = color_type;
116         
117 #ifndef G_DISABLE_CHECKS
118         /* Check that the new info is what we want */
119         
120         if (bit_depth != 8) {
121                 g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
122                 *fatal_error_occurred = TRUE;
123                 return;
124         }
125
126         if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
127                 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
128                 g_warning("Transformed PNG not RGB or RGBA.");
129                 *fatal_error_occurred = TRUE;
130                 return;
131         }
132
133         channels = png_get_channels(png_read_ptr, png_info_ptr);
134         if ( ! (channels == 3 || channels == 4) ) {
135                 g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
136                 *fatal_error_occurred = TRUE;
137                 return;
138         }
139 #endif
140 }
141
142 /* Destroy notification function for the libart pixbuf */
143 static void
144 free_buffer (gpointer user_data, gpointer data)
145 {
146         free (data);
147 }
148
149 /* Shared library entry point */
150 GdkPixbuf *
151 image_load (FILE *f)
152 {
153         png_structp png_ptr;
154         png_infop info_ptr, end_info;
155         gboolean failed = FALSE;
156         gint i, depth, ctype, inttype, passes, bpp;
157         png_uint_32 w, h;
158         png_bytepp rows;
159         guchar *pixels;
160
161         png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
162         if (!png_ptr)
163                 return NULL;
164
165         info_ptr = png_create_info_struct (png_ptr);
166         if (!info_ptr) {
167                 png_destroy_read_struct (&png_ptr, NULL, NULL);
168                 return NULL;
169         }
170
171         end_info = png_create_info_struct (png_ptr);
172         if (!end_info) {
173                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
174                 return NULL;
175         }
176
177         if (setjmp (png_ptr->jmpbuf)) {
178                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
179                 return NULL;
180         }
181
182         png_init_io (png_ptr, f);
183         png_read_info (png_ptr, info_ptr);
184
185         setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
186
187         if (failed) {
188                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
189                 return NULL;
190         }
191         
192         if (ctype & PNG_COLOR_MASK_ALPHA)
193                 bpp = 4;
194         else
195                 bpp = 3;
196
197         pixels = malloc (w * h * bpp);
198         if (!pixels) {
199                 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
200                 return NULL;
201         }
202
203         rows = g_new (png_bytep, h);
204
205         for (i = 0; i < h; i++)
206                 rows[i] = pixels + i * w * bpp;
207
208         png_read_image (png_ptr, rows);
209         png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
210         g_free (rows);
211
212         if (ctype & PNG_COLOR_MASK_ALPHA)
213                 return gdk_pixbuf_new_from_data (pixels, ART_PIX_RGB, TRUE,
214                                                  w, h, w * 4,
215                                                  free_buffer, NULL);
216         else
217                 return gdk_pixbuf_new_from_data (pixels, ART_PIX_RGB, FALSE,
218                                                  w, h, w * 3,
219                                                  free_buffer, NULL);
220 }
221
222 /* These avoid the setjmp()/longjmp() crap in libpng */
223 static void png_error_callback  (png_structp png_read_ptr,
224                                  png_const_charp error_msg);
225
226 static void png_warning_callback(png_structp png_read_ptr,
227                                  png_const_charp warning_msg);
228
229 /* Called at the start of the progressive load */
230 static void png_info_callback   (png_structp png_read_ptr,
231                                  png_infop   png_info_ptr);
232
233 /* Called for each row; note that you will get duplicate row numbers
234    for interlaced PNGs */
235 static void png_row_callback   (png_structp png_read_ptr,
236                                 png_bytep   new_row,
237                                 png_uint_32 row_num,
238                                 int pass_num);
239
240 /* Called after reading the entire image */
241 static void png_end_callback   (png_structp png_read_ptr,
242                                 png_infop   png_info_ptr);
243
244 typedef struct _LoadContext LoadContext;
245
246 struct _LoadContext {
247         png_structp png_read_ptr;
248         png_infop   png_info_ptr;
249
250         ModulePreparedNotifyFunc notify_func;
251         gpointer notify_user_data;
252
253         GdkPixbuf* pixbuf;
254         
255         guint fatal_error_occurred : 1;
256
257 };
258
259 gpointer
260 image_begin_load (ModulePreparedNotifyFunc func, gpointer user_data)
261 {
262         LoadContext* lc;
263         
264         lc = g_new0(LoadContext, 1);
265         
266         lc->fatal_error_occurred = FALSE;
267
268         lc->notify_func = func;
269         lc->notify_user_data = user_data;
270
271         /* Create the main PNG context struct */
272
273         lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
274                                                   lc, /* error/warning callback data */
275                                                   png_error_callback,
276                                                   png_warning_callback);
277
278         if (lc->png_read_ptr == NULL) {
279                 g_free(lc);
280                 return NULL;
281         }
282
283         /* Create the auxiliary context struct */
284
285         lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
286
287         if (lc->png_info_ptr == NULL) {
288                 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
289                 g_free(lc);
290                 return NULL;
291         }
292
293         png_set_progressive_read_fn(lc->png_read_ptr,
294                                     lc, /* callback data */
295                                     png_info_callback,
296                                     png_row_callback,
297                                     png_end_callback);
298         
299
300         return lc;
301 }
302
303 void
304 image_stop_load (gpointer context)
305 {
306         LoadContext* lc = context;
307
308         g_return_if_fail(lc != NULL);
309
310         gdk_pixbuf_unref(lc->pixbuf);
311         
312         png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
313         g_free(lc);
314 }
315
316 gboolean
317 image_load_increment(gpointer context, guchar *buf, guint size)
318 {
319         LoadContext* lc = context;
320
321         g_return_val_if_fail(lc != NULL, FALSE);
322
323         /* Invokes our callbacks as needed */
324         png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size);
325
326         if (lc->fatal_error_occurred)
327                 return FALSE;
328         else
329                 return TRUE;
330 }
331
332 /* Called at the start of the progressive load, once we have image info */
333 static void
334 png_info_callback   (png_structp png_read_ptr,
335                      png_infop   png_info_ptr)
336 {
337         LoadContext* lc;
338         png_uint_32 width, height;
339         int color_type;
340         gboolean have_alpha = FALSE;
341         gboolean failed = FALSE;
342         
343         lc = png_get_progressive_ptr(png_read_ptr);
344
345         if (lc->fatal_error_occurred)
346                 return;
347         
348
349         setup_png_transformations(lc->png_read_ptr,
350                                   lc->png_info_ptr,
351                                   &failed,
352                                   &width, &height, &color_type);
353
354         if (failed) {
355                 lc->fatal_error_occurred = TRUE;
356                 return;
357         }
358
359         /* If we have alpha, set a flag */
360         if (color_type & PNG_COLOR_MASK_ALPHA)
361                 have_alpha = TRUE;
362         
363         lc->pixbuf = gdk_pixbuf_new(have_alpha, width, height);
364
365         if (lc->pixbuf == NULL) {
366                 /* Failed to allocate memory */
367                 lc->fatal_error_occurred = TRUE;
368                 return;
369         }
370         
371         /* Notify the client that we are ready to go */
372
373         if (lc->notify_func)
374                 (* lc->notify_func) (lc->pixbuf, lc->notify_user_data);
375         
376         return;
377 }
378
379 /* Called for each row; note that you will get duplicate row numbers
380    for interlaced PNGs */
381 static void
382 png_row_callback   (png_structp png_read_ptr,
383                     png_bytep   new_row,
384                     png_uint_32 row_num,
385                     int pass_num)
386 {
387         LoadContext* lc;
388         guchar* old_row = NULL;
389         
390         lc = png_get_progressive_ptr(png_read_ptr);
391
392         if (lc->fatal_error_occurred)
393                 return;
394                 
395         old_row = lc->pixbuf->art_pixbuf->pixels + (row_num * lc->pixbuf->art_pixbuf->rowstride);
396
397         png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
398 }
399
400 /* Called after reading the entire image */
401 static void
402 png_end_callback   (png_structp png_read_ptr,
403                     png_infop   png_info_ptr)
404 {
405         LoadContext* lc;
406
407         lc = png_get_progressive_ptr(png_read_ptr);
408
409         if (lc->fatal_error_occurred)
410                 return;
411
412         /* Doesn't do anything for now */
413 }
414
415
416 static void
417 png_error_callback(png_structp png_read_ptr,
418                    png_const_charp error_msg)
419 {
420         LoadContext* lc;
421         
422         lc = png_get_error_ptr(png_read_ptr);
423         
424         lc->fatal_error_occurred = TRUE;
425         
426         fprintf(stderr, "Fatal error loading PNG: %s\n", error_msg);
427 }
428
429 static void
430 png_warning_callback(png_structp png_read_ptr,
431                      png_const_charp warning_msg)
432 {
433         LoadContext* lc;
434         
435         lc = png_get_error_ptr(png_read_ptr);
436         
437         fprintf(stderr, "Warning loading PNG: %s\n", warning_msg);
438 }
439