io-png.c 31.3 KB
Newer Older
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/* GdkPixbuf library - PNG image loader
3
 *
4
 * Copyright (C) 1999 Mark Crichton
5
6
7
8
 * Copyright (C) 1999 The Free Software Foundation
 *
 * Authors: Mark Crichton <crichton@gimp.org>
 *          Federico Mena-Quintero <federico@gimp.org>
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
13
14
15
16
17
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
22
23
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
24
 */
25

26
27
#include <config.h>
#include <stdio.h>
28
#include <stdlib.h>
29
#include <png.h>
30
#include "gdk-pixbuf-private.h"
31
#include "gdk-pixbuf-io.h"
32
33
34



35
static gboolean
36
setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
37
                          GError **error,
38
39
40
41
42
43
44
45
46
                          png_uint_32* width_p, png_uint_32* height_p,
                          int* color_type_p)
{
        png_uint_32 width, height;
        int bit_depth, color_type, interlace_type, compression_type, filter_type;
        int channels;
        
        /* Get the image info */

47
48
49
50
51
52
53
54
55
56
57
        /* Must check bit depth, since png_get_IHDR generates an 
           FPE on bit_depth 0.
        */
        bit_depth = png_get_bit_depth (png_read_ptr, png_info_ptr);
        if (bit_depth < 1 || bit_depth > 16) {
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Bits per channel of PNG image is invalid."));
                return FALSE;
        }
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
        png_get_IHDR (png_read_ptr, png_info_ptr,
                      &width, &height,
                      &bit_depth,
                      &color_type,
                      &interlace_type,
                      &compression_type,
                      &filter_type);

        /* set_expand() basically needs to be called unless
           we are already in RGB/RGBA mode
        */
        if (color_type == PNG_COLOR_TYPE_PALETTE &&
            bit_depth <= 8) {

                /* Convert indexed images to RGB */
                png_set_expand (png_read_ptr);

        } else if (color_type == PNG_COLOR_TYPE_GRAY &&
                   bit_depth < 8) {

                /* Convert grayscale to RGB */
                png_set_expand (png_read_ptr);

        } else if (png_get_valid (png_read_ptr, 
                                  png_info_ptr, PNG_INFO_tRNS)) {

                /* If we have transparency header, convert it to alpha
                   channel */
                png_set_expand(png_read_ptr);
                
        } else if (bit_depth < 8) {

                /* If we have < 8 scale it up to 8 */
                png_set_expand(png_read_ptr);


                /* Conceivably, png_set_packing() is a better idea;
                 * God only knows how libpng works
                 */
        }

        /* If we are 16-bit, convert to 8-bit */
        if (bit_depth == 16) {
                png_set_strip_16(png_read_ptr);
        }

        /* If gray scale, convert to RGB */
        if (color_type == PNG_COLOR_TYPE_GRAY ||
            color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
                png_set_gray_to_rgb(png_read_ptr);
        }
        
        /* If interlaced, handle that */
        if (interlace_type != PNG_INTERLACE_NONE) {
                png_set_interlace_handling(png_read_ptr);
        }
        
        /* Update the info the reflect our transformations */
        png_read_update_info(png_read_ptr, png_info_ptr);
        
        png_get_IHDR (png_read_ptr, png_info_ptr,
                      &width, &height,
                      &bit_depth,
                      &color_type,
                      &interlace_type,
                      &compression_type,
                      &filter_type);

        *width_p = width;
        *height_p = height;
        *color_type_p = color_type;
        
        /* Check that the new info is what we want */
        
132
133
134
135
136
137
138
139
        if (width == 0 || height == 0) {
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Transformed PNG has zero width or height."));
                return FALSE;
        }

140
        if (bit_depth != 8) {
141
142
143
144
145
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Bits per channel of transformed PNG is not 8."));
                return FALSE;
146
147
148
149
        }

        if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
                color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
150
151
152
153
154
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Transformed PNG not RGB or RGBA."));
                return FALSE;
155
156
157
158
        }

        channels = png_get_channels(png_read_ptr, png_info_ptr);
        if ( ! (channels == 3 || channels == 4) ) {
159
160
161
162
163
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
                return FALSE;
164
        }
165
        return TRUE;
166
167
}

Havoc Pennington's avatar
Havoc Pennington committed
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
static void
png_simple_error_callback(png_structp png_save_ptr,
                          png_const_charp error_msg)
{
        GError **error;
        
        error = png_get_error_ptr(png_save_ptr);

        /* I don't trust libpng to call the error callback only once,
         * so check for already-set error
         */
        if (error && *error == NULL) {
                g_set_error (error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_FAILED,
183
                             _("Fatal error in PNG image file: %s"),
Havoc Pennington's avatar
Havoc Pennington committed
184
185
                             error_msg);
        }
186
187

        longjmp (png_save_ptr->jmpbuf, 1);
Havoc Pennington's avatar
Havoc Pennington committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
}

static void
png_simple_warning_callback(png_structp png_save_ptr,
                            png_const_charp warning_msg)
{
        /* Don't print anything; we should not be dumping junk to
         * stderr, since that may be bad for some apps. If it's
         * important enough to display, we need to add a GError
         * **warning return location wherever we have an error return
         * location.
         */
}

202
/* Destroy notification function for the pixbuf */
203
static void
204
free_buffer (guchar *pixels, gpointer data)
205
{
206
	g_free (pixels);
207
}
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
static gboolean
png_text_to_pixbuf_option (png_text   text_ptr,
                           gchar    **key,
                           gchar    **value)
{
        *value = g_convert (text_ptr.text, -1, 
                            "UTF-8", "ISO-8859-1", 
                            NULL, NULL, NULL);
        if (*value) {
                *key = g_strconcat ("tEXt::", text_ptr.key, NULL);
                return TRUE;
        } else {
                g_warning ("Couldn't convert tEXt chunk value to UTF-8.");
                *key = NULL;
                return FALSE;
        }
}

227
228
229
230
231
232
233
234
235
236
237
238
static png_voidp
png_malloc_callback (png_structp o, png_size_t size)
{
        return g_try_malloc (size);
}

static void
png_free_callback (png_structp o, png_voidp x)
{
        g_free (x);
}

239
/* Shared library entry point */
240
static GdkPixbuf *
Havoc Pennington's avatar
Havoc Pennington committed
241
gdk_pixbuf__png_image_load (FILE *f, GError **error)
242
{
243
        GdkPixbuf *pixbuf;
244
	png_structp png_ptr;
Matthias Clasen's avatar
Matthias Clasen committed
245
	png_infop info_ptr;
246
        png_textp text_ptr;
247
	gint i, ctype, bpp;
248
	png_uint_32 w, h;
249
250
	png_bytepp volatile rows = NULL;
	guchar * volatile pixels = NULL;
251
252
        gint    num_texts;
        gchar **options = NULL;
253

254
255
256
257
258
259
260
261
262
#ifdef PNG_USER_MEM_SUPPORTED
	png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
                                            error,
                                            png_simple_error_callback,
                                            png_simple_warning_callback,
                                            NULL, 
                                            png_malloc_callback, 
                                            png_free_callback);
#else
Havoc Pennington's avatar
Havoc Pennington committed
263
264
265
266
	png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
                                          error,
                                          png_simple_error_callback,
                                          png_simple_warning_callback);
267
#endif
268
269
270
271
272
273
274
275
276
277
	if (!png_ptr)
		return NULL;

	info_ptr = png_create_info_struct (png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct (&png_ptr, NULL, NULL);
		return NULL;
	}

	if (setjmp (png_ptr->jmpbuf)) {
278
279
280
281
282
283
	    	if (rows)
		  	g_free (rows);

		if (pixels)
			g_free (pixels);

Matthias Clasen's avatar
Matthias Clasen committed
284
		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
285
286
287
		return NULL;
	}

288
	png_init_io (png_ptr, f);
289
	png_read_info (png_ptr, info_ptr);
290

291
        if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) {
Matthias Clasen's avatar
Matthias Clasen committed
292
                png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
293
294
295
                return NULL;
        }
        
296
297
298
299
300
	if (ctype & PNG_COLOR_MASK_ALPHA)
		bpp = 4;
	else
		bpp = 3;

301
	pixels = g_try_malloc (w * h * bpp);
302
	if (!pixels) {
Havoc Pennington's avatar
Havoc Pennington committed
303
304
305
306
307
308
309
310
311
312
                /* Check error NULL, normally this would be broken,
                 * but libpng makes me want to code defensively.
                 */
                if (error && *error == NULL) {
                        g_set_error (error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                     _("Insufficient memory to load PNG file"));
                }
                
Matthias Clasen's avatar
Matthias Clasen committed
313
		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
314
315
		return NULL;
	}
316

317
	rows = g_new (png_bytep, h);
318

319
320
	for (i = 0; i < h; i++)
		rows[i] = pixels + i * w * bpp;
321

322
	png_read_image (png_ptr, rows);
Matthias Clasen's avatar
Matthias Clasen committed
323
        png_read_end (png_ptr, info_ptr);
324
325
326
327
328
329
330
331
332

        if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) {
                options = g_new (gchar *, num_texts * 2);
                for (i = 0; i < num_texts; i++) {
                        png_text_to_pixbuf_option (text_ptr[i], 
                                                   options + 2*i, 
                                                   options + 2*i + 1);
                }
        }
Matthias Clasen's avatar
Matthias Clasen committed
333
	png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
334
	g_free (rows);
335

336
	if (ctype & PNG_COLOR_MASK_ALPHA)
337
338
339
		pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
                                                   w, h, w * 4,
                                                   free_buffer, NULL);
340
	else
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
		pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
                                                   w, h, w * 3,
                                                   free_buffer, NULL);

        if (options) {
                for (i = 0; i < num_texts; i++) {
                        if (pixbuf) {
                                if (!gdk_pixbuf_set_option (pixbuf, 
                                                            options[2*i], 
                                                            options[2*i+1]))
                                        g_warning ("Got multiple tEXt chunks for the same key.");
                        }
                        g_free (options[2*i]);
                        g_free (options[2*i+1]);
                }
                g_free (options);
        }

        return pixbuf;
360
}
361

362
363
/* I wish these avoided the setjmp()/longjmp() crap in libpng instead
   just allow you to change the error reporting. */
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
static void png_error_callback  (png_structp png_read_ptr,
                                 png_const_charp error_msg);

static void png_warning_callback(png_structp png_read_ptr,
                                 png_const_charp warning_msg);

/* Called at the start of the progressive load */
static void png_info_callback   (png_structp png_read_ptr,
                                 png_infop   png_info_ptr);

/* Called for each row; note that you will get duplicate row numbers
   for interlaced PNGs */
static void png_row_callback   (png_structp png_read_ptr,
                                png_bytep   new_row,
                                png_uint_32 row_num,
                                int pass_num);

/* Called after reading the entire image */
static void png_end_callback   (png_structp png_read_ptr,
                                png_infop   png_info_ptr);

typedef struct _LoadContext LoadContext;

struct _LoadContext {
        png_structp png_read_ptr;
        png_infop   png_info_ptr;

391
392
        ModulePreparedNotifyFunc prepare_func;
        ModuleUpdatedNotifyFunc update_func;
393
394
395
        gpointer notify_user_data;

        GdkPixbuf* pixbuf;
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411

        /* row number of first row seen, or -1 if none yet seen */

        gint first_row_seen_in_chunk;

        /* pass number for the first row seen */

        gint first_pass_seen_in_chunk;
        
        /* row number of last row seen */
        gint last_row_seen_in_chunk;

        gint last_pass_seen_in_chunk;

        /* highest row number seen */
        gint max_row_seen_in_chunk;
412
413
414
        
        guint fatal_error_occurred : 1;

Havoc Pennington's avatar
Havoc Pennington committed
415
        GError **error;
416
417
};

418
static gpointer
Federico Mena Quintero's avatar
Federico Mena Quintero committed
419
420
gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
				  ModuleUpdatedNotifyFunc update_func,
Havoc Pennington's avatar
Havoc Pennington committed
421
422
				  gpointer user_data,
                                  GError **error)
423
424
425
426
427
428
429
{
        LoadContext* lc;
        
        lc = g_new0(LoadContext, 1);
        
        lc->fatal_error_occurred = FALSE;

430
431
        lc->prepare_func = prepare_func;
        lc->update_func = update_func;
432
433
        lc->notify_user_data = user_data;

434
435
436
437
438
        lc->first_row_seen_in_chunk = -1;
        lc->last_row_seen_in_chunk = -1;
        lc->first_pass_seen_in_chunk = -1;
        lc->last_pass_seen_in_chunk = -1;
        lc->max_row_seen_in_chunk = -1;
Havoc Pennington's avatar
Havoc Pennington committed
439
        lc->error = error;
440
        
441
442
        /* Create the main PNG context struct */

443
444
445
446
447
448
449
450
451
#ifdef PNG_USER_MEM_SUPPORTED
        lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
                                                     lc, /* error/warning callback data */
                                                     png_error_callback,
                                                     png_warning_callback,
                                                     NULL,
                                                     png_malloc_callback,
                                                     png_free_callback);
#else
452
453
454
455
        lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                  lc, /* error/warning callback data */
                                                  png_error_callback,
                                                  png_warning_callback);
456
#endif
457
458
        if (lc->png_read_ptr == NULL) {
                g_free(lc);
Havoc Pennington's avatar
Havoc Pennington committed
459
                /* error callback should have set the error */
460
461
                return NULL;
        }
Havoc Pennington's avatar
Havoc Pennington committed
462
        
463
464
465
466
	if (setjmp (lc->png_read_ptr->jmpbuf)) {
		if (lc->png_info_ptr)
			png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
                g_free(lc);
Havoc Pennington's avatar
Havoc Pennington committed
467
                /* error callback should have set the error */
468
469
470
                return NULL;
	}

471
        /* Create the auxiliary context struct */
472
473
474
475
476
477

        lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);

        if (lc->png_info_ptr == NULL) {
                png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
                g_free(lc);
Havoc Pennington's avatar
Havoc Pennington committed
478
                /* error callback should have set the error */
479
480
481
482
483
484
485
486
487
488
                return NULL;
        }

        png_set_progressive_read_fn(lc->png_read_ptr,
                                    lc, /* callback data */
                                    png_info_callback,
                                    png_row_callback,
                                    png_end_callback);
        

Havoc Pennington's avatar
Havoc Pennington committed
489
490
491
492
493
        /* We don't want to keep modifying error after returning here,
         * it may no longer be valid.
         */
        lc->error = NULL;
        
494
495
496
        return lc;
}

497
498
static gboolean
gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
499
500
501
{
        LoadContext* lc = context;

502
        g_return_val_if_fail(lc != NULL, TRUE);
503

504
505
506
507
        /* FIXME this thing needs to report errors if
         * we have unused image data
         */
        
508
        if (lc->pixbuf)
509
                g_object_unref (lc->pixbuf);
510
        
Matthias Clasen's avatar
Matthias Clasen committed
511
        png_destroy_read_struct(&lc->png_read_ptr, &lc->png_info_ptr, NULL);
512
        g_free(lc);
513
514

        return TRUE;
515
516
}

517
518
519
static gboolean
gdk_pixbuf__png_image_load_increment(gpointer context,
                                     const guchar *buf, guint size,
Havoc Pennington's avatar
Havoc Pennington committed
520
                                     GError **error)
521
522
523
524
525
{
        LoadContext* lc = context;

        g_return_val_if_fail(lc != NULL, FALSE);

526
527
528
529
530
531
        /* reset */
        lc->first_row_seen_in_chunk = -1;
        lc->last_row_seen_in_chunk = -1;
        lc->first_pass_seen_in_chunk = -1;
        lc->last_pass_seen_in_chunk = -1;
        lc->max_row_seen_in_chunk = -1;
Havoc Pennington's avatar
Havoc Pennington committed
532
        lc->error = error;
533
        
534
        /* Invokes our callbacks as needed */
535
	if (setjmp (lc->png_read_ptr->jmpbuf)) {
Havoc Pennington's avatar
Havoc Pennington committed
536
                lc->error = NULL;
537
538
		return FALSE;
	} else {
539
540
		png_process_data(lc->png_read_ptr, lc->png_info_ptr,
                                 (guchar*) buf, size);
541
	}
542

Havoc Pennington's avatar
Havoc Pennington committed
543
544
        if (lc->fatal_error_occurred) {
                lc->error = NULL;
545
                return FALSE;
Havoc Pennington's avatar
Havoc Pennington committed
546
        } else {
547
548
549
550
551
552
553
554
                if (lc->first_row_seen_in_chunk >= 0) {
                        /* We saw at least one row */
                        gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
                        
                        g_assert(pass_diff >= 0);
                        
                        if (pass_diff == 0) {
                                /* start and end row were in the same pass */
555
                                (lc->update_func)(lc->pixbuf, 0,
556
                                                  lc->first_row_seen_in_chunk,
557
                                                  lc->pixbuf->width,
558
                                                  (lc->last_row_seen_in_chunk -
559
560
                                                   lc->first_row_seen_in_chunk) + 1,
						  lc->notify_user_data);
561
562
563
564
565
566
                        } else if (pass_diff == 1) {
                                /* We have from the first row seen to
                                   the end of the image (max row
                                   seen), then from the top of the
                                   image to the last row seen */
                                /* first row to end */
567
                                (lc->update_func)(lc->pixbuf, 0,
568
                                                  lc->first_row_seen_in_chunk,
569
                                                  lc->pixbuf->width,
570
                                                  (lc->max_row_seen_in_chunk -
571
572
                                                   lc->first_row_seen_in_chunk) + 1,
						  lc->notify_user_data);
573
                                /* top to last row */
574
                                (lc->update_func)(lc->pixbuf,
575
                                                  0, 0, 
576
                                                  lc->pixbuf->width,
577
578
                                                  lc->last_row_seen_in_chunk + 1,
						  lc->notify_user_data);
579
580
581
                        } else {
                                /* We made at least one entire pass, so update the
                                   whole image */
582
                                (lc->update_func)(lc->pixbuf,
583
                                                  0, 0, 
584
                                                  lc->pixbuf->width,
585
586
                                                  lc->max_row_seen_in_chunk + 1,
						  lc->notify_user_data);
587
588
                        }
                }
Havoc Pennington's avatar
Havoc Pennington committed
589
590

                lc->error = NULL;
591
                
592
                return TRUE;
593
        }
594
595
596
597
598
599
600
601
602
}

/* Called at the start of the progressive load, once we have image info */
static void
png_info_callback   (png_structp png_read_ptr,
                     png_infop   png_info_ptr)
{
        LoadContext* lc;
        png_uint_32 width, height;
603
604
        png_textp png_text_ptr;
        int i, num_texts;
605
        int color_type;
606
607
608
609
610
611
612
        gboolean have_alpha = FALSE;
        
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;

613
614
615
616
        if (!setup_png_transformations(lc->png_read_ptr,
                                       lc->png_info_ptr,
                                       lc->error,
                                       &width, &height, &color_type)) {
617
618
                lc->fatal_error_occurred = TRUE;
                return;
619
620
621
622
        }

        /* If we have alpha, set a flag */
        if (color_type & PNG_COLOR_MASK_ALPHA)
623
                have_alpha = TRUE;
624
        
625
        lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
626
627
628
629

        if (lc->pixbuf == NULL) {
                /* Failed to allocate memory */
                lc->fatal_error_occurred = TRUE;
Havoc Pennington's avatar
Havoc Pennington committed
630
631
632
633
634
635
636
                if (lc->error && *lc->error == NULL) {
                        g_set_error (lc->error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                     _("Insufficient memory to store a %ld by %ld image; try exiting some applications to reduce memory usage"),
                                     width, height);
                }
637
638
                return;
        }
639
640

        /* Extract tEXt chunks and attach them as pixbuf options */
641
        
642
643
644
645
646
647
648
649
650
651
652
653
654
        if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) {
                for (i = 0; i < num_texts; i++) {
                        gchar *key, *value;

                        if (png_text_to_pixbuf_option (png_text_ptr[i],
                                                       &key, &value)) {
                                gdk_pixbuf_set_option (lc->pixbuf, key, value);
                                g_free (key);
                                g_free (value);
                        }
                }
        }

655
656
        /* Notify the client that we are ready to go */

657
        if (lc->prepare_func)
Havoc Pennington's avatar
Havoc Pennington committed
658
                (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
659
660
661
662
663
664
665
666
667
668
669
670
671
672
        
        return;
}

/* Called for each row; note that you will get duplicate row numbers
   for interlaced PNGs */
static void
png_row_callback   (png_structp png_read_ptr,
                    png_bytep   new_row,
                    png_uint_32 row_num,
                    int pass_num)
{
        LoadContext* lc;
        guchar* old_row = NULL;
673

674
675
676
677
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
678

679
680
681
682
683
684
685
686
687
688
689
        if (row_num < 0 || row_num >= lc->pixbuf->height) {
                lc->fatal_error_occurred = TRUE;
                if (lc->error && *lc->error == NULL) {
                        g_set_error (lc->error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("Fatal error reading PNG image file"));
                }
                return;
        }

690
691
692
693
694
695
696
697
698
        if (lc->first_row_seen_in_chunk < 0) {
                lc->first_row_seen_in_chunk = row_num;
                lc->first_pass_seen_in_chunk = pass_num;
        }

        lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
        lc->last_row_seen_in_chunk = row_num;
        lc->last_pass_seen_in_chunk = pass_num;
        
699
        old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
700

701
702
703
704
705
706
707
        png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
}

/* Called after reading the entire image */
static void
png_end_callback   (png_structp png_read_ptr,
                    png_infop   png_info_ptr)
708
{
709
710
711
712
713
714
        LoadContext* lc;

        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
715
}
716
717
718
719
720
721
722
723
724
725

static void
png_error_callback(png_structp png_read_ptr,
                   png_const_charp error_msg)
{
        LoadContext* lc;
        
        lc = png_get_error_ptr(png_read_ptr);
        
        lc->fatal_error_occurred = TRUE;
Havoc Pennington's avatar
Havoc Pennington committed
726
727
728
729
730
731
732
733
734
735
736

        /* I don't trust libpng to call the error callback only once,
         * so check for already-set error
         */
        if (lc->error && *lc->error == NULL) {
                g_set_error (lc->error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Fatal error reading PNG image file: %s"),
                             error_msg);
        }
737
738

        longjmp (png_read_ptr->jmpbuf, 1);
739
740
741
742
743
744
745
746
747
}

static void
png_warning_callback(png_structp png_read_ptr,
                     png_const_charp warning_msg)
{
        LoadContext* lc;
        
        lc = png_get_error_ptr(png_read_ptr);
Havoc Pennington's avatar
Havoc Pennington committed
748
749
750
751
752
753
754

        /* Don't print anything; we should not be dumping junk to
         * stderr, since that may be bad for some apps. If it's
         * important enough to display, we need to add a GError
         * **warning return location wherever we have an error return
         * location.
         */
755
}
756
757


758
/* Save */
Havoc Pennington's avatar
Havoc Pennington committed
759

760
static gboolean
761
762
763
764
765
766
767
768
gdk_pixbuf__png_image_save (FILE          *f, 
                            GdkPixbuf     *pixbuf, 
                            gchar        **keys,
                            gchar        **values,
                            GError       **error)
{
       png_structp png_ptr;
       png_infop info_ptr;
769
       png_textp text_ptr = NULL;
770
771
       guchar *ptr;
       guchar *pixels;
772
773
       int y;
       int i;
774
       png_bytep row_ptr;
775
776
777
778
       png_color_8 sig_bit;
       int w, h, rowstride;
       int has_alpha;
       int bpc;
779
       int num_keys;
780
       gboolean success = TRUE;
781
782

       num_keys = 0;
783
784

       if (keys && *keys) {
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
               gchar **kiter;
               gchar  *key;
               int     len;

               for (kiter = keys; *kiter; kiter++) {
                       if (strncmp (*kiter, "tEXt::", 6) != 0) {
                                g_warning ("Bad option name '%s' passed to PNG saver", *kiter);
                                return FALSE;
                       }
                       key = *kiter + 6;
                       len = strlen (key);
                       if (len <= 1 || len > 79) {
                               g_set_error (error,
                                            GDK_PIXBUF_ERROR,
                                            GDK_PIXBUF_ERROR_BAD_OPTION,
                                            _("Keys for PNG tEXt chunks must have at least 1 and at most 79 characters."));
                               return FALSE;
                       }
                       for (i = 0; i < len; i++) {
                               if ((guchar) key[i] > 127) {
                                       g_set_error (error,
                                                    GDK_PIXBUF_ERROR,
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
                                                    _("Keys for PNG tEXt chunks must be ASCII characters."));
                                       return FALSE;
                               }
                       }
                       num_keys++;
               }
       }

       if (num_keys > 0) {
               text_ptr = g_new0 (png_text, num_keys);
               for (i = 0; i < num_keys; i++) {
                       text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
                       text_ptr[i].key  = keys[i] + 6;
                       text_ptr[i].text = g_convert (values[i], -1, 
                                                     "ISO-8859-1", "UTF-8", 
                                                     NULL, &text_ptr[i].text_length, 
                                                     NULL);
                       if (!text_ptr[i].text) {
                               g_set_error (error,
                                            GDK_PIXBUF_ERROR,
                                            GDK_PIXBUF_ERROR_BAD_OPTION,
                                            _("Value for PNG tEXt chunk can not be converted to ISO-8859-1 encoding."));
                               num_keys = i;
                               for (i = 0; i < num_keys; i++)
                                       g_free (text_ptr[i].text);
                               g_free (text_ptr);
                               return FALSE;
                       }
836
837
               }
       }
838

839
840
841
842
843
844
845
846
       bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
       w = gdk_pixbuf_get_width (pixbuf);
       h = gdk_pixbuf_get_height (pixbuf);
       rowstride = gdk_pixbuf_get_rowstride (pixbuf);
       has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
       pixels = gdk_pixbuf_get_pixels (pixbuf);

       png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
Havoc Pennington's avatar
Havoc Pennington committed
847
848
849
                                          error,
                                          png_simple_error_callback,
                                          png_simple_warning_callback);
850
851
852
853
854

       g_return_val_if_fail (png_ptr != NULL, FALSE);

       info_ptr = png_create_info_struct (png_ptr);
       if (info_ptr == NULL) {
855
856
	       success = FALSE;
	       goto cleanup;
857
858
       }
       if (setjmp (png_ptr->jmpbuf)) {
859
860
	       success = FALSE;
	       goto cleanup;
861
       }
862
863
864
865
866

       if (num_keys > 0) {
               png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
       }

867
       png_init_io (png_ptr, f);
868

869
870
       if (has_alpha) {
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Havoc Pennington's avatar
Havoc Pennington committed
871
872
                             PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
873
874
       } else {
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Havoc Pennington's avatar
Havoc Pennington committed
875
876
                             PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
877
878
879
880
881
882
883
884
885
886
887
888
       }
       sig_bit.red = bpc;
       sig_bit.green = bpc;
       sig_bit.blue = bpc;
       sig_bit.alpha = bpc;
       png_set_sBIT (png_ptr, info_ptr, &sig_bit);
       png_write_info (png_ptr, info_ptr);
       png_set_shift (png_ptr, &sig_bit);
       png_set_packing (png_ptr);

       ptr = pixels;
       for (y = 0; y < h; y++) {
889
               row_ptr = (png_bytep)ptr;
890
891
892
893
894
               png_write_rows (png_ptr, &row_ptr, 1);
               ptr += rowstride;
       }

       png_write_end (png_ptr, info_ptr);
895
896

cleanup:
897
898
       png_destroy_write_struct (&png_ptr, (png_infopp) NULL);

899
900
901
902
903
904
       if (num_keys > 0) {
               for (i = 0; i < num_keys; i++)
                       g_free (text_ptr[i].text);
               g_free (text_ptr);
       }

905
       return success;
906
907
}

908
909


910
911
912
913
914
915
916
917
918
void
gdk_pixbuf__png_fill_vtable (GdkPixbufModule *module)
{
  module->load = gdk_pixbuf__png_image_load;
  module->begin_load = gdk_pixbuf__png_image_begin_load;
  module->stop_load = gdk_pixbuf__png_image_stop_load;
  module->load_increment = gdk_pixbuf__png_image_load_increment;
  module->save = gdk_pixbuf__png_image_save;
}