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