352 lines
12 KiB
C++
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();
|
|
}
|
|
|
|
}
|