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 ¶ms, 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 ¶ms,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