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