File: png.cpp

package info (click to toggle)
povray 1%3A3.7.0.8-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 146,780 kB
  • sloc: cpp: 845,005; ansic: 122,118; sh: 34,206; pascal: 6,420; asm: 3,355; ada: 1,681; makefile: 1,387; cs: 879; awk: 590; perl: 245; xml: 95
file content (1037 lines) | stat: -rw-r--r-- 33,298 bytes parent folder | download | duplicates (6)
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
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
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
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
/*******************************************************************************
 * png.cpp
 *
 * This module contains the code to read and write the PNG output file
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/base/image/png.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

/*****************************************************************************
*  This code requires the use of libpng, Group 42's PNG reference library.
*  libpng is  Copyright (c) 1995 Guy Eric Schalnat, Group 42, Inc.
*
*  This code also requires the use of Zlib,
*  Zlib is Copyright (C) 1995 Jean-loup Gailly and Mark Adler
*
*  The latest version of these libraries are available at ftp.uu.net as
*
*  /graphics/png/libpngXX.tar.gz.
*  /archiver/zlib/zlib-XX.tar.gz.
*
*  where XX is the latest version of the library.
*
*****************************************************************************/

// NOTE: this file throws exceptions from 'extern C' functions. be sure to enable
// this in your compiler options (preferably for this file only).

#include <boost/scoped_ptr.hpp>
#include <boost/scoped_array.hpp>

#include <string>

// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/image/image.h"
#include "base/fileinputoutput.h"
#include "base/image/png_pov.h"
#include "base/types.h"
#include "base/povmsgid.h" // for kPOVList_GammaType_SRGB

#ifndef LIBPNG_MISSING

#include <png.h>
#include "metadata.h"

// this must be the last file included
#include "base/povdebug.h"


namespace pov_base
{

namespace Png
{

/*****************************************************************************
* Local preprocessor defines
******************************************************************************/

/* Number of scanlines between output flushes, and hence the maximum number of
 * lines lost for an interrupted render.  Note that making it much smaller
 * than about 10 for a 640x480 image will noticably degrade compression.
 * If a very small buffer is specified, we don't want to flush more than once
 * every 10 lines or so (assuming a 2:1 compression ratio).
 */
const int FLUSH_DIST = 16; // Set to 16 and removed complicated math [trf]
const int NTEXT = 15;      // Maximum number of tEXt comment blocks
const int MAXTEXT = 1024;  // Maximum length of a tEXt message


/*****************************************************************************
* Local typedefs
******************************************************************************/
struct Messages
{
	vector<string> warnings;
	string error;
};

/*****************************************************************************
* Local variables
******************************************************************************/

/*****************************************************************************
* Static functions
******************************************************************************/


extern "C"
{
	// These are replacement error and warning functions for the libpng code
	void png_pov_err(png_structp, png_const_charp);
	void png_pov_warn(png_structp, png_const_charp);
	void png_pov_read_data(png_structp, png_bytep, png_size_t);
	void png_pov_write_data(png_structp, png_bytep, png_size_t);
	void png_pov_flush_data(png_structp);


	/*****************************************************************************
	*
	* FUNCTION      : png_pov_warn
	*
	* ARGUMENTS     : png_struct *png_ptr; char *msg;
	*
	* MODIFIED ARGS :
	*
	* RETURN VALUE  :
	*
	* AUTHOR        : Andreas Dilger
	*
	* DESCRIPTION
	*
	*   Prints an warning message using the POV I/O functions.  This uses the
	*   png io_ptr to determine whether error messages should be printed or
	*   not.
	*
	* CHANGES
	*
	******************************************************************************/

	void png_pov_warn(png_structp png_ptr, png_const_charp msg)
	{
		Messages *m = (Messages *)png_get_error_ptr(png_ptr);

		if (m)
			m->warnings.push_back (string(msg)) ;
	}


	/*****************************************************************************
	*
	* FUNCTION      : png_pov_err
	*
	* ARGUMENTS     : png_struct *png_ptr; char *msg;
	*
	* MODIFIED ARGS :
	*
	* RETURN VALUE  :
	*
	* AUTHOR        : Andreas Dilger
	*
	* DESCRIPTION
	*
	*   If the png io_ptr is true, this prints an error message using the POV
	*   I/O function, then throws an exception.
	*
	* CHANGES
	*
	******************************************************************************/

	void png_pov_err(png_structp png_ptr, png_const_charp msg)
	{
		Messages *m = (Messages *)png_get_error_ptr(png_ptr);

		if (m)
			m->error = string(msg) ;
		throw POV_EXCEPTION(kFileDataErr, msg);
	}


	/*****************************************************************************
	*
	* FUNCTION      : png_pov_read_data
	*
	* ARGUMENTS     : png_structp png_ptr; png_bytep data; png_uint_32 length;
	*
	* MODIFIED ARGS :
	*
	* RETURN VALUE  :
	*
	* AUTHOR        : Thorsten Froehlich
	*
	* DESCRIPTION
	*
	*   Replacement read function.
	*
	* CHANGES
	*
	******************************************************************************/

	void png_pov_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
	{
		IStream *file = (IStream *)png_get_io_ptr(png_ptr);

		if (!file->read ((char *)data, length))
		{
			Messages *m = (Messages *)png_get_error_ptr(png_ptr);
			if (m)
				m->error = string("Cannot read PNG data");
			throw POV_EXCEPTION(kFileDataErr, "Cannot read PNG data");
		}
	}


	/*****************************************************************************
	*
	* FUNCTION      : png_pov_write_data
	*
	* ARGUMENTS     : png_structp png_ptr; png_bytep data; png_uint_32 length;
	*
	* MODIFIED ARGS :
	*
	* RETURN VALUE  :
	*
	* AUTHOR        : Thorsten Froehlich
	*
	* DESCRIPTION
	*
	*   Replacement write function.
	*
	* CHANGES
	*
	******************************************************************************/

	void png_pov_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
	{
		OStream *file = (OStream *)png_get_io_ptr(png_ptr);

		if (!file->write ((char *)data, length))
		{
			Messages *m = (Messages *)png_get_error_ptr(png_ptr);
			if (m)
				m->error = string("Cannot write PNG data");
			throw POV_EXCEPTION(kFileDataErr, "Cannot write PNG data");
		}
	}


	/*****************************************************************************
	*
	* FUNCTION      : png_pov_flush_data
	*
	* ARGUMENTS     : png_structp png_ptr;
	*
	* MODIFIED ARGS :
	*
	* RETURN VALUE  :
	*
	* AUTHOR        : Thorsten Froehlich
	*
	* DESCRIPTION
	*
	*   Replacement flush function.
	*
	* CHANGES
	*
	******************************************************************************/

	void png_pov_flush_data(png_structp png_ptr)
	{
		OStream *file = (OStream *)png_get_io_ptr(png_ptr);
		file->flush();
	}

	static bool ReadPNGUpdateInfo (png_structp png_ptr, png_infop info_ptr)
	{
		if (setjmp(png_jmpbuf(png_ptr)))
		{
			// If we get here, we had a problem reading the file
			return (false) ;
		}
		png_read_update_info(png_ptr, info_ptr);
		return (true);
	}

	static bool ReadPNGImage (png_structp r_png_ptr, png_bytepp row_ptrs)
	{
		if (setjmp(png_jmpbuf(r_png_ptr)))
		{
			// If we get here, we had a problem reading the file
			return (false) ;
		}
		png_read_image(r_png_ptr, row_ptrs);
		return (true);
	}
}

/*****************************************************************************
*
* FUNCTION      : Read_Png_Image
*
* ARGUMENTS     : IMAGE *Image; char *name;
*
* MODIFIED ARGS : Image
*
* RETURN VALUE  : none
*
* AUTHOR        : Andreas Dilger
*
* DESCRIPTION
*
*   Reads a PNG image into an RGB image buffer
*
* CHANGES
*
*   Updated for POV-Ray 3.X - [TIW]
*   Updated to allow grayscale and alpha together, Oct 1995 - [AED]
*   Fixed palette size for grayscale images with bit-depth <= 8, Nov 1995 [AED]
*   Changed how grayscale images > 8bpp are stored based on use, Nov 1995 [AED]
*
******************************************************************************/

// TODO: use scoped clean-up class in case of exception
Image *Read (IStream *file, const Image::ReadOptions& options)
{
	int                   stride;
	int                   j;
	png_info              *r_info_ptr;
	png_byte              **row_ptrs;
	png_struct            *r_png_ptr;
	unsigned int          width;
	unsigned int          height;
	Messages              messages;
	Image                 *image = NULL;

	if ((r_png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)(&messages), png_pov_err, png_pov_warn)) == NULL)
		throw POV_EXCEPTION(kOutOfMemoryErr, "Cannot allocate PNG data structures");
	if ((r_info_ptr = png_create_info_struct(r_png_ptr)) == NULL)
		throw POV_EXCEPTION(kOutOfMemoryErr, "Cannot allocate PNG data structures");

	// set up the input control
	png_set_read_fn(r_png_ptr, file, png_pov_read_data);

	// read the file information
	png_read_info(r_png_ptr, r_info_ptr);

	// PNG files are pretty clear about Gamma:
	// - If an ICC profile is present, that information should be used (but currently we don't, because that would be a lot of work).
	// - Otherwise, if an sRGB chunk is present, the image uses sRGB color space.
	// - Otherwise, if a gAMA chunk is present, the image uses the specified power-law gamma encoding.
	// - Otherwise, PNG/W3C recommendation is to assume that the image uses sRGB color space nonetheless.
	GammaCurvePtr gamma;
	if (options.gammacorrect)
	{
		if (options.gammaOverride) // user wants to override information, let them have their will
			gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma);
#if defined(PNG_sRGB_SUPPORTED) && defined(PNG_READ_sRGB_SUPPORTED)
		else if (png_get_valid(r_png_ptr, r_info_ptr, PNG_INFO_sRGB)) // file has a sRGB chunk, indicating sRGB color space
		{
			gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());
		}
#endif
#if defined(PNG_gAMA_SUPPORTED) && defined(PNG_READ_gAMA_SUPPORTED)
		else if (png_get_valid(r_png_ptr, r_info_ptr, PNG_INFO_gAMA)) // file has a gAMA chunk, indicating the encoding gamma
		{
			double file_gamma = 1/2.2;
			png_get_gAMA(r_png_ptr, r_info_ptr, &file_gamma);
			gamma = TranscodingGammaCurve::Get(options.workingGamma, PowerLawGammaCurve::GetByEncodingGamma(file_gamma));
		}
#endif
		else
		{
			// no gamma info; PNG/W3C recommends to expect sRGB
			if (options.defaultGamma)
				gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma);
			else
				gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());
		}
	}

	// PNG is specified to use non-premultiplied alpha, so that's the preferred mode to use for the image container unless the user overrides
	// (e.g. to handle a non-compliant file).
	bool premul = false;
	if (options.premultiplyOverride)
		premul = options.premultiply;

	width = png_get_image_width(r_png_ptr, r_info_ptr);
	height = png_get_image_height(r_png_ptr, r_info_ptr);

	stride = 1;

	png_byte color_type = png_get_color_type(r_png_ptr, r_info_ptr);
	png_byte bit_depth = png_get_bit_depth(r_png_ptr, r_info_ptr);

	if (color_type == PNG_COLOR_TYPE_PALETTE)
	{
		// color palette image
		vector<Image::RGBAMapEntry> colormap ;
		Image::RGBAMapEntry entry;
		png_colorp palette;
		int cmap_len;
		png_get_PLTE(r_png_ptr, r_info_ptr, &palette, &cmap_len);
		bool has_alpha = png_get_valid(r_png_ptr, r_info_ptr, PNG_INFO_tRNS);
		png_bytep trans;
		int num_trans;
		png_get_tRNS(r_png_ptr, r_info_ptr, &trans, &num_trans, NULL);

		for(int index = 0; index < cmap_len; index++)
		{
			entry.red   = IntDecode(gamma, palette[index].red,   255);
			entry.green = IntDecode(gamma, palette[index].green, 255);
			entry.blue  = IntDecode(gamma, palette[index].blue,  255);
			entry.alpha = 1.0f ;
			if (has_alpha)
				if (index < num_trans)
					entry.alpha = IntDecode(trans[index], 255);
			colormap.push_back (entry);
		}

		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
			imagetype = Image::Colour_Map;
		image = Image::Create (width, height, imagetype, colormap) ;
		image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha
		gamma.reset(); // gamma has been taken care of by transforming the color table.

		// tell pnglib to expand data to 1 pixel/byte
		png_set_packing(r_png_ptr);
	}
	else if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth <= 8))
	{
		// grayscale image
		// (To keep the actual data reading code simple, we're pretending this to be a color palette image.)
		vector<Image::RGBAMapEntry> colormap ;
		Image::RGBAMapEntry entry;
		int cmap_len = 1 << bit_depth;
		bool has_alpha = png_get_valid(r_png_ptr, r_info_ptr, PNG_INFO_tRNS);
		png_bytep trans;
		int num_trans;
		png_get_tRNS(r_png_ptr, r_info_ptr, &trans, &num_trans, NULL);

		for(int index = 0; index < cmap_len; index++)
		{
			entry.red = entry.green = entry.blue = IntDecode(gamma, index, cmap_len - 1);
			entry.alpha = 1.0f ;
			if (has_alpha)
				if (index < num_trans)
					entry.alpha = IntDecode(trans[index], 255);
			colormap.push_back (entry);
		}

		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
			imagetype = has_alpha ? Image::GrayA_Int8 : Image::Gray_Int8;
		image = Image::Create (width, height, imagetype, colormap) ;
		image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha
		gamma.reset(); // gamma has been taken care of by transforming the color table.

		// tellg pnglib to expand data to 1 pixel/byte
		png_set_packing(r_png_ptr);
	}
	else if ((color_type & PNG_COLOR_MASK_COLOR) == PNG_COLOR_TYPE_GRAY)
	{
		// grayscale image
		stride = bit_depth <= 8 ? 1 : 2 ;
		if (color_type & PNG_COLOR_MASK_ALPHA)
			stride *= 2 ;
	}
	else if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA))
	{
		// color image
		stride = color_type == PNG_COLOR_TYPE_RGB ? 3 : 4 ;
		if (bit_depth > 8)
			stride *= 2 ;
	}
	else
	{
		png_destroy_read_struct(&r_png_ptr, &r_info_ptr, (png_infopp)NULL);
		throw POV_EXCEPTION(kFileDataErr, "Unsupported color type in PNG image") ;
	}

	png_set_interlace_handling(r_png_ptr);

	if (ReadPNGUpdateInfo(r_png_ptr, r_info_ptr) == false)
	{
		png_destroy_read_struct(&r_png_ptr, &r_info_ptr, (png_infopp)NULL);
		if (messages.error.length() > 0)
			throw POV_EXCEPTION(kFileDataErr, messages.error.c_str());
		throw POV_EXCEPTION(kFileDataErr, "Cannot read PNG image.");
	}

	// Allocate row buffers for the input
	row_ptrs = new png_bytep [height];

	for(int row = 0; row < height; row++)
		row_ptrs[row] = new png_byte [png_get_rowbytes(r_png_ptr, r_info_ptr)];

	// Read in the entire image. we call a 'C' function for this as the PNG code
	// calls longjmp in the case of errors, and we'd rather keep that out of here.
	// NOTE: the above comment is probably out of date as of 20070325, but the
	// below code is still valid so there's no need to change it.
	if (ReadPNGImage (r_png_ptr, row_ptrs) == false)
	{
		for(int row = 0; row < height; row++)
			delete[] row_ptrs [row] ;
		delete[] row_ptrs;
		png_destroy_read_struct(&r_png_ptr, &r_info_ptr, (png_infopp)NULL);
		if (messages.error.length() > 0)
			throw POV_EXCEPTION(kFileDataErr, messages.error.c_str());
		throw POV_EXCEPTION(kFileDataErr, "Cannot read PNG image.");
	}

	// We must copy all the values because PNG supplies RGBRGB, but POV-Ray
	// stores RGB components in separate arrays
	if (image == NULL) // image will only be NULL at this point if the file is not a color-mapped type
	{
		bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) != 0 ;

		if ((color_type & PNG_COLOR_MASK_COLOR) == PNG_COLOR_TYPE_GRAY)
		{
			// grayscale image
			// (note that although 8-bit greyscale images should have been handled earlier already, 8-bit greyscale + 8-bit alpha images haven't.)

			Image::ImageDataType imagetype = options.itype;

			if (imagetype == Image::Undefined)
			{
				if (GammaCurve::IsNeutral(gamma))
				{
					if (has_alpha)
						imagetype = bit_depth > 8 ? Image::GrayA_Int16 : Image::GrayA_Int8;
					else
						imagetype = bit_depth > 8 ? Image::Gray_Int16 : Image::Gray_Int8;
				}
				else
				{
					if (has_alpha)
						imagetype = bit_depth > 8 ? Image::GrayA_Gamma16 : Image::GrayA_Gamma8;
					else
						imagetype = bit_depth > 8 ? Image::Gray_Gamma16 : Image::Gray_Gamma8;
				}
			}

			image = Image::Create (width, height, imagetype);
			image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication
			if (bit_depth <= 8)
				image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation.
			else
				image->TryDeferDecoding(gamma, 65535); // try to have gamma adjustment being deferred until image evaluation.

			if (has_alpha)
			{
				// with alpha channel
				if (bit_depth <= 8)
				{
					for(int row = 0; row < height; row++)
						for(int col = 0, j = 0; col < width; col++, j += stride)
							SetEncodedGrayAValue (image, col, row, gamma, 255, (unsigned int) row_ptrs[row][j], row_ptrs[row][j + 1], premul) ;
				}
				else
				{
					for(int row = 0; row < height; row++)
					{
						for(int col = j = 0; col < width; col++, j += stride)
						{
							unsigned int c = ((unsigned short)row_ptrs[row][j] << 8) | row_ptrs[row][j + 1];
							unsigned int a = (((unsigned short)row_ptrs[row][j + 2] << 8) | row_ptrs[row][j + 3]);
							SetEncodedGrayAValue (image, col, row, gamma, 65535, c, a, premul) ;
						}
					}
				}
			}
			else
			{
				// without alpha channel
				if (bit_depth <= 8)
				{
					for(int row = 0; row < height; row++)
						for(int col = j = 0; col < width; col++, j += stride)
							SetEncodedGrayValue (image, col, row, gamma, 255, (unsigned int) row_ptrs[row][j]) ;
				}
				else
				{
					for(int row = 0; row < height; row++)
						for(int col = j = 0; col < width; col ++, j += stride)
							SetEncodedGrayValue (image, col, row, gamma, 65535, ((unsigned int)row_ptrs[row][j] << 8) | row_ptrs[row][j + 1]) ;
				}
			}
		}
		else
		{
			// color image ((color_type & PNG_COLOR_MASK_COLOR) != 0)
			Image::ImageDataType imagetype = options.itype;

			if (imagetype == Image::Undefined)
			{
				if (GammaCurve::IsNeutral(gamma))
				{
					if (has_alpha)
						imagetype = bit_depth > 8 ? Image::RGBA_Int16 : Image::RGBA_Int8;
					else
						imagetype = bit_depth > 8 ? Image::RGB_Int16 : Image::RGB_Int8;
				}
				else
				{
					if (has_alpha)
						imagetype = bit_depth > 8 ? Image::RGBA_Gamma16 : Image::RGBA_Gamma8;
					else
						imagetype = bit_depth > 8 ? Image::RGB_Gamma16 : Image::RGB_Gamma8;
				}
			}

			image = Image::Create (width, height, imagetype);
			image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication
			if (bit_depth <= 8)
				image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation.
			else
				image->TryDeferDecoding(gamma, 65535); // try to have gamma adjustment being deferred until image evaluation.

			if (bit_depth <= 8)
			{
				for(int row = 0; row < height; row++)
				{
					for(int col = j = 0; col < width; col++, j += stride)
					{
						unsigned int r = row_ptrs[row][j];
						unsigned int g = row_ptrs[row][j + 1];
						unsigned int b = row_ptrs[row][j + 2];
						if (has_alpha)
							SetEncodedRGBAValue (image, col, row, gamma, 255, r, g, b, row_ptrs[row][j + 3], premul);
						else
							SetEncodedRGBValue (image, col, row, gamma, 255, r, g, b);
					}
				}
			}
			else
			{
				for(int row = 0; row < height; row++)
				{
					for(int col = j = 0; col < width; col++, j += stride)
					{
						unsigned int r = ((unsigned int)row_ptrs[row][j] << 8) | row_ptrs[row][j + 1];
						unsigned int g = ((unsigned int)row_ptrs[row][j + 2] << 8) | row_ptrs[row][j + 3];
						unsigned int b = ((unsigned int)row_ptrs[row][j + 4] << 8) | row_ptrs[row][j + 5];
						if (has_alpha)
							SetEncodedRGBAValue (image, col, row, gamma, 65535, r, g, b, (((unsigned int)row_ptrs[row][j + 6] << 8) | row_ptrs[row][j + 7]), premul) ;
						else
							SetEncodedRGBValue (image, col, row, gamma, 65535, r, g, b) ;
					}
				}
			}
		}
	}
	else
	{
		// grayscale palette image or color palette image
		for (int row = 0; row < height; row++)
			for (int col = j = 0; col < width; col++, j += stride)
				image->SetIndexedValue (col, row, row_ptrs [row] [j]) ;
	}

	// Clean up the rest of the PNG memory and such
	for(int row = 0; row < height; row++)
		delete[] row_ptrs [row] ;
	delete[] row_ptrs;

	// clean up after the read, and free any memory allocated
	png_destroy_read_struct(&r_png_ptr, &r_info_ptr, (png_infopp)NULL);

	if (messages.error.length() > 0)
		throw POV_EXCEPTION(kFileDataErr, messages.error.c_str());

	options.warnings = messages.warnings ;
	return (image) ;
}

void Write (OStream *file, const Image *image, const Image::WriteOptions& options)
{
	int             himask;
	int             png_stride;
	int             width = image->GetWidth() ;
	int             height = image->GetHeight() ;
	int             j;
	int             bpcc = options.bpcc;
	bool            use_alpha = image->HasTransparency() && options.alphachannel;
	unsigned int    color;
	unsigned int    alpha;
	unsigned int    r;
	unsigned int    g;
	unsigned int    b;
	unsigned int    maxValue;
	unsigned int    hiShift;
	unsigned int    loShift;
	png_info        *info_ptr = NULL;
	png_struct      *png_ptr  = NULL;
	Messages        messages;
	GammaCurvePtr   gamma;
	Metadata        meta;
	DitherHandler*  dither = options.dither.get();

	if (options.encodingGamma)
		gamma = TranscodingGammaCurve::Get(options.workingGamma, options.encodingGamma);
	else
		// PNG/W3C recommends to use sRGB color space
		gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());

	// PNG is specified to use non-premultiplied alpha, so that's the way we do it unless the user overrides
	// (e.g. to handle a non-compliant file).
	bool premul = false;
	if (options.premultiplyOverride)
		premul = options.premultiply;

	if (bpcc == 0)
		bpcc = image->GetMaxIntValue() == 65535 ? 16 : 8 ;
	else if (bpcc < 5)
		bpcc = 5 ;
	else if (bpcc > 16)
		bpcc = 16 ;

	// special case: if options.grayscale is set, we enforce 16bpp irregardless of other settings
	if (options.grayscale)
		bpcc = 16;

	maxValue = (1<<bpcc)-1;

	if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)(&messages), png_pov_err, png_pov_warn)) == NULL)
		throw POV_EXCEPTION(kOutOfMemoryErr, "Cannot allocate PNG data structures");
	if ((info_ptr = png_create_info_struct(png_ptr)) == NULL)
		throw POV_EXCEPTION(kOutOfMemoryErr, "Cannot allocate PNG data structures");

	if (setjmp(png_jmpbuf(png_ptr)))
	{
		// If we get here, we had a problem writing the file
		png_destroy_write_struct(&png_ptr, &info_ptr);
		throw POV_EXCEPTION(kFileDataErr, "Error writing PNG file") ;
	}

	// Set up the compression structure
	png_set_write_fn (png_ptr, file, png_pov_write_data, png_pov_flush_data);

	bool use_color = !(image->IsGrayscale() | options.grayscale);

	// Fill in the relevant image information
	png_set_IHDR(png_ptr, info_ptr,
	             width, height,
	             8 * ((bpcc + 7) / 8), // bit_depth
	             (use_color ? PNG_COLOR_MASK_COLOR : 0) | (use_alpha ? PNG_COLOR_MASK_ALPHA : 0), // color_type
	             PNG_INTERLACE_NONE,
	             PNG_COMPRESSION_TYPE_DEFAULT,
	             PNG_FILTER_TYPE_DEFAULT // interlace_method
	             );

#if defined(PNG_WRITE_sBIT_SUPPORTED)
	png_color_8 sbit = {0,0,0,0,0};
	if (use_color)
		sbit.red = sbit.green = sbit.blue = bpcc;
	else
		sbit.gray = bpcc;
	if (use_alpha)
		sbit.alpha = bpcc;
	png_set_sBIT(png_ptr, info_ptr, &sbit);
#endif // PNG_WRITE_sBIT_SUPPORTED

#if defined(PNG_WRITE_gAMA_SUPPORTED)
	png_set_gAMA(png_ptr, info_ptr, 1.0f / (options.workingGamma->ApproximateDecodingGamma() * gamma->ApproximateDecodingGamma()));
#endif // PNG_WRITE_gAMA_SUPPORTED

#if defined(PNG_WRITE_sRGB_SUPPORTED)
	if (options.encodingGamma && options.encodingGamma->GetTypeId() == kPOVList_GammaType_SRGB)
		// gamma curve is sRGB transfer function; pretend that color space also matches sRGB
		png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); // we have to write *some* value; perceptual will probably be the most common
#endif // PNG_WRITE_gAMA_SUPPORTED

#if defined(PNG_WRITE_oFFs_SUPPORTED)
	png_set_oFFs(png_ptr, info_ptr, options.offset_x, options.offset_y, PNG_OFFSET_PIXEL);
#endif // PNG_WRITE_oFFs_SUPPORTED

#if defined(PNG_WRITE_tIME_SUPPORTED)
	png_time timestamp;
	timestamp.year   = meta.getYear();
	timestamp.month  = meta.getMonth();
	timestamp.day    = meta.getDay();
	timestamp.hour   = meta.getHour();
	timestamp.minute = meta.getMin();
	timestamp.second = meta.getSec();
	png_set_tIME(png_ptr, info_ptr, &timestamp);
#endif // PNG_WRITE_tIME_SUPPORTED

#if defined(PNG_WRITE_TEXT_SUPPORTED)
	/* Line feed is "local" */
	string comment=string("Render Date: ") + meta.getDateTime() + "\n";
	if (!meta.getComment1().empty())
		comment += meta.getComment1() + "\n";
	if (!meta.getComment2().empty())
		comment += meta.getComment2() + "\n";
	if (!meta.getComment3().empty())
		comment += meta.getComment3() + "\n";
	if (!meta.getComment4().empty())
		comment += meta.getComment4() + "\n";
	string soft (meta.getSoftware());
	png_text p_text[2] = {
		{ PNG_TEXT_COMPRESSION_NONE, const_cast<char *>("Software"), const_cast<char *>(soft.c_str()),    soft.length() },
		{ PNG_TEXT_COMPRESSION_NONE, const_cast<char *>("Comment"),  const_cast<char *>(comment.c_str()), comment.length() },
	};
	png_set_text(png_ptr, info_ptr, p_text, 2);
#endif // PNG_WRITE_TEXT_SUPPORTED

	png_write_info(png_ptr, info_ptr);
	png_stride = use_color ? 3 : 1;
	if (use_alpha)
		png_stride++;
	png_stride *= (bpcc + 7) / 8;

	boost::scoped_array<png_byte> row_ptr (new png_byte [width*png_stride]);

	for (int row = 0 ; row < height ; row++)
	{
		/*
		* We must copy all the values because PNG expects RGBRGB bytes, but
		* POV-Ray stores RGB components in separate arrays as floats.  In
		* order to use the full scale values at the lower bit depth, PNG
		* recommends filling the low-order bits with a copy of the high-order
		* bits.  However, studies have shown that filling the low order bits
		* with constant bits significantly improves compression, which we're
		* doing here.  Note that since the true bit depth is stored in the
		* sBIT chunk, the extra packed bits are not important.
		*/

		switch (bpcc)
		{
			case 5:
			case 6:
			case 7:
				// Handle shifting for arbitrary output bit depth
				hiShift = 8 - bpcc;
				loShift = 2*bpcc - 8;
				if (use_color)
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedRGBAValue (image, col, row, gamma, maxValue, r, g, b, alpha, *dither, premul);
							row_ptr[j + 3] = alpha << hiShift;
							row_ptr[j + 3] |= alpha >> loShift;
						}
						else
							GetEncodedRGBValue (image, col, row, gamma, maxValue, r, g, b, *dither);

						row_ptr[j] = r << hiShift;
						row_ptr[j] |= r >> loShift;

						row_ptr[j+1] = g << hiShift;
						row_ptr[j+1] |= g >> loShift;

						row_ptr[j+2] = b << hiShift;
						row_ptr[j+2] |= b >> loShift;
					}
				}
				else
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedGrayAValue (image, col, row, gamma, maxValue, color, alpha, *dither, premul);
							row_ptr[j + 1] = alpha << hiShift;
							row_ptr[j + 1] |= alpha >> loShift;
						}
						else
							color = GetEncodedGrayValue (image, col, row, gamma, maxValue, *dither) ;

						// Use left-bit replication (LBR) for bit depths < 8
						row_ptr[j] = color << hiShift;
						row_ptr[j] |= color >> loShift;
					}
				}
				break;

			case 8:
				if (use_color)
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedRGBAValue (image, col, row, gamma, maxValue, r, g, b, alpha, *dither, premul);
							row_ptr[j + 3] = alpha;
						}
						else
							GetEncodedRGBValue (image, col, row, gamma, maxValue, r, g, b, *dither) ;
						row_ptr[j] = r;
						row_ptr[j + 1] = g;
						row_ptr[j + 2] = b;
					}
				}
				else
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedGrayAValue (image, col, row, gamma, maxValue, color, alpha, *dither, premul);
							row_ptr[j + 1] = alpha;
						}
						else
							color = GetEncodedGrayValue (image, col, row, gamma, maxValue, *dither) ;
						row_ptr[j] = color;
					}
				}
				break;

			case 16:
				if (use_color)
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedRGBAValue (image, col, row, gamma, maxValue, r, g, b, alpha, *dither, premul);
							row_ptr[j+6] = alpha >> 8;
							row_ptr[j+7] = alpha & 0xff;
						}
						else
							GetEncodedRGBValue (image, col, row, gamma, maxValue, r, g, b, *dither) ;

						row_ptr[j] = r >> 8;
						row_ptr[j + 1] = r & 0xFF;

						row_ptr[j + 2] = g >> 8;
						row_ptr[j + 3] = g & 0xFF;

						row_ptr[j + 4] = b >> 8;
						row_ptr[j + 5] = b & 0xFF;
					}
				}
				else
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedGrayAValue (image, col, row, gamma, maxValue, color, alpha, *dither, premul);
							row_ptr[j+2] = alpha >> 8;
							row_ptr[j+3] = alpha & 0xff;
						}
						else
							color = GetEncodedGrayValue (image, col, row, gamma, maxValue, *dither) ;
						row_ptr[j] = color >> 8;
						row_ptr[j+1] = color & 0xff;
					}
				}
				break;

			default:  // bpcc 9 - 15
				// Handle shifting for arbitrary output bit depth
				hiShift = 16 - bpcc;
				loShift = 2*bpcc - 16;
				himask = 0xFF ^ ((1 << hiShift) - 1);
				if (use_color)
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedRGBAValue (image, col, row, gamma, maxValue, r, g, b, alpha, *dither, premul);
							row_ptr[j + 6] = alpha >> (8 - hiShift);
							row_ptr[j + 7] = alpha << hiShift;
							row_ptr[j + 7] |= alpha >> loShift;
						}
						else
							GetEncodedRGBValue (image, col, row, gamma, maxValue, r, g, b, *dither) ;

						row_ptr[j] = r >> (8 - hiShift);
						row_ptr[j + 1] = r << hiShift;
						row_ptr[j + 1] |= r >> loShift;

						row_ptr[j + 2] = g >> (8 - hiShift);
						row_ptr[j + 3] = g << hiShift;
						row_ptr[j + 3] |= g >> loShift;

						row_ptr[j + 4] = b >> (8 - hiShift);
						row_ptr[j + 5] = b << hiShift;
						row_ptr[j + 5] |= b >> loShift;
					}
				}
				else
				{
					for (int col = j = 0; col < width; col++, j += png_stride)
					{
						if (use_alpha)
						{
							GetEncodedGrayAValue (image, col, row, gamma, maxValue, color, alpha, *dither, premul);
							row_ptr[j + 2] = alpha >> (8 - hiShift);
							row_ptr[j + 3] = alpha << hiShift;
							row_ptr[j + 3] |= alpha >> loShift;
						}
						else
							color = GetEncodedGrayValue (image, col, row, gamma, maxValue, *dither) ;

						row_ptr[j] = color >> (8 - hiShift);
						row_ptr[j + 1] = color << hiShift;
						row_ptr[j + 1] |= color >> loShift;
					}
				}
		}

		if (setjmp(png_jmpbuf(png_ptr)))
		{
			if (messages.error.length() > 0)
				throw POV_EXCEPTION(kFileDataErr, messages.error.c_str());

			// If we get here, we had a problem writing the file
			throw POV_EXCEPTION(kFileDataErr, "Cannot write PNG output data");
		}

		// Write out a scanline
		png_write_row (png_ptr, row_ptr.get());
	}

	if (messages.error.length() > 0)
		throw messages.error.c_str();

	png_write_end(png_ptr, info_ptr);
	png_destroy_write_struct(&png_ptr, &info_ptr);
}

} // end of namespace Png

}

#endif  // LIBPNG_MISSING