/******************************************************************************* * 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 . * --------------------------------------------------------------------------- * 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 #include #include // 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::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::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& 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 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& 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(); } }