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 ¶mName) 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