povray/source/base/image/png.cpp
Campbell Barton b3846f5723 quiet compiler warnings building with gcc and clang.
- define variables and functions as static when not declared by headers.
- remove some unused defines.
- other minor changes to quiet warnings.
2013-11-17 07:34:00 +11:00

1037 lines
32 KiB
C++

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