1 /*******************************************************************************
2 * imageprocessing.cpp
3 *
4 * ---------------------------------------------------------------------------
5 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
6 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
7 *
8 * POV-Ray is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as
10 * published by the Free Software Foundation, either version 3 of the
11 * License, or (at your option) any later version.
12 *
13 * POV-Ray is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * ---------------------------------------------------------------------------
21 * POV-Ray is based on the popular DKB raytracer version 2.12.
22 * DKBTrace was originally written by David K. Buck.
23 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
24 * ---------------------------------------------------------------------------
25 * $File: //depot/public/povray/3.x/source/frontend/imageprocessing.cpp $
26 * $Revision: #1 $
27 * $Change: 6069 $
28 * $DateTime: 2013/11/06 11:59:40 $
29 * $Author: chrisc $
30 *******************************************************************************/
31
32 #include <string>
33 #include <cctype>
34
35 #include <boost/scoped_ptr.hpp>
36
37 // configbase.h must always be the first POV file included within base *.cpp files
38 #include "base/configbase.h"
39 #include "base/types.h"
40 #include "base/image/encoding.h"
41
42 #include "frontend/imageprocessing.h"
43
44 // this must be the last file included
45 #include "base/povdebug.h"
46
47 // TODO: update ImageProcessing with the means of accepting and caching
48 // blocks of pixels as opposed to individual ones, with a back-end that
49 // can serialize completed rows to the final image output file.
50
51 namespace pov_frontend
52 {
53
54 using namespace pov;
55
56 enum
57 {
58 X = 0,
59 Y = 1,
60 Z = 2
61 };
62
ImageProcessing(unsigned int width,unsigned int height)63 ImageProcessing::ImageProcessing(unsigned int width, unsigned int height)
64 {
65 image = shared_ptr<Image>(Image::Create(width, height, Image::RGBFT_Float));
66 toStderr = toStdout = false;
67
68 // TODO FIXME - find a better place for this
69 image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
70 }
71
ImageProcessing(POVMS_Object & ropts)72 ImageProcessing::ImageProcessing(POVMS_Object& ropts)
73 {
74 unsigned int width(ropts.TryGetInt(kPOVAttrib_Width, 160));
75 unsigned int height(ropts.TryGetInt(kPOVAttrib_Height, 120));
76 unsigned int blockSize(ropts.TryGetInt(kPOVAttrib_RenderBlockSize, 32));
77 unsigned int maxBufferMem(ropts.TryGetInt(kPOVAttrib_MaxImageBufferMem, 128)); // number is megabytes
78
79 image = shared_ptr<Image>(Image::Create(width, height, Image::RGBFT_Float, maxBufferMem, blockSize * blockSize));
80 toStdout = OutputIsStdout(ropts);
81 toStderr = OutputIsStderr(ropts);
82
83 // TODO FIXME - find a better place for this
84 image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
85 }
86
ImageProcessing(shared_ptr<Image> & img)87 ImageProcessing::ImageProcessing(shared_ptr<Image>& img)
88 {
89 image = img;
90 toStderr = toStdout = false;
91
92 // TODO FIXME - find a better place for this
93 image->SetPremultiplied(true); // POV-Ray uses premultiplied opacity for its math, so that's what will end up in the image container
94 }
95
~ImageProcessing()96 ImageProcessing::~ImageProcessing()
97 {
98 }
99
WriteImage(POVMS_Object & ropts,POVMSInt frame,int digits)100 UCS2String ImageProcessing::WriteImage(POVMS_Object& ropts, POVMSInt frame, int digits)
101 {
102 if(ropts.TryGetBool(kPOVAttrib_OutputToFile, true) == true)
103 {
104 Image::WriteOptions wopts;
105 Image::ImageFileType imagetype = Image::SYS;
106 unsigned int filetype = POV_File_Image_System;
107
108 wopts.bpcc = clip(ropts.TryGetInt(kPOVAttrib_BitsPerColor, 8), 5, 16);
109 wopts.alphachannel = ropts.TryGetBool(kPOVAttrib_OutputAlpha, false);
110 wopts.compress = clip(ropts.TryGetInt(kPOVAttrib_Compression, 0), 0, 255);
111 wopts.grayscale = ropts.TryGetBool(kPOVAttrib_GrayscaleOutput, false);
112
113 switch(ropts.TryGetInt(kPOVAttrib_OutputFileType, DEFAULT_OUTPUT_FORMAT))
114 {
115 case kPOVList_FileType_Targa:
116 imagetype = Image::TGA;
117 filetype = POV_File_Image_Targa;
118 wopts.compress = 0;
119 break;
120 case kPOVList_FileType_CompressedTarga:
121 imagetype = Image::TGA;
122 filetype = POV_File_Image_Targa;
123 wopts.compress = 1;
124 break;
125 case kPOVList_FileType_PNG:
126 imagetype = Image::PNG;
127 filetype = POV_File_Image_PNG;
128 break;
129 case kPOVList_FileType_JPEG:
130 imagetype = Image::JPEG;
131 filetype = POV_File_Image_JPEG;
132 wopts.compress = clip(int(wopts.compress), 0, 100);
133 break;
134 case kPOVList_FileType_PPM:
135 imagetype = Image::PPM;
136 filetype = POV_File_Image_PPM;
137 break;
138 case kPOVList_FileType_BMP:
139 imagetype = Image::BMP;
140 filetype = POV_File_Image_BMP;
141 break;
142 case kPOVList_FileType_OpenEXR:
143 imagetype = Image::EXR;
144 filetype = POV_File_Image_EXR;
145 break;
146 case kPOVList_FileType_RadianceHDR:
147 imagetype = Image::HDR;
148 filetype = POV_File_Image_HDR;
149 break;
150 case kPOVList_FileType_System:
151 imagetype = Image::SYS;
152 filetype = POV_File_Image_System;
153 break;
154 default:
155 throw POV_EXCEPTION_STRING("Invalid file type for output");
156 }
157
158 int gammaType = ropts.TryGetInt(kPOVAttrib_FileGammaType, DEFAULT_FILE_GAMMA_TYPE);
159 float gamma = ropts.TryGetFloat(kPOVAttrib_FileGamma, DEFAULT_FILE_GAMMA);
160 wopts.encodingGamma = GetGammaCurve(gammaType, gamma);
161 // NB: RenderFrontend<...>::CreateView should have dealt with kPOVAttrib_LegacyGammaMode already and updated kPOVAttrib_WorkingGammaType and kPOVAttrib_WorkingGamma to fit.
162 gammaType = ropts.TryGetInt(kPOVAttrib_WorkingGammaType, DEFAULT_WORKING_GAMMA_TYPE);
163 gamma = ropts.TryGetFloat(kPOVAttrib_WorkingGamma, DEFAULT_WORKING_GAMMA);
164 wopts.workingGamma = GetGammaCurve(gammaType, gamma);
165
166 bool dither = ropts.TryGetBool(kPOVAttrib_Dither, false);
167 int ditherMethod = kPOVList_DitherMethod_None;
168 if (dither)
169 ditherMethod = ropts.TryGetInt(kPOVAttrib_DitherMethod, kPOVList_DitherMethod_FloydSteinberg);
170 wopts.dither = GetDitherHandler(ditherMethod, image->GetWidth());
171
172 // in theory this should always return a filename since the frontend code
173 // sets it via a call to GetOutputFilename() before the render starts.
174 UCS2String filename = ropts.TryGetUCS2String(kPOVAttrib_OutputFile, "");
175 if(filename.empty() == true)
176 filename = GetOutputFilename(ropts, frame, digits);
177
178 boost::scoped_ptr<OStream> imagefile(NewOStream(filename.c_str(), filetype, false)); // TODO - check file permissions somehow without macro [ttrf]
179 if(imagefile == NULL)
180 throw POV_EXCEPTION_CODE(kCannotOpenFileErr);
181
182 Image::Write(imagetype, imagefile.get(), image.get(), wopts);
183
184 return filename;
185 }
186 else
187 return UCS2String();
188 }
189
GetImage()190 shared_ptr<Image>& ImageProcessing::GetImage()
191 {
192 return image;
193 }
194
RGB2XYZ(const COLC * rgb,COLC * xyz)195 void ImageProcessing::RGB2XYZ(const COLC *rgb, COLC *xyz)
196 {
197 // assumes D65 white point (slightly rounded sRGB)
198 xyz[X] = (0.412453 * rgb[Colour::RED]) + (0.357580 * rgb[Colour::GREEN]) + (0.180423 * rgb[Colour::BLUE]);
199 xyz[Y] = (0.212671 * rgb[Colour::RED]) + (0.715160 * rgb[Colour::GREEN]) + (0.072169 * rgb[Colour::BLUE]);
200 xyz[Z] = (0.019334 * rgb[Colour::RED]) + (0.119193 * rgb[Colour::GREEN]) + (0.950227 * rgb[Colour::BLUE]);
201 }
202
XYZ2RGB(const COLC * xyz,COLC * rgb)203 void ImageProcessing::XYZ2RGB(const COLC *xyz, COLC *rgb)
204 {
205 // assumes D65 white point (slightly rounded sRGB)
206 rgb[Colour::RED] = (3.240479 * xyz[X]) + (-1.537150 * xyz[X]) + (-0.498535 * xyz[X]);
207 rgb[Colour::GREEN] = (-0.969256 * xyz[Y]) + (1.875992 * xyz[Y]) + (0.041556 * xyz[Y]);
208 rgb[Colour::BLUE] = (0.055648 * xyz[Z]) + (-0.204043 * xyz[Z]) + (1.057311 * xyz[Z]);
209 }
210
OutputIsStdout(POVMS_Object & ropts)211 bool ImageProcessing::OutputIsStdout(POVMS_Object& ropts)
212 {
213 UCS2String path(ropts.TryGetUCS2String(kPOVAttrib_OutputFile, ""));
214
215 toStdout = path == POVMS_ASCIItoUCS2String("-") || path == POVMS_ASCIItoUCS2String("stdout");
216 toStderr = path == POVMS_ASCIItoUCS2String("stderr");
217 return toStdout;
218 }
219
OutputIsStderr(POVMS_Object & ropts)220 bool ImageProcessing::OutputIsStderr(POVMS_Object& ropts)
221 {
222 OutputIsStdout(ropts);
223 return toStderr;
224 }
225
GetOutputFilename(POVMS_Object & ropts,POVMSInt frame,int digits)226 UCS2String ImageProcessing::GetOutputFilename(POVMS_Object& ropts, POVMSInt frame, int digits)
227 {
228 Path path(ropts.TryGetUCS2String(kPOVAttrib_OutputFile, ""));
229 UCS2String filename = path.GetFile();
230 UCS2String ext;
231 Image::ImageFileType imagetype;
232
233 switch(ropts.TryGetInt(kPOVAttrib_OutputFileType, DEFAULT_OUTPUT_FORMAT))
234 {
235 case kPOVList_FileType_Targa:
236 case kPOVList_FileType_CompressedTarga:
237 ext = ASCIItoUCS2String(".tga");
238 imagetype = Image::TGA;
239 break;
240
241 case kPOVList_FileType_PNG:
242 ext = ASCIItoUCS2String(".png");
243 imagetype = Image::PNG;
244 break;
245
246 case kPOVList_FileType_JPEG:
247 ext = ASCIItoUCS2String(".jpg");
248 imagetype = Image::JPEG;
249 break;
250
251 case kPOVList_FileType_PPM:
252 ext = ASCIItoUCS2String(".ppm"); // TODO FIXME - in case of greyscale output, extension should default to ".pgm"
253 imagetype = Image::PPM;
254 break;
255
256 case kPOVList_FileType_BMP:
257 ext = ASCIItoUCS2String(".bmp");
258 imagetype = Image::BMP;
259 break;
260
261 case kPOVList_FileType_OpenEXR:
262 ext = ASCIItoUCS2String(".exr");
263 imagetype = Image::EXR;
264 break;
265
266 case kPOVList_FileType_RadianceHDR:
267 ext = ASCIItoUCS2String(".hdr");
268 imagetype = Image::HDR;
269 break;
270
271 #ifdef SYS_TO_STANDARD
272 case kPOVList_FileType_System:
273 ext = ASCIItoUCS2String(POV_SYS_FILE_EXTENSION);
274 imagetype = Image::SYS_TO_STANDARD;
275 break;
276 #endif
277
278 default:
279 throw POV_EXCEPTION_STRING("Invalid file type for output");
280 }
281
282 if (OutputIsStdout(ropts) || OutputIsStderr())
283 {
284 switch (imagetype)
285 {
286 case Image::HDR:
287 case Image::PNG:
288 case Image::TGA:
289 case Image::PPM:
290 case Image::BMP:
291 break;
292
293 default:
294 throw POV_EXCEPTION_STRING("Output to STDOUT/STDERR not supported for selected file format");
295 }
296 return POVMS_ASCIItoUCS2String(OutputIsStdout() ? "stdout" : "stderr");
297 }
298
299 // we disallow an output filename that consists purely of the default extension
300 // (e.g. Output_File_Name=".png").
301 if((filename == ext) || (filename.empty() == true))
302 {
303 // get the input file name and merge the existing path if need be.
304 if (path.Empty() == true)
305 {
306 path = ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov");
307 filename = path.GetFile();
308 }
309 else
310 filename = Path(ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov")).GetFile();
311
312 // if the input file name ends with '.' or '.anything', we remove it
313 UCS2String::size_type pos = filename.find_last_of('.');
314 if(pos != string::npos)
315 filename.erase(pos);
316 }
317 else if ((path.HasVolume() == false) && (path.Empty() == false))
318 {
319 // to get here, path must be a relative path with filename
320 // if the filename ends with a '.' or with the default extension (case-sensitive),
321 // we remove it.
322 UCS2String::size_type pos = filename.find_last_of('.');
323 if((pos != UCS2String::npos) && ((pos == filename.size() - 1) || (filename.substr(pos) == ext)))
324 filename.erase(pos);
325 }
326 else
327 {
328 // if there is no path component already, get it from the input file.
329 if (path.Empty() == true)
330 path = ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov");
331
332 // if the filename ends with a '.' or with the default extension (case-sensitive),
333 // we remove it.
334 UCS2String::size_type pos = filename.find_last_of('.');
335 if((pos != UCS2String::npos) && ((pos == filename.size() - 1) || (filename.substr(pos) == ext)))
336 filename.erase(pos);
337 }
338
339 if (digits > 0)
340 {
341 for(int i = 0; i < digits; i++)
342 filename += '0';
343 for(UCS2String::size_type i = filename.length() - 1; frame > 0; i--, frame /= 10)
344 filename[i] = '0' + (frame % 10);
345 }
346
347 path.SetFile(filename + ext);
348
349 return path();
350 }
351
352 }
353