summaryrefslogtreecommitdiff
path: root/ext/gd/libgd/gd_png.c
blob: 8d5effb5a2fff42dce56f5e432a9a7382c2b28d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
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
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
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
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "gd.h"

/* JCE: Arrange HAVE_LIBPNG so that it can be set in gd.h */
#ifdef HAVE_LIBPNG

#include "png.h"		/* includes zlib.h and setjmp.h */
#include "gdhelpers.h"

#define TRUE 1
#define FALSE 0

/*---------------------------------------------------------------------------

    gd_png.c                 Copyright 1999 Greg Roelofs and Thomas Boutell

    The routines in this file, gdImagePng*() and gdImageCreateFromPng*(),
    are drop-in replacements for gdImageGif*() and gdImageCreateFromGif*(),
    except that these functions are noisier in the case of errors (comment
    out all fprintf() statements to disable that).

    GD 2.0 supports RGBA truecolor and will read and write truecolor PNGs.
    GD 2.0 supports 8 bits of color resolution per channel and 
    7 bits of alpha channel resolution. Images with more than 8 bits
    per channel are reduced to 8 bits. Images with an alpha channel are
    only able to resolve down to '1/128th opaque' instead of '1/256th',
    and this conversion is also automatic. I very much doubt you can see it.
    Both tRNS and true alpha are supported.

    Gamma is ignored, and there is no support for text annotations.

    Last updated:  9 February 2001

  ---------------------------------------------------------------------------*/

#ifndef PNG_SETJMP_NOT_SUPPORTED
typedef struct _jmpbuf_wrapper
  {
    jmp_buf jmpbuf;
  }
jmpbuf_wrapper;

static jmpbuf_wrapper gdPngJmpbufStruct;

static void
gdPngErrorHandler (png_structp png_ptr, png_const_charp msg)
{
  jmpbuf_wrapper *jmpbuf_ptr;

  /* This function, aside from the extra step of retrieving the "error
   * pointer" (below) and the fact that it exists within the application
   * rather than within libpng, is essentially identical to libpng's
   * default error handler.  The second point is critical:  since both
   * setjmp() and longjmp() are called from the same code, they are
   * guaranteed to have compatible notions of how big a jmp_buf is,
   * regardless of whether _BSD_SOURCE or anything else has (or has not)
   * been defined. */

  php_gd_error_ex(E_ERROR, "gd-png:  fatal libpng error: %s\n", msg);

  jmpbuf_ptr = png_get_error_ptr (png_ptr);
  if (jmpbuf_ptr == NULL)
    {				/* we are completely hosed now */
      php_gd_error_ex(E_ERROR, "gd-png:  EXTREMELY fatal error: jmpbuf unrecoverable; terminating.\n");
      exit (99);
    }

  longjmp (jmpbuf_ptr->jmpbuf, 1);
}
#endif

static void
gdPngReadData (png_structp png_ptr,
	       png_bytep data, png_size_t length)
{
  gdGetBuf (data, length, (gdIOCtx *)
	    png_get_io_ptr (png_ptr));
}

static void
gdPngWriteData (png_structp png_ptr,
		png_bytep data, png_size_t length)
{
  gdPutBuf (data, length, (gdIOCtx *)
	    png_get_io_ptr (png_ptr));
}

static void
gdPngFlushData (png_structp png_ptr)
{
}

gdImagePtr
gdImageCreateFromPng (FILE * inFile)
{
  gdImagePtr im;
  gdIOCtx *in = gdNewFileCtx (inFile);
  im = gdImageCreateFromPngCtx (in);
  in->gd_free (in);
  return im;
}


/* This routine is based in part on the Chapter 13 demo code in "PNG: The
 *  Definitive Guide" (http://www.cdrom.com/pub/png/pngbook.html).
 */
gdImagePtr
gdImageCreateFromPngCtx (gdIOCtx * infile)
{
  png_byte sig[8];
  png_structp png_ptr;
  png_infop info_ptr;
  png_uint_32 width, height, rowbytes, w, h;
  int bit_depth, color_type, interlace_type;
  int num_palette, num_trans;
  png_colorp palette;
  png_color_16p trans_gray_rgb;
  png_color_16p trans_color_rgb;
  png_bytep trans;
  png_bytep image_data = NULL;
  png_bytepp row_pointers = NULL;
  gdImagePtr im = NULL;
  int i, j, *open = NULL;
  volatile int transparent = -1;
  volatile int palette_allocated = FALSE;

  /* Make sure the signature can't match by dumb luck -- TBB */
  /* GRR: isn't sizeof(infile) equal to the size of the pointer? */
  memset (infile, 0, sizeof (infile));

  /* first do a quick check that the file really is a PNG image; could
   * have used slightly more general png_sig_cmp() function instead */
  gdGetBuf (sig, 8, infile);
  if (!png_check_sig (sig, 8))
    return NULL;		/* bad signature */

#ifndef PNG_SETJMP_NOT_SUPPORTED
  png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, &gdPngJmpbufStruct,
				    gdPngErrorHandler, NULL);
#else
  png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
  if (png_ptr == NULL)
    {
      php_gd_error("gd-png error: cannot allocate libpng main struct\n");
      return NULL;
    }

  info_ptr = png_create_info_struct (png_ptr);
  if (info_ptr == NULL)
    {
      php_gd_error("gd-png error: cannot allocate libpng info struct\n");
      png_destroy_read_struct (&png_ptr, NULL, NULL);
      return NULL;
    }

  /* we could create a second info struct here (end_info), but it's only
   * useful if we want to keep pre- and post-IDAT chunk info separated
   * (mainly for PNG-aware image editors and converters) */

  /* setjmp() must be called in every non-callback function that calls a
   * PNG-reading libpng function */
#ifndef PNG_SETJMP_NOT_SUPPORTED
  if (setjmp (gdPngJmpbufStruct.jmpbuf))
    {
      php_gd_error("gd-png error: setjmp returns error condition\n");
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
      return NULL;
    }
#endif

  png_set_sig_bytes (png_ptr, 8);	/* we already read the 8 signature bytes */

  png_set_read_fn (png_ptr, (void *) infile, gdPngReadData);
  png_read_info (png_ptr, info_ptr);	/* read all PNG info up to image data */

  png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
		&interlace_type, NULL, NULL);
  if ((color_type == PNG_COLOR_TYPE_RGB) ||
      (color_type == PNG_COLOR_TYPE_RGB_ALPHA))
    {
      im = gdImageCreateTrueColor ((int) width, (int) height);
    }
  else
    {
      im = gdImageCreate ((int) width, (int) height);
    }
  if (im == NULL)
    {
      php_gd_error("gd-png error: cannot allocate gdImage struct\n");
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
      gdFree (image_data);
      gdFree (row_pointers);
      return NULL;
    }

  if (bit_depth == 16)
    png_set_strip_16 (png_ptr);
  else if (bit_depth < 8)
    png_set_packing (png_ptr);	/* expand to 1 byte per pixel */
  switch (color_type)
    {
    case PNG_COLOR_TYPE_PALETTE:
      png_get_PLTE (png_ptr, info_ptr, &palette, &num_palette);
#ifdef DEBUG
      php_gd_error("gd-png color_type is palette, colors: %d\n",
	       num_palette);
#endif /* DEBUG */
      if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
	{
	  /* gd 2.0: we support this rather thoroughly now. Grab the
	     first fully transparent entry, if any, as the value of 
	     the simple-transparency index, mostly for backwards
	     binary compatibility. The alpha channel is where it's
	     really at these days. */
	  int firstZero = 1;
	  png_get_tRNS (png_ptr, info_ptr, &trans, &num_trans, NULL);
	  for (i = 0; i < num_trans; ++i)
	    {
	      im->alpha[i] = gdAlphaMax - (trans[i] >> 1);
	      if ((trans[i] == 0) && (firstZero))
		{
		  transparent = i;
		  firstZero = 0;
		}
	    }
	}
      break;

    case PNG_COLOR_TYPE_GRAY:
    case PNG_COLOR_TYPE_GRAY_ALPHA:
      /* create a fake palette and check for single-shade transparency */
      if ((palette = (png_colorp) gdMalloc (256 * sizeof (png_color))) == NULL)
	{
	  php_gd_error("gd-png error: cannot allocate gray palette\n");
	  png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
	  return NULL;
	}
      palette_allocated = TRUE;
      if (bit_depth < 8)
	{
	  num_palette = 1 << bit_depth;
	  for (i = 0; i < 256; ++i)
	    {
	      j = (255 * i) / (num_palette - 1);
	      palette[i].red = palette[i].green = palette[i].blue = j;
	    }
	}
      else
	{
	  num_palette = 256;
	  for (i = 0; i < 256; ++i)
	    {
	      palette[i].red = palette[i].green = palette[i].blue = i;
	    }
	}
      if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
	{
	  png_get_tRNS (png_ptr, info_ptr, NULL, NULL, &trans_gray_rgb);
	  if (bit_depth == 16)	/* png_set_strip_16() not yet in effect */
	    transparent = trans_gray_rgb->gray >> 8;
	  else
	    transparent = trans_gray_rgb->gray;
	  /* Note slight error in 16-bit case:  up to 256 16-bit shades
	   * may get mapped to a single 8-bit shade, and only one of them
	   * is supposed to be transparent.  IOW, both opaque pixels and
	   * transparent pixels will be mapped into the transparent entry.
	   * There is no particularly good way around this in the case
	   * that all 256 8-bit shades are used, but one could write some
	   * custom 16-bit code to handle the case where there are gdFree
	   * palette entries.  This error will be extremely rare in
	   * general, though.  (Quite possibly there is only one such
	   * image in existence.) */
	}
      break;

    case PNG_COLOR_TYPE_RGB:
    case PNG_COLOR_TYPE_RGB_ALPHA:
      /* gd 2.0: we now support truecolor. See the comment above
         for a rare situation in which the transparent pixel may not
         work properly with 16-bit channels. */
      if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
	{
	  png_get_tRNS (png_ptr, info_ptr, NULL, NULL, &trans_color_rgb);
	  if (bit_depth == 16)	/* png_set_strip_16() not yet in effect */
	    transparent = gdTrueColor (trans_color_rgb->red >> 8,
				       trans_color_rgb->green >> 8,
				       trans_color_rgb->blue >> 8);
	  else
	    transparent = gdTrueColor (trans_color_rgb->red,
				       trans_color_rgb->green,
				       trans_color_rgb->blue);
	}
      break;
    }

  png_read_update_info (png_ptr, info_ptr);

  /* allocate space for the PNG image data */
  rowbytes = png_get_rowbytes (png_ptr, info_ptr);
  if ((image_data = (png_bytep) gdMalloc (rowbytes * height)) == NULL)
    {
      php_gd_error("gd-png error: cannot allocate image data\n");
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
      return NULL;
    }
  if ((row_pointers = (png_bytepp) gdMalloc (height * sizeof (png_bytep))) == NULL)
    {
      php_gd_error("gd-png error: cannot allocate row pointers\n");
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
      gdFree (image_data);
      return NULL;
    }

  /* set the individual row_pointers to point at the correct offsets */
  for (h = 0; h < height; ++h)
    {
      row_pointers[h] = image_data + h * rowbytes;
    }

  png_read_image (png_ptr, row_pointers);	/* read whole image... */
  png_read_end (png_ptr, NULL);	/* ...done! */

  if (!im->trueColor)
    {
      im->colorsTotal = num_palette;
      im->transparent = transparent;
      /* load the palette and mark all entries "open" (unused) for now */
      open = im->open;
      for (i = 0; i < num_palette; ++i)
	{
	  im->red[i] = palette[i].red;
	  im->green[i] = palette[i].green;
	  im->blue[i] = palette[i].blue;
	  open[i] = 1;
	}
      for (i = num_palette; i < gdMaxColors; ++i)
	{
	  open[i] = 1;
	}
    }
  im->interlace = (interlace_type == PNG_INTERLACE_ADAM7);

  /* can't nuke structs until done with palette */
  png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
  switch (color_type)
    {
    case PNG_COLOR_TYPE_RGB:
      for (h = 0; h < height; h++)
	{
	  int boffset = 0;
	  for (w = 0; w < width; w++)
	    {
	      register png_byte r = row_pointers[h][boffset++];
	      register png_byte g = row_pointers[h][boffset++];
	      register png_byte b = row_pointers[h][boffset++];
	      im->tpixels[h][w] = gdTrueColor (r, g, b);
	    }
	}
      break;
    case PNG_COLOR_TYPE_RGB_ALPHA:
      for (h = 0; h < height; h++)
	{
	  int boffset = 0;
	  for (w = 0; w < width; w++)
	    {
	      register png_byte r = row_pointers[h][boffset++];
	      register png_byte g = row_pointers[h][boffset++];
	      register png_byte b = row_pointers[h][boffset++];
	      /* gd has only 7 bits of alpha channel resolution, and
	         127 is transparent, 0 opaque. A moment of convenience, 
	         a lifetime of compatibility. */
	      register png_byte a = gdAlphaMax -
	      (row_pointers[h][boffset++] >> 1);
	      im->tpixels[h][w] = gdTrueColorAlpha (r, g, b, a);
	    }
	}
      break;
    default:
      /* Palette image, or something coerced to be one */
      for (h = 0; h < height; ++h)
	{
	  for (w = 0; w < width; ++w)
	    {
	      register png_byte idx = row_pointers[h][w];
	      im->pixels[h][w] = idx;
	      open[idx] = 0;
	    }
	}
    }
#ifdef DEBUG
  if (!im->trueColor)
    {
      for (i = num_palette; i < gdMaxColors; ++i)
	{
	  if (!open[i])
	    {
	      php_gd_error("gd-png warning: image data references out-of-range"
		       " color index (%d)\n", i);
	    }
	}
    }
#endif

  if (palette_allocated)
    gdFree (palette);
  gdFree (image_data);
  gdFree (row_pointers);

  return im;
}


void
gdImagePng (gdImagePtr im, FILE * outFile)
{
  gdIOCtx *out = gdNewFileCtx (outFile);
  gdImagePngCtx (im, out);
  out->gd_free (out);
}

void *
gdImagePngPtr (gdImagePtr im, int *size)
{
  void *rv;
  gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
  gdImagePngCtx (im, out);
  rv = gdDPExtractData (out, size);
  out->gd_free (out);
  return rv;
}

/* This routine is based in part on code from Dale Lutz (Safe Software Inc.)
 *  and in part on demo code from Chapter 15 of "PNG: The Definitive Guide"
 *  (http://www.cdrom.com/pub/png/pngbook.html).
 */
void
gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile)
{
  int i, j, bit_depth = 0, interlace_type;
  int width = im->sx;
  int height = im->sy;
  int colors = im->colorsTotal;
  int *open = im->open;
  int mapping[gdMaxColors];	/* mapping[gd_index] == png_index */
  png_byte trans_values[256];
  png_color_16 trans_rgb_value;
  png_color palette[gdMaxColors];
  png_structp png_ptr;
  png_infop info_ptr;
  volatile int transparent = im->transparent;
  volatile int remap = FALSE;


#ifndef PNG_SETJMP_NOT_SUPPORTED
  png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
			       &gdPngJmpbufStruct, gdPngErrorHandler, NULL);
#else
  png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
  if (png_ptr == NULL)
    {
      php_gd_error("gd-png error: cannot allocate libpng main struct\n");
      return;
    }

  info_ptr = png_create_info_struct (png_ptr);
  if (info_ptr == NULL)
    {
      php_gd_error("gd-png error: cannot allocate libpng info struct\n");
      png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
      return;
    }

#ifndef PNG_SETJMP_NOT_SUPPORTED
  if (setjmp (gdPngJmpbufStruct.jmpbuf))
    {
      php_gd_error("gd-png error: setjmp returns error condition\n");
      png_destroy_write_struct (&png_ptr, &info_ptr);
      return;
    }
#endif

  png_set_write_fn (png_ptr, (void *) outfile, gdPngWriteData, gdPngFlushData);

  /* This is best for palette images, and libpng defaults to it for
     palette images anyway, so we don't need to do it explicitly.
     What to ideally do for truecolor images depends, alas, on the image.
     gd is intentionally imperfect and doesn't spend a lot of time
     fussing with such things. */
/*  png_set_filter(png_ptr, 0, PNG_FILTER_NONE);  */

  /* may want to force maximum compression, but time penalty is large */
/*  png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);  */

  /* can set this to a smaller value without compromising compression if all
   * image data is 16K or less; will save some decoder memory [min == 8] */
/*  png_set_compression_window_bits(png_ptr, 15);  */

  if (!im->trueColor)
    {
      if (transparent >= im->colorsTotal ||
	  (transparent >= 0 && open[transparent]))
	transparent = -1;
    }
  if (!im->trueColor)
    {
      for (i = 0; i < gdMaxColors; ++i)
	mapping[i] = -1;
    }
  if (!im->trueColor)
    {
      /* count actual number of colors used (colorsTotal == high-water mark) */
      colors = 0;
      for (i = 0; i < im->colorsTotal; ++i)
	{
	  if (!open[i])
	    {
	      mapping[i] = colors;
	      ++colors;
	    }
	}
      if (colors < im->colorsTotal)
	{
	  remap = TRUE;
	}
      if (colors <= 2)
	bit_depth = 1;
      else if (colors <= 4)
	bit_depth = 2;
      else if (colors <= 16)
	bit_depth = 4;
      else
	bit_depth = 8;
    }
  interlace_type = im->interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;

  if (im->trueColor)
    {
      if (im->saveAlphaFlag)
	{
	  png_set_IHDR (png_ptr, info_ptr, width, height, 8,
			PNG_COLOR_TYPE_RGB_ALPHA, interlace_type,
		     PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	}
      else
	{
	  png_set_IHDR (png_ptr, info_ptr, width, height, 8,
			PNG_COLOR_TYPE_RGB, interlace_type,
		     PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	}
    }
  else
    {
      png_set_IHDR (png_ptr, info_ptr, width, height, bit_depth,
		    PNG_COLOR_TYPE_PALETTE, interlace_type,
		    PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    }
  if (im->trueColor && (!im->saveAlphaFlag) && (transparent >= 0))
    {
      trans_rgb_value.red = gdTrueColorGetRed (im->trueColor);
      trans_rgb_value.green = gdTrueColorGetGreen (im->trueColor);
      trans_rgb_value.blue = gdTrueColorGetBlue (im->trueColor);
      png_set_tRNS (png_ptr, info_ptr, 0, 0, &trans_rgb_value);
    }
  if (!im->trueColor)
    {
      /* Oy veh. Remap the PNG palette to put the 
         entries with interesting alpha channel
         values first. This minimizes the size
         of the tRNS chunk and thus the size
         of the PNG file as a whole. */
      int tc = 0;
      int i;
      int j;
      int k;
      for (i = 0; (i < im->colorsTotal); i++)
	{
	  if ((!im->open[i]) &&
	      (im->alpha[i] != gdAlphaOpaque))
	    {
	      tc++;
	    }
	}
      if (tc)
	{
#if 0
	  for (i = 0; (i < im->colorsTotal); i++)
	    {
	      trans_values[i] = 255 -
		((im->alpha[i] << 1) +
		 (im->alpha[i] >> 6));
	    }
	  png_set_tRNS (png_ptr, info_ptr, trans_values, 256, NULL);
#endif
	  if (!remap)
	    {
	      remap = TRUE;
	    }
	  /* (Semi-)transparent indexes come up from the bottom
	     of the list of real colors; opaque
	     indexes come down from the top */
	  j = 0;
	  k = colors - 1;
	  for (i = 0; (i < im->colorsTotal); i++)
	    {
	      if (!im->open[i])
		{
		  if (im->alpha[i] != gdAlphaOpaque)
		    {
		       /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ 
		      trans_values[j] = 255 -
			((im->alpha[i] << 1) +
			 (im->alpha[i] >> 6));
		      mapping[i] = j++;
		    }
		  else
		    {
		      mapping[i] = k--;
		    }
		}
	    }
	  png_set_tRNS (png_ptr, info_ptr, trans_values, tc, NULL);
	}
    }

  /* convert palette to libpng layout */
  if (!im->trueColor)
    {
      if (remap)
	for (i = 0; i < im->colorsTotal; ++i)
	  {
	    if (mapping[i] < 0)
	      continue;
	    palette[mapping[i]].red = im->red[i];
	    palette[mapping[i]].green = im->green[i];
	    palette[mapping[i]].blue = im->blue[i];
	  }
      else
	for (i = 0; i < colors; ++i)
	  {
	    palette[i].red = im->red[i];
	    palette[i].green = im->green[i];
	    palette[i].blue = im->blue[i];
	  }
      png_set_PLTE (png_ptr, info_ptr, palette, colors);
    }

  /* write out the PNG header info (everything up to first IDAT) */
  png_write_info (png_ptr, info_ptr);

  /* make sure < 8-bit images are packed into pixels as tightly as possible */
  png_set_packing (png_ptr);

  /* This code allocates a set of row buffers and copies the gd image data
   * into them only in the case that remapping is necessary; in gd 1.3 and
   * later, the im->pixels array is laid out identically to libpng's row
   * pointers and can be passed to png_write_image() function directly.
   * The remapping case could be accomplished with less memory for non-
   * interlaced images, but interlacing causes some serious complications. */
  if (im->trueColor)
    {
      int channels = im->saveAlphaFlag ? 4 : 3;
      /* Our little 7-bit alpha channel trick costs us a bit here. */
      png_bytep *row_pointers;
      row_pointers = gdMalloc (sizeof (png_bytep) * height);
      for (j = 0; j < height; ++j)
	{
	  int bo = 0;
	  row_pointers[j] = (png_bytep) gdMalloc (width * channels);
	  for (i = 0; i < width; ++i)
	    {
	      unsigned char a;
	      row_pointers[j][bo++] = gdTrueColorGetRed (im->tpixels[j][i]);
	      row_pointers[j][bo++] = gdTrueColorGetGreen (im->tpixels[j][i]);
	      row_pointers[j][bo++] = gdTrueColorGetBlue (im->tpixels[j][i]);
	      if (im->saveAlphaFlag)
		{
		  /* convert the 7-bit alpha channel to an 8-bit alpha channel.
		     We do a little bit-flipping magic, repeating the MSB
		     as the LSB, to ensure that 0 maps to 0 and
		     127 maps to 255. We also have to invert to match
		     PNG's convention in which 255 is opaque. */
		  a = gdTrueColorGetAlpha (im->tpixels[j][i]);
		   /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ 
		  row_pointers[j][bo++] = 255 - ((a << 1) + (a >> 6));
		}
	    }
	}

      png_write_image (png_ptr, row_pointers);
      png_write_end (png_ptr, info_ptr);

      for (j = 0; j < height; ++j)
	gdFree (row_pointers[j]);
      gdFree (row_pointers);
    }
  else
    {
      if (remap)
	{
	  png_bytep *row_pointers;
	  row_pointers = gdMalloc (sizeof (png_bytep) * height);
	  for (j = 0; j < height; ++j)
	    {
	      row_pointers[j] = (png_bytep) gdMalloc (width);
	      for (i = 0; i < width; ++i) 
		row_pointers[j][i] = mapping[im->pixels[j][i]];
	    }

	  png_write_image (png_ptr, row_pointers);
	  png_write_end (png_ptr, info_ptr);

	  for (j = 0; j < height; ++j)
	    gdFree (row_pointers[j]);
	  gdFree (row_pointers);
	}
      else
	{
	  png_write_image (png_ptr, im->pixels);
	  png_write_end (png_ptr, info_ptr);
	}
    }
  /* 1.6.3: maybe we should give that memory BACK! TBB */
  png_destroy_write_struct (&png_ptr, &info_ptr);
}


#endif /* HAVE_LIBPNG */