1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-io <https://github.com/MrKepzie/openfx-io>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-io is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-io is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-io.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX PFM reader plugin.
21  * Reads an image in the Portable Float Map (PFM) format.
22  */
23 
24 #include <cstdio> // fopen, fread...
25 #include <algorithm>
26 
27 #include "GenericReader.h"
28 #include "GenericOCIO.h"
29 #include "ofxsFileOpen.h"
30 #include "ofxsMacros.h"
31 
32 using namespace OFX;
33 using namespace OFX::IO;
34 
35 #ifdef OFX_IO_USING_OCIO
36 namespace OCIO = OCIO_NAMESPACE;
37 #endif
38 
39 using std::string;
40 using std::stringstream;
41 using std::vector;
42 
43 OFXS_NAMESPACE_ANONYMOUS_ENTER
44 
45 #define kPluginName "ReadPFM"
46 #define kPluginGrouping "Image/Readers"
47 #define kPluginDescription "Read PFM (Portable Float Map) files."
48 #define kPluginIdentifier "fr.inria.openfx.ReadPFM"
49 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
50 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
51 #define kPluginEvaluation 92 // better than ReadOIIO
52 
53 #define kSupportsRGBA true
54 #define kSupportsRGB true
55 #define kSupportsXY false
56 #define kSupportsAlpha true
57 #define kSupportsTiles false
58 
59 class ReadPFMPlugin
60     : public GenericReaderPlugin
61 {
62 public:
63 
64     ReadPFMPlugin(OfxImageEffectHandle handle, const vector<string>& extensions);
65 
66     virtual ~ReadPFMPlugin();
67 
68 private:
69 
isVideoStream(const string &)70     virtual bool isVideoStream(const string& /*filename*/) OVERRIDE FINAL { return false; }
71 
72     virtual void decode(const string& filename, OfxTime time, int view, bool isPlayback, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds, PixelComponentEnum pixelComponents, int pixelComponentCount, int rowBytes) OVERRIDE FINAL;
73     virtual bool getFrameBounds(const string& filename, OfxTime time, int view, OfxRectI *bounds, OfxRectI *format, double *par, string *error, int* tile_width, int* tile_height) OVERRIDE FINAL;
74 
75     /**
76      * @brief Called when the input image/video file changed.
77      *
78      * returns true if file exists and parameters successfully guessed, false in case of error.
79      *
80      * This function is only called once: when the filename is first set.
81      *
82      * Besides returning colorspace, premult, components, and componentcount, if it returns true
83      * this function may also set extra format-specific parameters using Param::setValue.
84      * The parameters must not be animated, since their value must remain the same for a whole sequence.
85      *
86      * You shouldn't do any strong processing as this is called on the main thread and
87      * the getRegionOfDefinition() and  decode() should open the file in a separate thread.
88      *
89      * The colorspace may be set if available, else a default colorspace is used.
90      *
91      * You must also return the premultiplication state and pixel components of the image.
92      * When reading an image sequence, this is called only for the first image when the user actually selects the new sequence.
93      **/
94     virtual bool guessParamsFromFilename(const string& filename, string *colorspace, PreMultiplicationEnum *filePremult, PixelComponentEnum *components, int *componentCount) OVERRIDE FINAL;
95 };
96 
97 
98 /**
99    \return \c false for "Little Endian", \c true for "Big Endian".
100  **/
101 static inline bool
endianness()102 endianness()
103 {
104     const int x = 1;
105 
106     return ( (unsigned char *)&x )[0] ? false : true;
107 }
108 
109 //! Invert endianness of a memory buffer.
110 template<typename T>
111 static void
invert_endianness(T * const buffer,const unsigned int size)112 invert_endianness(T *const buffer,
113                   const unsigned int size)
114 {
115     if (size) {
116         switch ( sizeof(T) ) {
117         case 1:
118             break;
119         case 2:
120             for (unsigned short *ptr = (unsigned short *)buffer + size; ptr > (unsigned short *)buffer; ) {
121                 const unsigned short val = *(--ptr);
122                 *ptr = (unsigned short)( (val >> 8) | ( (val << 8) ) );
123             }
124             break;
125         case 4:
126             for (unsigned int *ptr = (unsigned int *)buffer + size; ptr > (unsigned int *)buffer; ) {
127                 const unsigned int val = *(--ptr);
128                 *ptr = (val >> 24) | ( (val >> 8) & 0xff00 ) | ( (val << 8) & 0xff0000 ) | (val << 24);
129             }
130             break;
131         default:
132             for (T *ptr = buffer + size; ptr > buffer; ) {
133                 unsigned char *pb = (unsigned char *)(--ptr), *pe = pb + sizeof(T);
134                 for (int i = 0; i < (int)sizeof(T) / 2; ++i) {
135                     std::swap( *(pb++), *(--pe) );
136                 }
137             }
138         }
139     }
140 }
141 
ReadPFMPlugin(OfxImageEffectHandle handle,const vector<string> & extensions)142 ReadPFMPlugin::ReadPFMPlugin(OfxImageEffectHandle handle,
143                              const vector<string>& extensions)
144     : GenericReaderPlugin(handle, extensions, kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles, false)
145 {
146 }
147 
~ReadPFMPlugin()148 ReadPFMPlugin::~ReadPFMPlugin()
149 {
150 }
151 
152 template <class PIX, int srcC, int dstC>
153 static void
copyLine(PIX * image,int x1,int x2,int C,PIX * dstPix)154 copyLine(PIX *image,
155          int x1,
156          int x2,
157          int C,
158          PIX *dstPix)
159 {
160     assert(srcC == C);
161 
162     const PIX *srcPix = image + x1 * C;
163     dstPix += x1 * dstC;
164 
165     for (int x = x1; x < x2; ++x) {
166         if (srcC == 1) {
167             // alpha/grayscale image
168             for (int c = 0; c < std::min(dstC, 3); ++c) {
169                 dstPix[c] = srcPix[0];
170             }
171         } else {
172             // color image (if dstC == 1, only the red channel is extracted)
173             for (int c = 0; c < std::min(dstC, 3); ++c) {
174                 dstPix[c] = srcPix[c];
175             }
176         }
177         if (dstC == 4) {
178             // Alpha is 0 on RGBA images to allow adding alpha using a Roto node.
179             // Alpha is set to 0 and premult is set to Opaque.
180             // That way, the Roto node can be conveniently used to draw a mask. This shouldn't
181             // disturb anything else in the process, since Opaque premult means that alpha should
182             // be considered as being 1 everywhere, whatever the actual alpha value is.
183             // see GenericWriterPlugin::render, if (userPremult == eImageOpaque...
184             dstPix[3] = 0.f; // alpha
185         }
186 
187         srcPix += srcC;
188         dstPix += dstC;
189     }
190 }
191 
192 void
decode(const string & filename,OfxTime,int,bool,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,PixelComponentEnum pixelComponents,int pixelComponentCount,int rowBytes)193 ReadPFMPlugin::decode(const string& filename,
194                       OfxTime /*time*/,
195                       int /*view*/,
196                       bool /*isPlayback*/,
197                       const OfxRectI& renderWindow,
198                       float *pixelData,
199                       const OfxRectI& bounds,
200                       PixelComponentEnum pixelComponents,
201                       int pixelComponentCount,
202                       int rowBytes)
203 {
204     if ( (pixelComponents != ePixelComponentRGBA) && (pixelComponents != ePixelComponentRGB) && (pixelComponents != ePixelComponentAlpha) ) {
205         setPersistentMessage(Message::eMessageError, "", "PFM: can only read RGBA, RGB or Alpha components images");
206         throwSuiteStatusException(kOfxStatErrFormat);
207 
208         return;
209     }
210 
211     // read PFM header
212     std::FILE *const nfile = fopen_utf8(filename.c_str(), "rb");
213 
214     char pfm_type, item[1024] = { 0 };
215     int W = 0;
216     int H = 0;
217     int C = 0;
218     int err = 0;
219     double scale = 0.0;
220     while ( ( err = std::fscanf(nfile, "%1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
221         int c = std::fgetc(nfile);
222         (void)c;
223     }
224     if (std::sscanf(item, " P%c", &pfm_type) != 1) {
225         std::fclose(nfile);
226         setPersistentMessage(Message::eMessageError, "", string("PFM header not found in file \"") + filename + "\".");
227         throwSuiteStatusException(kOfxStatFailed);
228 
229         return;
230     }
231     while ( ( err = std::fscanf(nfile, " %1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
232         int c = std::fgetc(nfile);
233         (void)c;
234     }
235     if (std::sscanf(item, " %d %d", &W, &H) != 2) {
236         std::fclose(nfile);
237         setPersistentMessage(Message::eMessageError, "", string("WIDTH and HEIGHT fields are undefined in file \"") + filename + "\".");
238         throwSuiteStatusException(kOfxStatFailed);
239 
240         return;
241     }
242     if ( (W <= 0) || (H <= 0) || (0xffff < W) || (0xffff < H) ) {
243         std::fclose(nfile);
244         setPersistentMessage(Message::eMessageError, "", string("invalid WIDTH or HEIGHT fields in file \"") + filename + "\".");
245         throwSuiteStatusException(kOfxStatFailed);
246 
247         return;
248     }
249 
250     clearPersistentMessage();
251     while ( ( err = std::fscanf(nfile, " %1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
252         int c = std::fgetc(nfile);
253         (void)c;
254     }
255     if (std::sscanf(item, "%lf", &scale) != 1) {
256         setPersistentMessage(Message::eMessageWarning, "", string("SCALE field is undefined in file \"") + filename + "\".");
257     }
258 
259     {
260         int c = std::fgetc(nfile);
261         (void)c;
262     }
263 
264     const bool is_inverted = (scale > 0) != endianness();
265     if (pfm_type == 'F') {
266         C = 3;
267     } else {
268         C = 1;
269     }
270 
271     std::size_t numpixels = W * C;
272     vector<float> image(numpixels);
273 
274     assert(0 <= renderWindow.x1 && renderWindow.x2 <= W &&
275            0 <= renderWindow.y1 && renderWindow.y2 <= H);
276     const int x1 = renderWindow.x1;
277     const int x2 = renderWindow.x2;
278 
279     for (int y = renderWindow.y1; y < renderWindow.y2; ++y) {
280         std::size_t numread = std::fread(&image.front(), 4, numpixels, nfile);
281         if (numread < numpixels) {
282             std::fclose(nfile);
283             setPersistentMessage(Message::eMessageError, "", "could not read all the image samples needed");
284             throwSuiteStatusException(kOfxStatFailed);
285 
286             return;
287         }
288 
289         if (is_inverted) {
290             invert_endianness(&image.front(), numpixels);
291         }
292 
293         // now copy to the dstImg
294         float* dstPix = (float*)( (char*)pixelData + (y - bounds.y1) * rowBytes );
295         if (C == 1) {
296             switch (pixelComponentCount) {
297             case 1:
298                 copyLine<float, 1, 1>(&image.front(), x1, x2, C, dstPix);
299                 break;
300             case 2:
301                 copyLine<float, 1, 2>(&image.front(), x1, x2, C, dstPix);
302                 break;
303             case 3:
304                 copyLine<float, 1, 3>(&image.front(), x1, x2, C, dstPix);
305                 break;
306             case 4:
307                 copyLine<float, 1, 4>(&image.front(), x1, x2, C, dstPix);
308                 break;
309             default:
310                 break;
311             }
312         } else if (C == 3) {
313             switch (pixelComponentCount) {
314             case 1:
315                 copyLine<float, 3, 1>(&image.front(), x1, x2, C, dstPix);
316                 break;
317             case 2:
318                 copyLine<float, 3, 2>(&image.front(), x1, x2, C, dstPix);
319                 break;
320             case 3:
321                 copyLine<float, 3, 3>(&image.front(), x1, x2, C, dstPix);
322                 break;
323             case 4:
324                 copyLine<float, 3, 4>(&image.front(), x1, x2, C, dstPix);
325                 break;
326             default:
327                 break;
328             }
329         }
330     }
331     std::fclose(nfile);
332 } // ReadPFMPlugin::decode
333 
334 bool
getFrameBounds(const string & filename,OfxTime,int,OfxRectI * bounds,OfxRectI * format,double * par,string * error,int * tile_width,int * tile_height)335 ReadPFMPlugin::getFrameBounds(const string& filename,
336                               OfxTime /*time*/,
337                               int /*view*/,
338                               OfxRectI *bounds,
339                               OfxRectI *format,
340                               double *par,
341                               string *error,
342                               int* tile_width,
343                               int* tile_height)
344 {
345     assert(bounds && par);
346     // read PFM header
347     std::FILE *const nfile = std::fopen(filename.c_str(), "rb");
348 
349     char pfm_type, item[1024] = { 0 };
350     int W = 0;
351     int H = 0;
352     int err = 0;
353     double scale = 0.0;
354     while ( ( err = std::fscanf(nfile, "%1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
355         int c = std::fgetc(nfile);
356         (void)c;
357     }
358     if (std::sscanf(item, " P%c", &pfm_type) != 1) {
359         std::fclose(nfile);
360         if (error) {
361             *error = string("PFM header not found in file \"") + filename + "\".";
362         }
363 
364         return false;
365     }
366     while ( ( err = std::fscanf(nfile, " %1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
367         int c = std::fgetc(nfile);
368         (void)c;
369     }
370     if ( ( err = std::sscanf(item, " %d %d", &W, &H) ) < 2 ) {
371         std::fclose(nfile);
372         if (error) {
373             *error =  string("WIDTH and HEIGHT fields are undefined in file \"") + filename + "\".";
374         }
375 
376         return false;
377     }
378     if (err == 2) {
379         clearPersistentMessage();
380         while ( ( err = std::fscanf(nfile, " %1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
381             int c = std::fgetc(nfile);
382             (void)c;
383         }
384         if (std::sscanf(item, "%lf", &scale) != 1) {
385             setPersistentMessage(Message::eMessageWarning, "", string("SCALE field is undefined in file \"") + filename + "\".");
386         }
387     }
388     std::fclose(nfile);
389 
390     bounds->x1 = 0;
391     bounds->x2 = W;
392     bounds->y1 = 0;
393     bounds->y2 = H;
394     *format = *bounds;
395     *par = 1.;
396     *tile_width = *tile_height = 0;
397 
398     return true;
399 } // ReadPFMPlugin::getFrameBounds
400 
401 /**
402  * @brief Called when the input image/video file changed.
403  *
404  * returns true if file exists and parameters successfully guessed, false in case of error.
405  *
406  * This function is only called once: when the filename is first set.
407  *
408  * Besides returning colorspace, premult, components, and componentcount, if it returns true
409  * this function may also set extra format-specific parameters using Param::setValue.
410  * The parameters must not be animated, since their value must remain the same for a whole sequence.
411  *
412  * You shouldn't do any strong processing as this is called on the main thread and
413  * the getRegionOfDefinition() and  decode() should open the file in a separate thread.
414  *
415  * The colorspace may be set if available, else a default colorspace is used.
416  *
417  * You must also return the premultiplication state and pixel components of the image.
418  * When reading an image sequence, this is called only for the first image when the user actually selects the new sequence.
419  **/
420 bool
guessParamsFromFilename(const string &,string * colorspace,PreMultiplicationEnum * filePremult,PixelComponentEnum * components,int * componentCount)421 ReadPFMPlugin::guessParamsFromFilename(const string& /*newFile*/,
422                                        string *colorspace,
423                                        PreMultiplicationEnum *filePremult,
424                                        PixelComponentEnum *components,
425                                        int *componentCount)
426 {
427     assert(colorspace && filePremult && components && componentCount);
428 # ifdef OFX_IO_USING_OCIO
429     // Unless otherwise specified, pfm files are assumed to be linear.
430     *colorspace = OCIO::ROLE_SCENE_LINEAR;
431 # endif
432     int startingTime = getStartingTime();
433     string filename;
434     OfxStatus st = getFilenameAtTime(startingTime, &filename);
435     if ( (st != kOfxStatOK) || filename.empty() ) {
436         return false;
437     }
438     stringstream ss;
439 
440     // read PFM header
441     std::FILE *const nfile = std::fopen(filename.c_str(), "rb");
442 
443     char pfm_type = 0;
444     char item[1024] = { 0 };
445     int err = 0;
446     while ( ( err = std::fscanf(nfile, "%1023[^\n]", item) ) != EOF && (*item == '#' || !err) ) {
447         int c = std::fgetc(nfile);
448         (void)c;
449     }
450     if (std::sscanf(item, " P%c", &pfm_type) != 1) {
451         std::fclose(nfile);
452 
453         //setPersistentMessage(Message::eMessageWarning, "", string("PFM header not found in file \"") + filename + "\".");
454         return false;
455     }
456     std::fclose(nfile);
457 
458     // set the components of _outputClip
459     *components = ePixelComponentNone;
460     *componentCount = 0;
461     if (pfm_type == 'F') {
462         *components = ePixelComponentRGB;
463         *componentCount = 3;
464     } else if (pfm_type == 'f') {
465         *components = ePixelComponentAlpha;
466         *componentCount = 1;
467     } else {
468         *filePremult = eImageOpaque;
469 
470         return false;
471     }
472     if ( (*components != ePixelComponentRGBA) && (*components != ePixelComponentAlpha) ) {
473         *filePremult = eImageOpaque;
474     } else {
475         // output is always premultiplied
476         *filePremult = eImagePreMultiplied;
477     }
478 
479     return true;
480 } // ReadPFMPlugin::guessParamsFromFilename
481 
482 mDeclareReaderPluginFactory(ReadPFMPluginFactory, {}, false);
483 void
load()484 ReadPFMPluginFactory::load()
485 {
486     _extensions.clear();
487     _extensions.push_back("pfm");
488 }
489 
490 /** @brief The basic describe function, passed a plugin descriptor */
491 void
describe(ImageEffectDescriptor & desc)492 ReadPFMPluginFactory::describe(ImageEffectDescriptor &desc)
493 {
494     GenericReaderDescribe(desc, _extensions, kPluginEvaluation, kSupportsTiles, false);
495 
496     // basic labels
497     desc.setLabel(kPluginName);
498     desc.setPluginDescription(kPluginDescription);
499 }
500 
501 /** @brief The describe in context function, passed a plugin descriptor and a context */
502 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)503 ReadPFMPluginFactory::describeInContext(ImageEffectDescriptor &desc,
504                                         ContextEnum context)
505 {
506     // make some pages and to things in
507     PageParamDescriptor *page = GenericReaderDescribeInContextBegin(desc, context, isVideoStreamPlugin(),
508                                                                     kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles, true);
509 
510     GenericReaderDescribeInContextEnd(desc, context, page, "scene_linear", "scene_linear");
511 }
512 
513 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
514 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)515 ReadPFMPluginFactory::createInstance(OfxImageEffectHandle handle,
516                                      ContextEnum /*context*/)
517 {
518     ReadPFMPlugin* ret =  new ReadPFMPlugin(handle, _extensions);
519 
520     ret->restoreStateFromParams();
521 
522     return ret;
523 }
524 
525 static ReadPFMPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
526 mRegisterPluginFactoryInstance(p)
527 
528 OFXS_NAMESPACE_ANONYMOUS_EXIT
529