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