- define variables and functions as static when not declared by headers. - remove some unused defines. - other minor changes to quiet warnings.
1037 lines
32 KiB
C++
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, ×tamp);
|
|
#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
|