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