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 writer plugin.
21  * Writes an image in the Portable Float Map (PFM) format.
22  */
23 
24 #include <cstdio> // fopen, fwrite, fprintf...
25 #include <vector>
26 #include <algorithm>
27 
28 #include "GenericOCIO.h"
29 
30 #include "GenericWriter.h"
31 #include "ofxsMacros.h"
32 #include "ofxsFileOpen.h"
33 
34 using namespace OFX;
35 using namespace IO;
36 #ifdef OFX_IO_USING_OCIO
37 namespace OCIO = OCIO_NAMESPACE;
38 #endif
39 
40 using std::string;
41 using std::vector;
42 
43 OFXS_NAMESPACE_ANONYMOUS_ENTER
44 
45 #define kPluginName "WritePFM"
46 #define kPluginGrouping "Image/Writers"
47 #define kPluginDescription "Write PFM (Portable Float Map) files."
48 #define kPluginIdentifier "fr.inria.openfx.WritePFM"
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 40 // plugin quality from 0 (bad) to 100 (perfect) or -1 if not evaluated
52 
53 #define kSupportsRGBA true
54 #define kSupportsRGB true
55 #define kSupportsXY false
56 #define kSupportsAlpha true
57 
58 /**
59    \return \c false for "Little Endian", \c true for "Big Endian".
60  **/
61 static inline bool
endianness()62 endianness()
63 {
64     const int x = 1;
65 
66     return ( (unsigned char *)&x )[0] ? false : true;
67 }
68 
69 class WritePFMPlugin
70     : public GenericWriterPlugin
71 {
72 public:
73 
74     WritePFMPlugin(OfxImageEffectHandle handle, const vector<string>& extensions);
75 
76     virtual ~WritePFMPlugin();
77 
78 private:
79 
80     virtual void encode(const string& filename,
81                         const OfxTime time,
82                         const string& viewName,
83                         const float *pixelData,
84                         const OfxRectI& bounds,
85                         const float pixelAspectRatio,
86                         const int pixelDataNComps,
87                         const int dstNCompsStartIndex,
88                         const int dstNComps,
89                         const int rowBytes) OVERRIDE FINAL;
90     virtual bool isImageFile(const string& fileExtension) const OVERRIDE FINAL;
getExpectedInputPremultiplication() const91     virtual PreMultiplicationEnum getExpectedInputPremultiplication() const OVERRIDE FINAL { return eImageUnPreMultiplied; }
92 
93     virtual void onOutputFileChanged(const string& newFile, bool setColorSpace) OVERRIDE FINAL;
94 };
95 
WritePFMPlugin(OfxImageEffectHandle handle,const vector<string> & extensions)96 WritePFMPlugin::WritePFMPlugin(OfxImageEffectHandle handle,
97                                const vector<string>& extensions)
98     : GenericWriterPlugin(handle, extensions, kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha)
99 {
100 }
101 
~WritePFMPlugin()102 WritePFMPlugin::~WritePFMPlugin()
103 {
104 }
105 
106 template <class PIX, int srcC, int dstC>
107 static void
copyLine(const PIX * pixelData,int rowbytes,int W,int,int dstNCompsStartIndex,int C,int y,PIX * image)108 copyLine(const PIX* pixelData,
109          int rowbytes,
110          int W,
111          int /*H*/,
112          int dstNCompsStartIndex,
113          int C,
114          int y,
115          PIX *image)
116 {
117     assert(dstC == 3 || dstC == 1);
118 
119     const PIX *srcPix = (const PIX*)( (char*)pixelData + y * rowbytes );
120     PIX *dstPix = image;
121 
122     for (int x = 0; x < W; ++x) {
123         if (srcC == 1) {
124             // alpha/grayscale image
125             for (int c = 0; c < std::min(dstC, 3); ++c) {
126                 dstPix[c] = srcPix[dstNCompsStartIndex];
127             }
128         } else {
129             // color image (if dstC == 1, only the red channel is extracted)
130             for (int c = 0; c < std::min(dstC, 3); ++c) {
131                 dstPix[c] = srcPix[dstNCompsStartIndex + c];
132             }
133         }
134 
135         srcPix += C;
136         dstPix += dstC;
137     }
138 }
139 
140 void
encode(const string & filename,const OfxTime,const string &,const float * pixelData,const OfxRectI & bounds,const float,const int pixelDataNComps,const int dstNCompsStartIndex,const int dstNComps,const int rowBytes)141 WritePFMPlugin::encode(const string& filename,
142                        const OfxTime /*time*/,
143                        const string& /*viewName*/,
144                        const float *pixelData,
145                        const OfxRectI& bounds,
146                        const float /*pixelAspectRatio*/,
147                        const int pixelDataNComps,
148                        const int dstNCompsStartIndex,
149                        const int dstNComps,
150                        const int rowBytes)
151 {
152     if ( (dstNComps != 4) && (dstNComps != 3) && (dstNComps != 1) ) {
153         setPersistentMessage(Message::eMessageError, "", "PFM: can only write RGBA, RGB or Alpha components images");
154         throwSuiteStatusException(kOfxStatErrFormat);
155 
156         return;
157     }
158 
159     std::FILE *const nfile = fopen_utf8(filename.c_str(), "wb");
160     if (!nfile) {
161         setPersistentMessage(Message::eMessageError, "", "Cannot open file \"" + filename + "\"");
162         throwSuiteStatusException(kOfxStatFailed);
163 
164         return;
165     }
166     int width = (bounds.x2 - bounds.x1);
167     int height = (bounds.y2 - bounds.y1);
168     const int depth = (dstNComps == 1 ? 1 : 3);
169     const unsigned int buf_size = width * depth;
170     vector<float> buffer(buf_size);
171     std::fill(buffer.begin(), buffer.end(), 0.);
172 
173     std::fprintf(nfile, "P%c\n%u %u\n%d.0\n", (dstNComps == 1 ? 'f' : 'F'), width, height, endianness() ? 1 : -1);
174 
175     for (int y = 0; y < height; ++y) {
176         // now copy to the dstImg
177         if (depth == 1) {
178             assert(dstNComps == 1);
179             copyLine<float, 1, 1>( pixelData, rowBytes, width, height,  dstNCompsStartIndex, pixelDataNComps, y, &buffer.front() );
180         } else if (depth == 3) {
181             assert(dstNComps == 3 || dstNComps == 4);
182             if (dstNComps == 3) {
183                 copyLine<float, 3, 3>( pixelData, rowBytes, width, height, dstNCompsStartIndex, pixelDataNComps, y, &buffer.front() );
184             } else if (dstNComps == 4) {
185                 copyLine<float, 4, 3>( pixelData, rowBytes, width, height, dstNCompsStartIndex, pixelDataNComps, y, &buffer.front() );
186             }
187         }
188 
189         std::fwrite(&buffer.front(), sizeof(float), buf_size, nfile);
190     }
191     std::fclose(nfile);
192 }
193 
194 bool
isImageFile(const string &) const195 WritePFMPlugin::isImageFile(const string& /*fileExtension*/) const
196 {
197     return true;
198 }
199 
200 void
onOutputFileChanged(const string &,bool setColorSpace)201 WritePFMPlugin::onOutputFileChanged(const string & /*filename*/,
202                                     bool setColorSpace)
203 {
204     if (setColorSpace) {
205 #     ifdef OFX_IO_USING_OCIO
206         // Unless otherwise specified, pfm files are assumed to be linear.
207         _ocio->setOutputColorspace(OCIO::ROLE_SCENE_LINEAR);
208 #     endif
209     }
210 }
211 
212 mDeclareWriterPluginFactory(WritePFMPluginFactory, {}, false);
213 void
load()214 WritePFMPluginFactory::load()
215 {
216     _extensions.clear();
217     _extensions.push_back("pfm");
218 }
219 
220 /** @brief The basic describe function, passed a plugin descriptor */
221 void
describe(ImageEffectDescriptor & desc)222 WritePFMPluginFactory::describe(ImageEffectDescriptor &desc)
223 {
224     GenericWriterDescribe(desc, eRenderFullySafe, _extensions, kPluginEvaluation, false, false);
225     // basic labels
226     desc.setLabel(kPluginName);
227     desc.setPluginDescription(kPluginDescription);
228 }
229 
230 /** @brief The describe in context function, passed a plugin descriptor and a context */
231 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)232 WritePFMPluginFactory::describeInContext(ImageEffectDescriptor &desc,
233                                          ContextEnum context)
234 {
235     // make some pages and to things in
236     PageParamDescriptor *page = GenericWriterDescribeInContextBegin(desc, context,
237                                                                     kSupportsRGBA,
238                                                                     kSupportsRGB,
239                                                                     kSupportsXY,
240                                                                     kSupportsAlpha,
241                                                                     "scene_linear", "scene_linear", false);
242 
243     GenericWriterDescribeInContextEnd(desc, context, page);
244 }
245 
246 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
247 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)248 WritePFMPluginFactory::createInstance(OfxImageEffectHandle handle,
249                                       ContextEnum /*context*/)
250 {
251     WritePFMPlugin* ret = new WritePFMPlugin(handle, _extensions);
252 
253     ret->restoreStateFromParams();
254 
255     return ret;
256 }
257 
258 static WritePFMPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
259 mRegisterPluginFactoryInstance(p)
260 
261 OFXS_NAMESPACE_ANONYMOUS_EXIT
262