io-png.c 24 KB
Newer Older
1
/* GdkPixbuf library - PNG image loader
2
 *
3
 * Copyright (C) 1999 Mark Crichton
4 5 6 7
 * Copyright (C) 1999 The Free Software Foundation
 *
 * Authors: Mark Crichton <crichton@gimp.org>
 *          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 12 13 14 15 16
 * 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
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public
20 21 22
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
23
 */
24

25 26
#include <config.h>
#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 33



34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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 132 133 134 135 136 137 138 139 140 141 142
static void
setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
                          gboolean *fatal_error_occurred,
                          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 */

        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;
        
#ifndef G_DISABLE_CHECKS
        /* Check that the new info is what we want */
        
        if (bit_depth != 8) {
                g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
                *fatal_error_occurred = TRUE;
                return;
        }

        if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
                color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
                g_warning("Transformed PNG not RGB or RGBA.");
                *fatal_error_occurred = TRUE;
                return;
        }

        channels = png_get_channels(png_read_ptr, png_info_ptr);
        if ( ! (channels == 3 || channels == 4) ) {
                g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
                *fatal_error_occurred = TRUE;
                return;
        }
#endif
}

Havoc Pennington's avatar
Havoc Pennington committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
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,
                             _("Fatal error saving PNG image file: %s"),
                             error_msg);
        }
}

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.
         */
}

175
/* Destroy notification function for the pixbuf */
176
static void
177
free_buffer (guchar *pixels, gpointer data)
178
{
179
	free (pixels);
180
}
181 182

/* Shared library entry point */
183
GdkPixbuf *
Havoc Pennington's avatar
Havoc Pennington committed
184
gdk_pixbuf__png_image_load (FILE *f, GError **error)
185
{
186 187
	png_structp png_ptr;
	png_infop info_ptr, end_info;
188
        gboolean failed = FALSE;
189
	gint i, ctype, bpp;
190
	png_uint_32 w, h;
191
	png_bytepp rows;
192
	guchar *pixels;
193

Havoc Pennington's avatar
Havoc Pennington committed
194 195 196 197
	png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
                                          error,
                                          png_simple_error_callback,
                                          png_simple_warning_callback);
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	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;
	}

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

	if (setjmp (png_ptr->jmpbuf)) {
		png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
		return NULL;
	}

218
	png_init_io (png_ptr, f);
219
	png_read_info (png_ptr, info_ptr);
220

221
        setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
222

223 224 225 226 227
        if (failed) {
                png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
                return NULL;
        }
        
228 229 230 231 232
	if (ctype & PNG_COLOR_MASK_ALPHA)
		bpp = 4;
	else
		bpp = 3;

233
	pixels = malloc (w * h * bpp);
234
	if (!pixels) {
Havoc Pennington's avatar
Havoc Pennington committed
235 236 237 238 239 240 241 242 243 244
                /* 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"));
                }
                
245 246 247
		png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
		return NULL;
	}
248

249
	rows = g_new (png_bytep, h);
250

251 252
	for (i = 0; i < h; i++)
		rows[i] = pixels + i * w * bpp;
253

254 255
	png_read_image (png_ptr, rows);
	png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
256
	g_free (rows);
257

258
	if (ctype & PNG_COLOR_MASK_ALPHA)
259
		return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
260 261
						 w, h, w * 4,
						 free_buffer, NULL);
262
	else
263
		return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
264 265
						 w, h, w * 3,
						 free_buffer, NULL);
266
}
267

268 269
/* I wish these avoided the setjmp()/longjmp() crap in libpng instead
   just allow you to change the error reporting. */
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
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;

297 298
        ModulePreparedNotifyFunc prepare_func;
        ModuleUpdatedNotifyFunc update_func;
299 300 301
        gpointer notify_user_data;

        GdkPixbuf* pixbuf;
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

        /* 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;
318 319 320
        
        guint fatal_error_occurred : 1;

Havoc Pennington's avatar
Havoc Pennington committed
321
        GError **error;
322 323 324
};

gpointer
Federico Mena Quintero's avatar
Federico Mena Quintero committed
325 326 327 328
gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
				  ModuleUpdatedNotifyFunc update_func,
				  ModuleFrameDoneNotifyFunc frame_done_func,
				  ModuleAnimationDoneNotifyFunc anim_done_func,
Havoc Pennington's avatar
Havoc Pennington committed
329 330
				  gpointer user_data,
                                  GError **error)
331 332 333 334 335 336 337
{
        LoadContext* lc;
        
        lc = g_new0(LoadContext, 1);
        
        lc->fatal_error_occurred = FALSE;

338 339
        lc->prepare_func = prepare_func;
        lc->update_func = update_func;
340 341
        lc->notify_user_data = user_data;

342 343 344 345 346
        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
347
        lc->error = error;
348
        
349 350
        /* Create the main PNG context struct */

351
		
352 353 354 355 356 357 358
        lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                  lc, /* error/warning callback data */
                                                  png_error_callback,
                                                  png_warning_callback);

        if (lc->png_read_ptr == NULL) {
                g_free(lc);
Havoc Pennington's avatar
Havoc Pennington committed
359
                /* error callback should have set the error */
360 361
                return NULL;
        }
Havoc Pennington's avatar
Havoc Pennington committed
362
        
363 364 365 366
	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
367
                /* error callback should have set the error */
368 369 370
                return NULL;
	}

371
        /* Create the auxiliary context struct */
372 373 374 375 376 377

        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
378
                /* error callback should have set the error */
379 380 381 382 383 384 385 386 387 388
                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
389 390 391 392 393
        /* We don't want to keep modifying error after returning here,
         * it may no longer be valid.
         */
        lc->error = NULL;
        
394 395 396 397
        return lc;
}

void
Federico Mena Quintero's avatar
Federico Mena Quintero committed
398
gdk_pixbuf__png_image_stop_load (gpointer context)
399 400 401 402 403 404 405 406 407 408 409
{
        LoadContext* lc = context;

        g_return_if_fail(lc != NULL);

        gdk_pixbuf_unref(lc->pixbuf);
        
        png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
        g_free(lc);
}

410
gboolean
Havoc Pennington's avatar
Havoc Pennington committed
411 412
gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size,
                                     GError **error)
413 414 415 416 417
{
        LoadContext* lc = context;

        g_return_val_if_fail(lc != NULL, FALSE);

418 419 420 421 422 423
        /* 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
424
        lc->error = error;
425
        
426
        /* Invokes our callbacks as needed */
427
	if (setjmp (lc->png_read_ptr->jmpbuf)) {
Havoc Pennington's avatar
Havoc Pennington committed
428
                lc->error = NULL;
429 430 431 432
		return FALSE;
	} else {
		png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size);
	}
433

Havoc Pennington's avatar
Havoc Pennington committed
434 435
        if (lc->fatal_error_occurred) {
                lc->error = NULL;
436
                return FALSE;
Havoc Pennington's avatar
Havoc Pennington committed
437
        } else {
438 439 440 441 442 443 444 445
                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 */
446
                                (lc->update_func)(lc->pixbuf, 0,
447
                                                  lc->first_row_seen_in_chunk,
448
                                                  lc->pixbuf->width,
449
                                                  (lc->last_row_seen_in_chunk -
450 451
                                                   lc->first_row_seen_in_chunk) + 1,
						  lc->notify_user_data);
452 453 454 455 456 457
                        } 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 */
458
                                (lc->update_func)(lc->pixbuf, 0,
459
                                                  lc->first_row_seen_in_chunk,
460
                                                  lc->pixbuf->width,
461
                                                  (lc->max_row_seen_in_chunk -
462 463
                                                   lc->first_row_seen_in_chunk) + 1,
						  lc->notify_user_data);
464
                                /* top to last row */
465
                                (lc->update_func)(lc->pixbuf,
466
                                                  0, 0, 
467
                                                  lc->pixbuf->width,
468 469
                                                  lc->last_row_seen_in_chunk + 1,
						  lc->notify_user_data);
470 471 472
                        } else {
                                /* We made at least one entire pass, so update the
                                   whole image */
473
                                (lc->update_func)(lc->pixbuf,
474
                                                  0, 0, 
475
                                                  lc->pixbuf->width,
476 477
                                                  lc->max_row_seen_in_chunk + 1,
						  lc->notify_user_data);
478 479
                        }
                }
Havoc Pennington's avatar
Havoc Pennington committed
480 481

                lc->error = NULL;
482
                
483
                return TRUE;
484
        }
485 486 487 488 489 490 491 492 493
}

/* 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;
494
        int color_type;
495
        gboolean have_alpha = FALSE;
496
        gboolean failed = FALSE;
497 498 499 500 501 502
        
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;

503 504 505 506
        setup_png_transformations(lc->png_read_ptr,
                                  lc->png_info_ptr,
                                  &failed,
                                  &width, &height, &color_type);
507

508 509 510
        if (failed) {
                lc->fatal_error_occurred = TRUE;
                return;
511 512 513 514
        }

        /* If we have alpha, set a flag */
        if (color_type & PNG_COLOR_MASK_ALPHA)
515
                have_alpha = TRUE;
516
        
517
        lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
518 519 520 521

        if (lc->pixbuf == NULL) {
                /* Failed to allocate memory */
                lc->fatal_error_occurred = TRUE;
Havoc Pennington's avatar
Havoc Pennington committed
522 523 524 525 526 527 528
                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);
                }
529 530 531 532 533
                return;
        }
        
        /* Notify the client that we are ready to go */

534 535
        if (lc->prepare_func)
                (* lc->prepare_func) (lc->pixbuf, lc->notify_user_data);
536 537 538 539 540 541 542 543 544 545 546 547 548 549
        
        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;
550

551 552 553 554
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
555 556 557 558 559 560 561 562 563 564

        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;
        
565
        old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
566

567 568 569 570 571 572 573
        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)
574
{
575 576 577 578 579 580
        LoadContext* lc;

        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
581
}
582 583 584 585 586 587 588 589 590 591

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
592 593 594 595 596 597 598 599 600 601 602

        /* 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);
        }
603 604 605 606 607 608 609 610 611
}

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
612 613 614 615 616 617 618

        /* 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.
         */
619
}
620 621


622
/* Save */
Havoc Pennington's avatar
Havoc Pennington committed
623

624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
gboolean
gdk_pixbuf__png_image_save (FILE          *f, 
                            GdkPixbuf     *pixbuf, 
                            gchar        **keys,
                            gchar        **values,
                            GError       **error)
{
       png_structp png_ptr;
       png_infop info_ptr;
       guchar *ptr;
       guchar *pixels;
       int x, y, j;
       png_bytep row_ptr, data = NULL;
       png_color_8 sig_bit;
       int w, h, rowstride;
       int has_alpha;
       int bpc;

       if (keys && *keys) {
               g_warning ("Bad option name '%s' passed to PNG saver",
                          *keys);
               return FALSE;
#if 0
               gchar **kiter = keys;
               gchar **viter = values;

               
               while (*kiter) {
                       
                       ++kiter;
                       ++viter;
               }
#endif
       }
       
       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
667 668 669
                                          error,
                                          png_simple_error_callback,
                                          png_simple_warning_callback);
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684

       g_return_val_if_fail (png_ptr != NULL, FALSE);

       info_ptr = png_create_info_struct (png_ptr);
       if (info_ptr == NULL) {
               png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
               return FALSE;
       }
       if (setjmp (png_ptr->jmpbuf)) {
               png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
               return FALSE;
       }
       png_init_io (png_ptr, f);
       if (has_alpha) {
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Havoc Pennington's avatar
Havoc Pennington committed
685 686
                             PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
687 688 689 690 691 692 693
#ifdef WORDS_BIGENDIAN
               png_set_swap_alpha (png_ptr);
#else
               png_set_bgr (png_ptr);
#endif
       } else {
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Havoc Pennington's avatar
Havoc Pennington committed
694 695
                             PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
696
               data = malloc (w * 3 * sizeof (char));
Havoc Pennington's avatar
Havoc Pennington committed
697 698 699 700 701 702 703 704 705 706 707 708 709 710

               if (data == NULL) {
                       /* 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 save PNG file"));
                       }
                       png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
                       return FALSE;
               }
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
       }
       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++) {
               if (has_alpha)
                       row_ptr = (png_bytep)ptr;
               else {
                       for (j = 0, x = 0; x < w; x++)
                               memcpy (&(data[x*3]), &(ptr[x*3]), 3);

                       row_ptr = (png_bytep)data;
               }
               png_write_rows (png_ptr, &row_ptr, 1);
               ptr += rowstride;
       }

       if (data)
               free (data);

       png_write_end (png_ptr, info_ptr);
       png_destroy_write_struct (&png_ptr, (png_infopp) NULL);

       return TRUE;
}

744 745