io-png.c 24.5 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
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,
158
                             _("Fatal error in PNG image file: %s"),
Havoc Pennington's avatar
Havoc Pennington committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
                             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
	g_free (pixels);
180
}
181 182

/* Shared library entry point */
183
static 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 = g_try_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
static gpointer
Federico Mena Quintero's avatar
Federico Mena Quintero committed
325 326
gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
				  ModuleUpdatedNotifyFunc update_func,
Havoc Pennington's avatar
Havoc Pennington committed
327 328
				  gpointer user_data,
                                  GError **error)
329 330 331 332 333 334 335
{
        LoadContext* lc;
        
        lc = g_new0(LoadContext, 1);
        
        lc->fatal_error_occurred = FALSE;

336 337
        lc->prepare_func = prepare_func;
        lc->update_func = update_func;
338 339
        lc->notify_user_data = user_data;

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

349
		
350 351 352 353 354 355 356
        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
357
                /* error callback should have set the error */
358 359
                return NULL;
        }
Havoc Pennington's avatar
Havoc Pennington committed
360
        
361 362 363 364
	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
365
                /* error callback should have set the error */
366 367 368
                return NULL;
	}

369
        /* Create the auxiliary context struct */
370 371 372 373 374 375

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

395 396
static gboolean
gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
397 398 399
{
        LoadContext* lc = context;

400
        g_return_val_if_fail(lc != NULL, TRUE);
401

402 403 404 405
        /* FIXME this thing needs to report errors if
         * we have unused image data
         */
        
406 407 408 409
        gdk_pixbuf_unref(lc->pixbuf);
        
        png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
        g_free(lc);
410 411

        return TRUE;
412 413
}

414 415 416
static gboolean
gdk_pixbuf__png_image_load_increment(gpointer context,
                                     const guchar *buf, guint size,
Havoc Pennington's avatar
Havoc Pennington committed
417
                                     GError **error)
418 419 420 421 422
{
        LoadContext* lc = context;

        g_return_val_if_fail(lc != NULL, FALSE);

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

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

                lc->error = NULL;
488
                
489
                return TRUE;
490
        }
491 492 493 494 495 496 497 498 499
}

/* 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;
500
        int color_type;
501
        gboolean have_alpha = FALSE;
502
        gboolean failed = FALSE;
503 504 505 506 507 508
        
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;

509 510 511 512
        setup_png_transformations(lc->png_read_ptr,
                                  lc->png_info_ptr,
                                  &failed,
                                  &width, &height, &color_type);
513

514 515 516
        if (failed) {
                lc->fatal_error_occurred = TRUE;
                return;
517 518 519 520
        }

        /* If we have alpha, set a flag */
        if (color_type & PNG_COLOR_MASK_ALPHA)
521
                have_alpha = TRUE;
522
        
523
        lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
524 525 526 527

        if (lc->pixbuf == NULL) {
                /* Failed to allocate memory */
                lc->fatal_error_occurred = TRUE;
Havoc Pennington's avatar
Havoc Pennington committed
528 529 530 531 532 533 534
                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);
                }
535 536 537 538 539
                return;
        }
        
        /* Notify the client that we are ready to go */

540
        if (lc->prepare_func)
Havoc Pennington's avatar
Havoc Pennington committed
541
                (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
542 543 544 545 546 547 548 549 550 551 552 553 554 555
        
        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;
556

557 558 559 560
        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
561 562 563 564 565 566 567 568 569 570

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

573 574 575 576 577 578 579
        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)
580
{
581 582 583 584 585 586
        LoadContext* lc;

        lc = png_get_progressive_ptr(png_read_ptr);

        if (lc->fatal_error_occurred)
                return;
587
}
588 589 590 591 592 593 594 595 596 597

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
598 599 600 601 602 603 604 605 606 607 608

        /* 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);
        }
609 610 611 612 613 614 615 616 617
}

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
618 619 620 621 622 623 624

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


628
/* Save */
Havoc Pennington's avatar
Havoc Pennington committed
629

630
static gboolean
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 667 668 669 670 671 672
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
673 674 675
                                          error,
                                          png_simple_error_callback,
                                          png_simple_warning_callback);
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690

       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
691 692
                             PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
693 694 695 696 697 698 699
#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
700 701
                             PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
702
               data = g_try_malloc (w * 3 * sizeof (char));
Havoc Pennington's avatar
Havoc Pennington committed
703 704 705 706 707 708 709 710 711 712 713 714 715 716

               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;
               }
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
       }
       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)
742
               g_free (data);
743 744 745 746 747 748 749

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

       return TRUE;
}

750 751


752 753 754 755 756 757 758 759 760
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;
}