1 /****************************************************************************
2  *
3  * 		exrHandler.cc: EXR format handler
4  *      This is part of the yafray package
5  *      Copyright (C) 2010 Rodrigo Placencia Vazquez
6  *
7  *      This library is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU Lesser General Public
9  *      License as published by the Free Software Foundation; either
10  *      version 2.1 of the License, or (at your option) any later version.
11  *
12  *      This library is distributed in the hope that it will be useful,
13  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *      Lesser General Public License for more details.
16  *
17  *      You should have received a copy of the GNU Lesser General Public
18  *      License along with this library; if not, write to the Free Software
19  *      Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  */
22 
23 #include <core_api/imagehandler.h>
24 #include <core_api/logging.h>
25 #include <core_api/session.h>
26 #include <core_api/environment.h>
27 #include <core_api/params.h>
28 #include <utilities/math_utils.h>
29 #include <core_api/file.h>
30 
31 #include <ImfOutputFile.h>
32 #include <ImfChannelList.h>
33 #include <ImfRgbaFile.h>
34 #include <ImfArray.h>
35 #include <ImfVersion.h>
36 #include <IexThrowErrnoExc.h>
37 
38 typedef uint64_t Int64;
39 
40 using namespace Imf;
41 using namespace Imath;
42 
43 __BEGIN_YAFRAY
44 
45 typedef genericScanlineBuffer_t<Rgba> halfRgbaScanlineImage_t;
46 typedef genericScanlineBuffer_t<float> grayScanlineImage_t;
47 
48 //Class C_IStream from "Reading and Writing OpenEXR Image Files with the IlmImf Library" in the OpenEXR sources
49 class C_IStream: public Imf::IStream
50 {
51 public:
C_IStream(FILE * file,const char fileName[])52 	C_IStream (FILE *file, const char fileName[]):
53 	Imf::IStream (fileName), _file (file) {}
54 	virtual bool read (char c[], int n);
55 	virtual Int64 tellg ();
56 	virtual void seekg (Int64 pos);
57 	virtual void clear ();
58 private:
59 	FILE * _file;
60 };
61 
read(char c[],int n)62 bool C_IStream::read (char c[], int n)
63 {
64 	if (n != (int) fread (c, 1, n, _file))
65 	{
66 		// fread() failed, but the return value does not distinguish
67 		// between I/O errors and end of file, so we call ferror() to
68 		// determine what happened.
69 		if (ferror (_file)) Iex::throwErrnoExc();
70 		else throw Iex::InputExc ("Unexpected end of file.");
71 	}
72 	return feof (_file);
73 }
74 
tellg()75 Int64 C_IStream::tellg ()
76 {
77 	return ftell (_file);
78 }
79 
seekg(Int64 pos)80 void C_IStream::seekg (Int64 pos)
81 {
82 	clearerr (_file);
83 	fseek (_file, pos, SEEK_SET);
84 }
85 
clear()86 void C_IStream::clear ()
87 {
88 	clearerr (_file);
89 }
90 
91 
92 class C_OStream: public Imf::OStream
93 {
94 public:
C_OStream(FILE * file,const char fileName[])95 	C_OStream (FILE *file, const char fileName[]):
96 	Imf::OStream (fileName), _file (file) {}
97 	virtual void write (const char c[], int n);
98 	virtual Int64 tellp ();
99 	virtual void seekp (Int64 pos);
100 private:
101 	FILE * _file;
102 };
103 
write(const char c[],int n)104 void C_OStream::write (const char c[], int n)
105 {
106 	if (n != (int) fwrite (c, 1, n, _file))
107 	{
108 		// fwrite() failed, but the return value does not distinguish
109 		// between I/O errors and end of file, so we call ferror() to
110 		// determine what happened.
111 		if (ferror (_file)) Iex::throwErrnoExc();
112 		else throw Iex::InputExc ("Unexpected end of file.");
113 	}
114 }
115 
tellp()116 Int64 C_OStream::tellp ()
117 {
118 	return ftell (_file);
119 }
120 
seekp(Int64 pos)121 void C_OStream::seekp (Int64 pos)
122 {
123 	clearerr (_file);
124 	fseek (_file, pos, SEEK_SET);
125 }
126 
127 
128 
129 class exrHandler_t: public imageHandler_t
130 {
131 public:
132 	exrHandler_t();
133 	~exrHandler_t();
134 	bool loadFromFile(const std::string &name);
135 	bool saveToFile(const std::string &name, int imgIndex = 0);
136     bool saveToFileMultiChannel(const std::string &name, const renderPasses_t *renderPasses);
137 	static imageHandler_t *factory(paraMap_t &params, renderEnvironment_t &render);
isHDR()138 	bool isHDR() { return true; }
139 };
140 
exrHandler_t()141 exrHandler_t::exrHandler_t()
142 {
143 	handlerName = "EXRHandler";
144 }
145 
~exrHandler_t()146 exrHandler_t::~exrHandler_t()
147 {
148 	clearImgBuffers();
149 }
150 
saveToFile(const std::string & name,int imgIndex)151 bool exrHandler_t::saveToFile(const std::string &name, int imgIndex)
152 {
153 	int h = getHeight(imgIndex);
154 	int w = getWidth(imgIndex);
155 
156 	std::string nameWithoutTmp = name;
157 	nameWithoutTmp.erase(nameWithoutTmp.length()-4);
158 	if(session.renderInProgress()) Y_INFO << handlerName << ": Autosaving partial render (" << RoundFloatPrecision(session.currentPassPercent(), 0.01) << "% of pass " << session.currentPass() << " of " << session.totalPasses() << ") RGB" << ( m_hasAlpha ? "A" : "" ) << " file as \"" << nameWithoutTmp << "\"...  " << getDenoiseParams()  << yendl;
159 	else Y_INFO << handlerName << ": Saving RGB" << ( m_hasAlpha ? "A" : "" ) << " file as \"" << nameWithoutTmp << "\"...  " << getDenoiseParams()  << yendl;
160 
161 	int chan_size = sizeof(half);
162 	const int num_colchan = 4;
163 	int totchan_size = num_colchan*chan_size;
164 
165 	Header header(w, h);
166 
167 	header.compression() = ZIP_COMPRESSION;
168 
169 	header.channels().insert("R", Channel(HALF));
170 	header.channels().insert("G", Channel(HALF));
171 	header.channels().insert("B", Channel(HALF));
172 	header.channels().insert("A", Channel(HALF));
173 
174 	FILE *fp = file_t::open(name.c_str(), "wb");
175 	C_OStream ostr (fp, name.c_str());
176 	OutputFile file(ostr, header);
177 
178 	Imf::Array2D<Imf::Rgba> pixels;
179 	pixels.resizeErase(h, w);
180 
181 	for(int i = 0; i < w; ++i)
182 	{
183 		for(int j = 0; j < h; ++j)
184 		{
185 			colorA_t col = imgBuffer.at(imgIndex)->getColor(i, j);
186 			pixels[j][i].r = col.R;
187 			pixels[j][i].g = col.G;
188 			pixels[j][i].b = col.B;
189 			pixels[j][i].a = col.A;
190 		}
191 	}
192 
193 	char* data_ptr = (char *)&pixels[0][0];
194 
195 	FrameBuffer fb;
196 	fb.insert("R", Slice(HALF, data_ptr              , totchan_size, w * totchan_size));
197 	fb.insert("G", Slice(HALF, data_ptr +   chan_size, totchan_size, w * totchan_size));
198 	fb.insert("B", Slice(HALF, data_ptr + 2*chan_size, totchan_size, w * totchan_size));
199 	fb.insert("A", Slice(HALF, data_ptr + 3*chan_size, totchan_size, w * totchan_size));
200 
201 	file.setFrameBuffer(fb);
202 
203 	try
204 	{
205 		file.writePixels(h);
206 		Y_VERBOSE << handlerName << ": Done." << yendl;
207 		return true;
208 	}
209 	catch (const std::exception &exc)
210 	{
211 		Y_ERROR << handlerName << ": " << exc.what() << yendl;
212 		return false;
213 	}
214 
215 	file_t::close(fp);
216 	fp = nullptr;
217 
218 	return true;
219 }
220 
saveToFileMultiChannel(const std::string & name,const renderPasses_t * renderPasses)221 bool exrHandler_t::saveToFileMultiChannel(const std::string &name, const renderPasses_t *renderPasses)
222 {
223 	int h0 = imgBuffer.at(0)->getHeight();
224 	int w0 = imgBuffer.at(0)->getWidth();
225 
226 	bool allImageBuffersSameSize = true;
227 	for(size_t idx = 0; idx < imgBuffer.size(); ++idx)
228 	{
229 		if(imgBuffer.at(idx)->getHeight() != h0) allImageBuffersSameSize = false;
230 		if(imgBuffer.at(idx)->getWidth() != w0) allImageBuffersSameSize = false;
231 	}
232 
233 	if(!allImageBuffersSameSize)
234 	{
235 		Y_ERROR << handlerName << ": Saving Multilayer EXR failed: not all the images in the imageBuffer have the same size. Make sure all images in buffer have the same size or use a non-multilayered EXR format." << yendl;
236 		return false;
237 	}
238 
239     std::string extPassName;
240 
241 	std::string nameWithoutTmp = name;
242 	nameWithoutTmp.erase(nameWithoutTmp.length()-4);
243 
244     if(session.renderInProgress()) Y_INFO << handlerName << ": Autosaving partial render (" << RoundFloatPrecision(session.currentPassPercent(), 0.01) << "% of pass " << session.currentPass() << " of " << session.totalPasses() << ") Multilayer EXR" << " file as \"" << nameWithoutTmp << "\"...  " << getDenoiseParams() << yendl;
245     else Y_INFO << handlerName << ": Saving Multilayer EXR" << " file as \"" << nameWithoutTmp << "\"...  " << getDenoiseParams()  << yendl;
246 
247 	int chan_size = sizeof(half);
248 	const int num_colchan = 4;
249 	int totchan_size = num_colchan*chan_size;
250 
251     Header header(w0, h0);
252     FrameBuffer fb;
253 	header.compression() = ZIP_COMPRESSION;
254 
255 	std::vector<Imf::Array2D<Imf::Rgba> *> pixels;
256 
257     for(size_t idx = 0; idx < imgBuffer.size(); ++idx)
258     {
259 		extPassName = "RenderLayer." + renderPasses->extPassTypeStringFromIndex(idx) + ".";
260 		Y_VERBOSE << "    Writing EXR Layer: " << renderPasses->extPassTypeStringFromIndex(idx) << yendl;
261 
262         const std::string channelR_string = extPassName + "R";
263         const std::string channelG_string = extPassName + "G";
264         const std::string channelB_string = extPassName + "B";
265         const std::string channelA_string = extPassName + "A";
266 
267         const char* channelR = channelR_string.c_str();
268         const char* channelG = channelG_string.c_str();
269         const char* channelB = channelB_string.c_str();
270         const char* channelA = channelA_string.c_str();
271 
272         header.channels().insert(channelR, Channel(HALF));
273         header.channels().insert(channelG, Channel(HALF));
274         header.channels().insert(channelB, Channel(HALF));
275         header.channels().insert(channelA, Channel(HALF));
276 
277 		pixels.push_back(new Imf::Array2D<Imf::Rgba>);
278 		pixels.at(idx)->resizeErase(h0, w0);
279 
280 		for(int i = 0; i < w0; ++i)
281 		{
282 			for(int j = 0; j < h0; ++j)
283 			{
284 				colorA_t col = imgBuffer.at(idx)->getColor(i, j);
285 				(*pixels.at(idx))[j][i].r = col.R;
286 				(*pixels.at(idx))[j][i].g = col.G;
287 				(*pixels.at(idx))[j][i].b = col.B;
288 				(*pixels.at(idx))[j][i].a = col.A;
289 			}
290 		}
291 
292 		char* data_ptr = (char *)&(*pixels.at(idx))[0][0];
293 
294         fb.insert(channelR, Slice(HALF, data_ptr              , totchan_size, w0 * totchan_size));
295         fb.insert(channelG, Slice(HALF, data_ptr +   chan_size, totchan_size, w0 * totchan_size));
296         fb.insert(channelB, Slice(HALF, data_ptr + 2*chan_size, totchan_size, w0 * totchan_size));
297         fb.insert(channelA, Slice(HALF, data_ptr + 3*chan_size, totchan_size, w0 * totchan_size));
298     }
299 
300 	FILE *fp = file_t::open(name.c_str(), "wb");
301 	C_OStream ostr (fp, name.c_str());
302 	OutputFile file(ostr, header);
303 	file.setFrameBuffer(fb);
304 
305 	try
306 	{
307 		file.writePixels(h0);
308 		Y_VERBOSE << handlerName << ": Done." << yendl;
309 		for(size_t idx = 0; idx < pixels.size(); ++idx)
310 		{
311 			delete pixels.at(idx);
312 			pixels.at(idx) = nullptr;
313 		}
314 		pixels.clear();
315 		return true;
316 	}
317 	catch (const std::exception &exc)
318 	{
319 		Y_ERROR << handlerName << ": " << exc.what() << yendl;
320 		for(size_t idx = 0; idx < pixels.size(); ++idx)
321 		{
322 			delete pixels.at(idx);
323 			pixels.at(idx) = nullptr;
324 		}
325 		pixels.clear();
326 		return false;
327 	}
328 
329 	file_t::close(fp);
330 	fp = nullptr;
331 
332 	return true;
333 }
334 
loadFromFile(const std::string & name)335 bool exrHandler_t::loadFromFile(const std::string &name)
336 {
337 	FILE *fp = file_t::open(name.c_str(), "rb");
338 	Y_INFO << handlerName << ": Loading image \"" << name << "\"..." << yendl;
339 
340 	if(!fp)
341 	{
342 		Y_ERROR << handlerName << ": Cannot open file " << name << yendl;
343 		return false;
344 	}
345 	else
346 	{
347 		char bytes[4];
348 		fread(&bytes, 1, 4, fp);
349 		if(!isImfMagic(bytes)) return false;
350 		fseek(fp, 0, SEEK_SET);
351 	}
352 
353 	try
354 	{
355 		C_IStream istr (fp, name.c_str());
356 		RgbaInputFile file (istr);
357 		Box2i dw = file.dataWindow();
358 
359 		m_width  = dw.max.x - dw.min.x + 1;
360 		m_height = dw.max.y - dw.min.y + 1;
361 		m_hasAlpha = true;
362 
363 		clearImgBuffers();
364 
365 		int nChannels = 3;
366 		if(m_grayscale) nChannels = 1;
367 		else if(m_hasAlpha) nChannels = 4;
368 
369 		imgBuffer.push_back(new imageBuffer_t(m_width, m_height, nChannels, getTextureOptimization()));
370 
371 		Imf::Array2D<Imf::Rgba> pixels;
372 		pixels.resizeErase(m_width, m_height);
373 		file.setFrameBuffer(&pixels[0][0] - dw.min.y - dw.min.x * m_height, m_height, 1);
374 		file.readPixels(dw.min.y, dw.max.y);
375 
376 		for(int i = 0; i < m_width; ++i)
377 		{
378 			for(int j = 0; j < m_height; ++j)
379 			{
380 				colorA_t col;
381 				col.R = pixels[i][j].r;
382 				col.G = pixels[i][j].g;
383 				col.B = pixels[i][j].b;
384 				col.A = pixels[i][j].a;
385 				imgBuffer.at(0)->setColor(i, j, col, m_colorSpace, m_gamma);
386 			}
387 		}
388 	}
389 	catch (const std::exception &exc)
390 	{
391 		Y_ERROR << handlerName << ": " << exc.what() << yendl;
392 		return false;
393 	}
394 
395 	file_t::close(fp);
396 	fp = nullptr;
397 
398 	return true;
399 }
400 
factory(paraMap_t & params,renderEnvironment_t & render)401 imageHandler_t *exrHandler_t::factory(paraMap_t &params,renderEnvironment_t &render)
402 {
403 	int pixtype = HALF;
404 	int compression = ZIP_COMPRESSION;
405 	int width = 0;
406 	int height = 0;
407 	bool withAlpha = false;
408 	bool forOutput = true;
409 	bool multiLayer = false;
410 	bool img_grayscale = false;
411 	bool denoiseEnabled = false;
412 	int denoiseHLum = 3;
413 	int denoiseHCol = 3;
414 	float denoiseMix = 0.8f;
415 
416 	params.getParam("pixel_type", pixtype);
417 	params.getParam("compression", compression);
418 	params.getParam("width", width);
419 	params.getParam("height", height);
420 	params.getParam("alpha_channel", withAlpha);
421 	params.getParam("for_output", forOutput);
422 	params.getParam("img_multilayer", multiLayer);
423 	params.getParam("img_grayscale", img_grayscale);
424 /*	//Denoise is not available for HDR/EXR images
425  * 	params.getParam("denoiseEnabled", denoiseEnabled);
426  *	params.getParam("denoiseHLum", denoiseHLum);
427  *	params.getParam("denoiseHCol", denoiseHCol);
428  *	params.getParam("denoiseMix", denoiseMix);
429  */
430 	imageHandler_t *ih = new exrHandler_t();
431 
432 	ih->setTextureOptimization(TEX_OPTIMIZATION_NONE);
433 
434 	if(forOutput)
435 	{
436 		if(yafLog.getUseParamsBadge()) height += yafLog.getBadgeHeight();
437 		ih->initForOutput(width, height, render.getRenderPasses(), denoiseEnabled, denoiseHLum, denoiseHCol, denoiseMix, withAlpha, multiLayer, img_grayscale);
438 	}
439 
440 	return ih;
441 }
442 
443 extern "C"
444 {
445 
registerPlugin(renderEnvironment_t & render)446 	YAFRAYPLUGIN_EXPORT void registerPlugin(renderEnvironment_t &render)
447 	{
448 		render.registerImageHandler("exr", "exr", "EXR [IL&M OpenEXR]", exrHandler_t::factory);
449 	}
450 
451 }
452 __END_YAFRAY
453