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