1 /*
2  * This file is part of openfx-arena <https://github.com/olear/openfx-arena>,
3  * Copyright (C) 2016 INRIA
4  *
5  * openfx-arena is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as published
7  * by the Free Software Foundation.
8  *
9  * openfx-arena is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with openfx-arena.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
16 */
17 
18 #ifndef MagickPlugin_h
19 #define MagickPlugin_h
20 
21 #include "ofxsImageEffect.h"
22 #include "ofxsMacros.h"
23 #include "ofxsMultiThread.h"
24 #include <Magick++.h>
25 
26 #define kParamOpenMP "openmp"
27 #define kParamOpenMPLabel "OpenMP"
28 #define kParamOpenMPHint "Enable/Disable OpenMP support. This will enable the plugin to use as many threads as allowed by host."
29 #define kParamOpenMPDefault false
30 
31 #define kParamMatte "matte"
32 #define kParamMatteLabel "Matte"
33 #define kParamMatteHint "Merge Alpha before applying effect."
34 #define kParamMatteDefault false
35 
36 #define kParamVPixel "vpixel"
37 #define kParamVPixelLabel "Virtual Pixel"
38 #define kParamVPixelHint "Virtual Pixel Method."
39 #define kParamVPixelDefault 12
40 
41 static bool _hasMP = false;
42 
43 class MagickPluginHelperBase
44     : public OFX::ImageEffect
45 {
46 public:
47 
48     MagickPluginHelperBase(OfxImageEffectHandle handle);
49     virtual void changedParam(const OFX::InstanceChangedArgs &args, const std::string &paramName) OVERRIDE;
50     static OFX::PageParamDescriptor* describeInContextBegin(OFX::ImageEffectDescriptor &desc, OFX::ContextEnum context);
51     static void describeInContextEnd(OFX::ImageEffectDescriptor &desc, OFX::ContextEnum context, OFX::PageParamDescriptor* page);
52 
53 protected:
54     OFX::Clip *_dstClip;
55     OFX::Clip *_srcClip;
56     OFX::BooleanParam *_enableMP;
57     OFX::BooleanParam *_matte;
58     OFX::ChoiceParam *_vpixel;
59     int _renderscale;
60 };
61 
62 template <int SupportsRenderScale>
63 class MagickPluginHelper
64     : public MagickPluginHelperBase
65 {
66 public:
67 
MagickPluginHelper(OfxImageEffectHandle handle)68     MagickPluginHelper(OfxImageEffectHandle handle)
69         : MagickPluginHelperBase(handle)
70     {
71         _renderscale = SupportsRenderScale;
72     }
73 
74     virtual void render(const OFX::RenderArguments &args) OVERRIDE FINAL;
75     virtual bool getRegionOfDefinition(const OFX::RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
76     virtual void render(const OFX::RenderArguments &args, Magick::Image &image) = 0;
describeInContextBegin(OFX::ImageEffectDescriptor & desc,OFX::ContextEnum context)77     static OFX::PageParamDescriptor* describeInContextBegin(OFX::ImageEffectDescriptor &desc, OFX::ContextEnum context)
78     {
79         return MagickPluginHelperBase::describeInContextBegin(desc, context);
80     }
81 };
82 
83 template <int SupportsRenderScale>
render(const OFX::RenderArguments & args)84 void MagickPluginHelper<SupportsRenderScale>::render(const OFX::RenderArguments &args)
85 {
86     // render scale
87     if (!SupportsRenderScale && (args.renderScale.x != 1. || args.renderScale.y != 1.)) {
88         OFX::throwSuiteStatusException(kOfxStatFailed);
89         return;
90     }
91 
92     // get src clip
93     if (!_srcClip) {
94         OFX::throwSuiteStatusException(kOfxStatFailed);
95         return;
96     }
97     assert(_srcClip);
98     OFX::auto_ptr<const OFX::Image> srcImg(_srcClip->fetchImage(args.time));
99     OfxRectI srcRod,srcBounds;
100     if (srcImg.get()) {
101         srcRod = srcImg->getRegionOfDefinition();
102         srcBounds = srcImg->getBounds();
103         if (srcImg->getRenderScale().x != args.renderScale.x ||
104             srcImg->getRenderScale().y != args.renderScale.y ||
105             srcImg->getField() != args.fieldToRender) {
106             setPersistentMessage(OFX::Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
107             OFX::throwSuiteStatusException(kOfxStatFailed);
108             return;
109         }
110     } else {
111         OFX::throwSuiteStatusException(kOfxStatFailed);
112         return;
113     }
114 
115     // get dest clip
116     if (!_dstClip) {
117         OFX::throwSuiteStatusException(kOfxStatFailed);
118         return;
119     }
120     assert(_dstClip);
121     OFX::auto_ptr<OFX::Image> dstImg(_dstClip->fetchImage(args.time));
122     if (!dstImg.get()) {
123         OFX::throwSuiteStatusException(kOfxStatFailed);
124         return;
125     }
126     if (dstImg->getRenderScale().x != args.renderScale.x ||
127         dstImg->getRenderScale().y != args.renderScale.y ||
128         dstImg->getField() != args.fieldToRender) {
129         setPersistentMessage(OFX::Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
130         OFX::throwSuiteStatusException(kOfxStatFailed);
131         return;
132     }
133 
134     // get bit depth
135     OFX::BitDepthEnum dstBitDepth = dstImg->getPixelDepth();
136     if (dstBitDepth != OFX::eBitDepthFloat || (srcImg.get() && (dstBitDepth != srcImg->getPixelDepth()))) {
137         OFX::throwSuiteStatusException(kOfxStatErrFormat);
138         return;
139     }
140 
141     // get pixel component
142     OFX::PixelComponentEnum dstComponents  = dstImg->getPixelComponents();
143     if (dstComponents != OFX::ePixelComponentRGBA|| (srcImg.get() && (dstComponents != srcImg->getPixelComponents()))) {
144         OFX::throwSuiteStatusException(kOfxStatErrFormat);
145         return;
146     }
147 
148     // are we in the image bounds?
149     OfxRectI dstBounds = dstImg->getBounds();
150     if(args.renderWindow.x1 < dstBounds.x1 || args.renderWindow.x1 >= dstBounds.x2 || args.renderWindow.y1 < dstBounds.y1 || args.renderWindow.y1 >= dstBounds.y2 ||
151        args.renderWindow.x2 <= dstBounds.x1 || args.renderWindow.x2 > dstBounds.x2 || args.renderWindow.y2 <= dstBounds.y1 || args.renderWindow.y2 > dstBounds.y2) {
152         OFX::throwSuiteStatusException(kOfxStatErrValue);
153         return;
154     }
155 
156     // get image size
157     int width = args.renderWindow.x2 - args.renderWindow.x1;
158     int height = args.renderWindow.y2 - args.renderWindow.y1;
159 
160     // params
161     bool enableMP, matte;
162     int vpixel;
163     _enableMP->getValueAtTime(args.time, enableMP);
164     _matte->getValueAtTime(args.time, matte);
165     _vpixel->getValueAtTime(args.time, vpixel);
166 
167     // OpenMP
168 #ifndef DISABLE_OPENMP
169     unsigned int threads = 1;
170     if (_hasMP && enableMP) {
171         threads = OFX::MultiThread::getNumCPUs();
172     }
173     Magick::ResourceLimits::thread(threads);
174 #endif
175 
176     // render
177     Magick::Image image(Magick::Geometry(width, height), Magick::Color("rgba(0,0,0,0)"));
178     Magick::Image output(Magick::Geometry(width, height), Magick::Color("rgba(0,0,0,1)"));
179     if (_srcClip && _srcClip->isConnected()) {
180         image.read(width, height, "RGBA", Magick::FloatPixel, (float*)srcImg->getPixelData());
181         image.flip();
182         switch (vpixel) {
183         case 0:
184             image.virtualPixelMethod(Magick::UndefinedVirtualPixelMethod);
185             break;
186         case 1:
187             image.virtualPixelMethod(Magick::BackgroundVirtualPixelMethod);
188             break;
189         case 2:
190             image.virtualPixelMethod(Magick::BlackVirtualPixelMethod);
191             break;
192         case 3:
193             image.virtualPixelMethod(Magick::CheckerTileVirtualPixelMethod);
194             break;
195         case 4:
196             image.virtualPixelMethod(Magick::DitherVirtualPixelMethod);
197             break;
198         case 5:
199             image.virtualPixelMethod(Magick::EdgeVirtualPixelMethod);
200             break;
201         case 6:
202             image.virtualPixelMethod(Magick::GrayVirtualPixelMethod);
203             break;
204         case 7:
205             image.virtualPixelMethod(Magick::HorizontalTileVirtualPixelMethod);
206             break;
207         case 8:
208             image.virtualPixelMethod(Magick::HorizontalTileEdgeVirtualPixelMethod);
209             break;
210         case 9:
211             image.virtualPixelMethod(Magick::MirrorVirtualPixelMethod);
212             break;
213         case 10:
214             image.virtualPixelMethod(Magick::RandomVirtualPixelMethod);
215             break;
216         case 11:
217             image.virtualPixelMethod(Magick::TileVirtualPixelMethod);
218             break;
219         case 12:
220             image.virtualPixelMethod(Magick::TransparentVirtualPixelMethod);
221             break;
222         case 13:
223             image.virtualPixelMethod(Magick::VerticalTileVirtualPixelMethod);
224             break;
225         case 14:
226             image.virtualPixelMethod(Magick::VerticalTileEdgeVirtualPixelMethod);
227             break;
228         case 15:
229             image.virtualPixelMethod(Magick::WhiteVirtualPixelMethod);
230             break;
231         }
232         if (matte) {
233 #if MagickLibVersion >= 0x700
234             image.alpha(false);
235             image.alpha(true);
236 #else
237             image.matte(false);
238             image.matte(true);
239 #endif
240         }
241         render(args, image);
242         image.flip();
243     }
244     if (_dstClip && _dstClip->isConnected()) {
245         output.composite(image, 0, 0, Magick::OverCompositeOp);
246 #if MagickLibVersion >= 0x700
247         output.composite(image, 0, 0, Magick::CopyAlphaCompositeOp);
248 #else
249         output.composite(image, 0, 0, Magick::CopyOpacityCompositeOp);
250 #endif
251         output.write(0, 0, args.renderWindow.x2 - args.renderWindow.x1,args.renderWindow.y2 - args.renderWindow.y1, "RGBA", Magick::FloatPixel, (float*)dstImg->getPixelData());
252     }
253 
254 }
255 
256 template <int SupportsRenderScale>
getRegionOfDefinition(const OFX::RegionOfDefinitionArguments & args,OfxRectD & rod)257 bool MagickPluginHelper<SupportsRenderScale>::getRegionOfDefinition(const OFX::RegionOfDefinitionArguments &args, OfxRectD &rod)
258 {
259     if (!SupportsRenderScale && (args.renderScale.x != 1. || args.renderScale.y != 1.)) {
260         OFX::throwSuiteStatusException(kOfxStatFailed);
261         return false;
262     }
263     if (_srcClip && _srcClip->isConnected()) {
264         rod = _srcClip->getRegionOfDefinition(args.time);
265     } else {
266         rod.x1 = rod.y1 = kOfxFlagInfiniteMin;
267         rod.x2 = rod.y2 = kOfxFlagInfiniteMax;
268     }
269     return true;
270 }
271 
272 #endif // MagickPlugin_h
273