povray/source/frontend/imageprocessing.cpp
2013-11-06 13:07:19 -05:00

352 lines
12 KiB
C++

/*******************************************************************************
* imageprocessing.cpp
*
* ---------------------------------------------------------------------------
* 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/frontend/imageprocessing.cpp $
* $Revision: #1 $
* $Change: 6069 $
* $DateTime: 2013/11/06 11:59:40 $
* $Author: chrisc $
*******************************************************************************/
#include <string>
#include <cctype>
#include <boost/scoped_ptr.hpp>
// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/types.h"
#include "base/image/encoding.h"
#include "frontend/imageprocessing.h"
// this must be the last file included
#include "base/povdebug.h"
// TODO: update ImageProcessing with the means of accepting and caching
// blocks of pixels as opposed to individual ones, with a back-end that
// can serialize completed rows to the final image output file.
namespace pov_frontend
{
using namespace pov;
enum
{
X = 0,
Y = 1,
Z = 2
};
ImageProcessing::ImageProcessing(unsigned int width, unsigned int height)
{
image = shared_ptr<Image>(Image::Create(width, height, Image::RGBFT_Float));
toStderr = toStdout = false;
// TODO FIXME - find a better place for this
image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
}
ImageProcessing::ImageProcessing(POVMS_Object& ropts)
{
unsigned int width(ropts.TryGetInt(kPOVAttrib_Width, 160));
unsigned int height(ropts.TryGetInt(kPOVAttrib_Height, 120));
unsigned int blockSize(ropts.TryGetInt(kPOVAttrib_RenderBlockSize, 32));
unsigned int maxBufferMem(ropts.TryGetInt(kPOVAttrib_MaxImageBufferMem, 128)); // number is megabytes
image = shared_ptr<Image>(Image::Create(width, height, Image::RGBFT_Float, maxBufferMem, blockSize * blockSize));
toStdout = OutputIsStdout(ropts);
toStderr = OutputIsStderr(ropts);
// TODO FIXME - find a better place for this
image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
}
ImageProcessing::ImageProcessing(shared_ptr<Image>& img)
{
image = img;
toStderr = toStdout = false;
// TODO FIXME - find a better place for this
image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
}
ImageProcessing::~ImageProcessing()
{
}
UCS2String ImageProcessing::WriteImage(POVMS_Object& ropts, POVMSInt frame, int digits)
{
if(ropts.TryGetBool(kPOVAttrib_OutputToFile, true) == true)
{
Image::WriteOptions wopts;
Image::ImageFileType imagetype = Image::SYS;
unsigned int filetype = POV_File_Image_System;
wopts.bpcc = clip(ropts.TryGetInt(kPOVAttrib_BitsPerColor, 8), 5, 16);
wopts.alphachannel = ropts.TryGetBool(kPOVAttrib_OutputAlpha, false);
wopts.compress = clip(ropts.TryGetInt(kPOVAttrib_Compression, 0), 0, 255);
wopts.grayscale = ropts.TryGetBool(kPOVAttrib_GrayscaleOutput, false);
switch(ropts.TryGetInt(kPOVAttrib_OutputFileType, DEFAULT_OUTPUT_FORMAT))
{
case kPOVList_FileType_Targa:
imagetype = Image::TGA;
filetype = POV_File_Image_Targa;
wopts.compress = 0;
break;
case kPOVList_FileType_CompressedTarga:
imagetype = Image::TGA;
filetype = POV_File_Image_Targa;
wopts.compress = 1;
break;
case kPOVList_FileType_PNG:
imagetype = Image::PNG;
filetype = POV_File_Image_PNG;
break;
case kPOVList_FileType_JPEG:
imagetype = Image::JPEG;
filetype = POV_File_Image_JPEG;
wopts.compress = clip(int(wopts.compress), 0, 100);
break;
case kPOVList_FileType_PPM:
imagetype = Image::PPM;
filetype = POV_File_Image_PPM;
break;
case kPOVList_FileType_BMP:
imagetype = Image::BMP;
filetype = POV_File_Image_BMP;
break;
case kPOVList_FileType_OpenEXR:
imagetype = Image::EXR;
filetype = POV_File_Image_EXR;
break;
case kPOVList_FileType_RadianceHDR:
imagetype = Image::HDR;
filetype = POV_File_Image_HDR;
break;
case kPOVList_FileType_System:
imagetype = Image::SYS;
filetype = POV_File_Image_System;
break;
default:
throw POV_EXCEPTION_STRING("Invalid file type for output");
}
int gammaType = ropts.TryGetInt(kPOVAttrib_FileGammaType, DEFAULT_FILE_GAMMA_TYPE);
float gamma = ropts.TryGetFloat(kPOVAttrib_FileGamma, DEFAULT_FILE_GAMMA);
wopts.encodingGamma = GetGammaCurve(gammaType, gamma);
// NB: RenderFrontend<...>::CreateView should have dealt with kPOVAttrib_LegacyGammaMode already and updated kPOVAttrib_WorkingGammaType and kPOVAttrib_WorkingGamma to fit.
gammaType = ropts.TryGetInt(kPOVAttrib_WorkingGammaType, DEFAULT_WORKING_GAMMA_TYPE);
gamma = ropts.TryGetFloat(kPOVAttrib_WorkingGamma, DEFAULT_WORKING_GAMMA);
wopts.workingGamma = GetGammaCurve(gammaType, gamma);
bool dither = ropts.TryGetBool(kPOVAttrib_Dither, false);
int ditherMethod = kPOVList_DitherMethod_None;
if (dither)
ditherMethod = ropts.TryGetInt(kPOVAttrib_DitherMethod, kPOVList_DitherMethod_FloydSteinberg);
wopts.dither = GetDitherHandler(ditherMethod, image->GetWidth());
// in theory this should always return a filename since the frontend code
// sets it via a call to GetOutputFilename() before the render starts.
UCS2String filename = ropts.TryGetUCS2String(kPOVAttrib_OutputFile, "");
if(filename.empty() == true)
filename = GetOutputFilename(ropts, frame, digits);
boost::scoped_ptr<OStream> imagefile(NewOStream(filename.c_str(), filetype, false)); // TODO - check file permissions somehow without macro [ttrf]
if(imagefile == NULL)
throw POV_EXCEPTION_CODE(kCannotOpenFileErr);
Image::Write(imagetype, imagefile.get(), image.get(), wopts);
return filename;
}
else
return UCS2String();
}
shared_ptr<Image>& ImageProcessing::GetImage()
{
return image;
}
void ImageProcessing::RGB2XYZ(const COLC *rgb, COLC *xyz)
{
// assumes D65 white point (slightly rounded sRGB)
xyz[X] = (0.412453 * rgb[Colour::RED]) + (0.357580 * rgb[Colour::GREEN]) + (0.180423 * rgb[Colour::BLUE]);
xyz[Y] = (0.212671 * rgb[Colour::RED]) + (0.715160 * rgb[Colour::GREEN]) + (0.072169 * rgb[Colour::BLUE]);
xyz[Z] = (0.019334 * rgb[Colour::RED]) + (0.119193 * rgb[Colour::GREEN]) + (0.950227 * rgb[Colour::BLUE]);
}
void ImageProcessing::XYZ2RGB(const COLC *xyz, COLC *rgb)
{
// assumes D65 white point (slightly rounded sRGB)
rgb[Colour::RED] = (3.240479 * xyz[X]) + (-1.537150 * xyz[X]) + (-0.498535 * xyz[X]);
rgb[Colour::GREEN] = (-0.969256 * xyz[Y]) + (1.875992 * xyz[Y]) + (0.041556 * xyz[Y]);
rgb[Colour::BLUE] = (0.055648 * xyz[Z]) + (-0.204043 * xyz[Z]) + (1.057311 * xyz[Z]);
}
bool ImageProcessing::OutputIsStdout(POVMS_Object& ropts)
{
UCS2String path(ropts.TryGetUCS2String(kPOVAttrib_OutputFile, ""));
toStdout = path == POVMS_ASCIItoUCS2String("-") || path == POVMS_ASCIItoUCS2String("stdout");
toStderr = path == POVMS_ASCIItoUCS2String("stderr");
return toStdout;
}
bool ImageProcessing::OutputIsStderr(POVMS_Object& ropts)
{
OutputIsStdout(ropts);
return toStderr;
}
UCS2String ImageProcessing::GetOutputFilename(POVMS_Object& ropts, POVMSInt frame, int digits)
{
Path path(ropts.TryGetUCS2String(kPOVAttrib_OutputFile, ""));
UCS2String filename = path.GetFile();
UCS2String ext;
Image::ImageFileType imagetype;
switch(ropts.TryGetInt(kPOVAttrib_OutputFileType, DEFAULT_OUTPUT_FORMAT))
{
case kPOVList_FileType_Targa:
case kPOVList_FileType_CompressedTarga:
ext = ASCIItoUCS2String(".tga");
imagetype = Image::TGA;
break;
case kPOVList_FileType_PNG:
ext = ASCIItoUCS2String(".png");
imagetype = Image::PNG;
break;
case kPOVList_FileType_JPEG:
ext = ASCIItoUCS2String(".jpg");
imagetype = Image::JPEG;
break;
case kPOVList_FileType_PPM:
ext = ASCIItoUCS2String(".ppm"); // TODO FIXME - in case of greyscale output, extension should default to ".pgm"
imagetype = Image::PPM;
break;
case kPOVList_FileType_BMP:
ext = ASCIItoUCS2String(".bmp");
imagetype = Image::BMP;
break;
case kPOVList_FileType_OpenEXR:
ext = ASCIItoUCS2String(".exr");
imagetype = Image::EXR;
break;
case kPOVList_FileType_RadianceHDR:
ext = ASCIItoUCS2String(".hdr");
imagetype = Image::HDR;
break;
#ifdef SYS_TO_STANDARD
case kPOVList_FileType_System:
ext = ASCIItoUCS2String(POV_SYS_FILE_EXTENSION);
imagetype = Image::SYS_TO_STANDARD;
break;
#endif
default:
throw POV_EXCEPTION_STRING("Invalid file type for output");
}
if (OutputIsStdout(ropts) || OutputIsStderr())
{
switch (imagetype)
{
case Image::HDR:
case Image::PNG:
case Image::TGA:
case Image::PPM:
case Image::BMP:
break;
default:
throw POV_EXCEPTION_STRING("Output to STDOUT/STDERR not supported for selected file format");
}
return POVMS_ASCIItoUCS2String(OutputIsStdout() ? "stdout" : "stderr");
}
// we disallow an output filename that consists purely of the default extension
// (e.g. Output_File_Name=".png").
if((filename == ext) || (filename.empty() == true))
{
// get the input file name and merge the existing path if need be.
if (path.Empty() == true)
{
path = ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov");
filename = path.GetFile();
}
else
filename = Path(ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov")).GetFile();
// if the input file name ends with '.' or '.anything', we remove it
UCS2String::size_type pos = filename.find_last_of('.');
if(pos != string::npos)
filename.erase(pos);
}
else if ((path.HasVolume() == false) && (path.Empty() == false))
{
// to get here, path must be a relative path with filename
// if the filename ends with a '.' or with the default extension (case-sensitive),
// we remove it.
UCS2String::size_type pos = filename.find_last_of('.');
if((pos != UCS2String::npos) && ((pos == filename.size() - 1) || (filename.substr(pos) == ext)))
filename.erase(pos);
}
else
{
// if there is no path component already, get it from the input file.
if (path.Empty() == true)
path = ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov");
// if the filename ends with a '.' or with the default extension (case-sensitive),
// we remove it.
UCS2String::size_type pos = filename.find_last_of('.');
if((pos != UCS2String::npos) && ((pos == filename.size() - 1) || (filename.substr(pos) == ext)))
filename.erase(pos);
}
if (digits > 0)
{
for(int i = 0; i < digits; i++)
filename += '0';
for(UCS2String::size_type i = filename.length() - 1; frame > 0; i--, frame /= 10)
filename[i] = '0' + (frame % 10);
}
path.SetFile(filename + ext);
return path();
}
}