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