1 /* ***** BEGIN LICENSE BLOCK *****
2 * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3 * Copyright (C) 2013-2018 INRIA
4 *
5 * openfx-misc 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-misc 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-misc. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17 * ***** END LICENSE BLOCK ***** */
18
19 /*
20 * OFX Transform & DirBlur plugins.
21 */
22
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <iostream>
26 #include <limits>
27 #include <algorithm>
28
29 #include "ofxsTransform3x3.h"
30 #include "ofxsTransformInteract.h"
31 #include "ofxsFormatResolution.h"
32 #include "ofxsCoords.h"
33 #include "ofxsThreadSuite.h"
34
35 using namespace OFX;
36
37 OFXS_NAMESPACE_ANONYMOUS_ENTER
38
39 #define kPluginName "ReformatOFX"
40 #define kPluginGrouping "Transform"
41 #define kPluginDescription "Convert the image to another format or size.\n" \
42 "An image transform is computed that goes from the input region of definition (RoD) to the selected format. The Resize Type parameter adjust the way the transform is computed.\n" \
43 "This plugin concatenates transforms.\n" \
44 "See also: http://opticalenquiry.com/nuke/index.php?title=Reformat"
45
46 #define kPluginDescriptionNatron "Convert the image to another format or size.\n" \
47 "An image transform is computed that goes from the input format, regardless of the region of definition (RoD), to the selected format. The Resize Type parameter adjust the way the transform is computed.\n" \
48 "The output format is set by this effect.\n" \
49 "In order to set the output format without transforming the image content, use the NoOp effect.\n" \
50 "This plugin concatenates transforms.\n" \
51 "See also: http://opticalenquiry.com/nuke/index.php?title=Reformat"
52
53 #define kPluginIdentifier "net.sf.openfx.Reformat"
54 // History:
55 // version 1.0: initial version
56 // version 1.1: fix https://github.com/MrKepzie/Natron/issues/1397
57 // version 1.2: add useRoD parameter for Natron
58 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
59 #define kPluginVersionMinor 1 // Increment this when you have fixed a bug or made it faster.
60
61 #define kParamUseRoD "useRoD"
62 #define kParamUseRoDLabel "Use Source RoD"
63 #define kParamUseRoDHint "Use the region of definition of the source as the source format."
64
65 #define kParamType "reformatType"
66 #define kParamTypeLabel "Type"
67 #define kParamTypeHint "To Format: Converts between formats, the image is resized to fit in the target format. " \
68 "To Box: Scales to fit into a box of a given width and height. " \
69 "Scale: Scales the image (rounding to integer pixel sizes)."
70 #define kParamTypeOptionToFormat "To Format", "Resize to predefined format.", "format"
71 #define kParamTypeOptionToBox "To Box", "Resize to given bounding box.", "box"
72 #define kParamTypeOptionScale "Scale", "Apply scale.", "scale"
73
74 enum ReformatTypeEnum
75 {
76 eReformatTypeToFormat = 0,
77 eReformatTypeToBox,
78 eReformatTypeScale,
79 };
80
81 #define kParamFormat kNatronParamFormatChoice
82 #define kParamFormatLabel "Format"
83 #define kParamFormatHint "The output format"
84 #define kParamFormatDefault eParamFormatPCVideo
85
86
87 #define kParamFormatBoxSize kNatronParamFormatSize
88 #define kParamFormatBoxSizeLabel "Size"
89 #define kParamFormatBoxSizeHint "The output dimensions of the image in pixels."
90
91 #define kParamFormatBoxPAR kNatronParamFormatPar
92 #define kParamFormatBoxPARLabel "Pixel Aspect Ratio"
93 #define kParamFormatBoxPARHint "Output pixel aspect ratio."
94
95 #define kParamBoxSize "boxSize"
96 #define kParamBoxSizeLabel "Size"
97 #define kParamBoxSizeHint "The output dimensions of the image in pixels."
98
99 #define kParamBoxFixed "boxFixed"
100 #define kParamBoxFixedLabel "Force This Shape"
101 #define kParamBoxFixedHint "If checked, the output image is cropped to this size. Else, image is resized according to the resize type but the whole image is kept."
102
103 #define kParamBoxPAR "boxPar"
104 #define kParamBoxPARLabel "Pixel Aspect Ratio"
105 #define kParamBoxPARHint "Output pixel aspect ratio."
106
107 #define kParamScale "reformatScale"
108 #define kParamScaleLabel "Scale"
109 #define kParamScaleHint "The scale factor to apply to the image. The scale factor is rounded slightly, so that the output image is an integer number of pixels in the direction chosen under resize type."
110
111 #define kParamScaleUniform "reformatScaleUniform"
112 #define kParamScaleUniformLabel "Uniform"
113 #define kParamScaleUniformHint "Use the X scale for both directions"
114
115 #define kParamResize "resize"
116 #define kParamResizeLabel "Resize Type"
117 #define kParamResizeHint "Format: Converts between formats, the image is resized to fit in the target format. " \
118 "Size: Scales to fit into a box of a given width and height. " \
119 "Scale: Scales the image."
120 #define kParamResizeOptionNone "None", "Do not resize the original.", "none"
121 #define kParamResizeOptionWidth "Width", "Scale the original so that its width fits the output width, while preserving the aspect ratio.", "width"
122 #define kParamResizeOptionHeight "Height", "Scale the original so that its height fits the output height, while preserving the aspect ratio.", "height"
123 #define kParamResizeOptionFit "Fit", "Scale the original so that its smallest size fits the output width or height, while preserving the aspect ratio.", "fit"
124 #define kParamResizeOptionFill "Fill", "Scale the original so that its longest size fits the output width or height, while preserving the aspect ratio.", "fill"
125 #define kParamResizeOptionDistort "Distort", "Scale the original so that both sides fit the output dimensions. This does not preserve the aspect ratio.", "distort"
126
127 enum ResizeEnum
128 {
129 eResizeNone = 0,
130 eResizeWidth,
131 eResizeHeight,
132 eResizeFit,
133 eResizeFill,
134 eResizeDistort,
135 };
136
137
138 #define kParamReformatCenter "reformatCentered"
139 #define kParamReformatCenterLabel "Center"
140 #define kParamReformatCenterHint "Translate the center of the image to the center of the output. Otherwise, the lower left corner is left untouched."
141
142 #define kParamFlip "flip"
143 #define kParamFlipLabel "Flip"
144 #define kParamFlipHint "Mirror the image vertically."
145
146 #define kParamFlop "flop"
147 #define kParamFlopLabel "Flop"
148 #define kParamFlopHint "Mirror the image horizontally."
149
150 #define kParamTurn "turn"
151 #define kParamTurnLabel "Turn"
152 #define kParamTurnHint "Rotate the image by 90 degrees counter-clockwise."
153
154 #define kParamPreserveBoundingBox "preserveBB"
155 #define kParamPreserveBoundingBoxLabel "Preserve BBox" // and Concat"
156 #define kParamPreserveBoundingBoxHint \
157 "If checked, preserve the whole image bounding box and concatenate transforms downstream.\n" \
158 "Normally, all pixels outside of the outside format are clipped off. If this is checked, the whole image RoD is kept.\n" \
159 "By default, transforms are only concatenated upstream, i.e. the image is rendered by this effect by concatenating upstream transforms (e.g. CornerPin, Transform...), and the original image is resampled only once. If checked, and there are concatenating transform effects downstream, the image is rendered by the last consecutive concatenating effect."
160
161
162 static bool gHostCanTransform;
163 static bool gHostIsNatron = false;
164 static bool gHostSupportsFormat = false;
165
166 ////////////////////////////////////////////////////////////////////////////////
167 /** @brief The plugin that does our work */
168 class ReformatPlugin
169 : public Transform3x3Plugin
170 {
171 public:
172 /** @brief ctor */
ReformatPlugin(OfxImageEffectHandle handle)173 ReformatPlugin(OfxImageEffectHandle handle)
174 : Transform3x3Plugin(handle, false, eTransform3x3ParamsTypeNone)
175 , _type(NULL)
176 , _format(NULL)
177 , _formatBoxSize(NULL)
178 , _formatBoxPAR(NULL)
179 , _boxSize(NULL)
180 , _boxFixed(NULL)
181 , _boxPAR(NULL)
182 , _scale(NULL)
183 , _scaleUniform(NULL)
184 , _preserveBB(NULL)
185 , _resize(NULL)
186 , _center(NULL)
187 , _flip(NULL)
188 , _flop(NULL)
189 , _turn(NULL)
190 {
191 _filter = fetchChoiceParam(kParamFilterType);
192 _clamp = fetchBooleanParam(kParamFilterClamp);
193 _blackOutside = fetchBooleanParam(kParamFilterBlackOutside);
194
195 // NON-GENERIC
196 _useRoD = fetchBooleanParam(kParamUseRoD);
197 _type = fetchChoiceParam(kParamType);
198 _format = fetchChoiceParam(kParamFormat);
199 _formatBoxSize = fetchInt2DParam(kParamFormatBoxSize);
200 _formatBoxPAR = fetchDoubleParam(kParamFormatBoxPAR);
201 _boxSize = fetchInt2DParam(kParamBoxSize);
202 _boxFixed = fetchBooleanParam(kParamBoxFixed);
203 _boxPAR = fetchDoubleParam(kParamBoxPAR);
204 _scale = fetchDouble2DParam(kParamScale);
205 _scaleUniform = fetchBooleanParam(kParamScaleUniform);
206 _preserveBB = fetchBooleanParam(kParamPreserveBoundingBox);
207 _resize = fetchChoiceParam(kParamResize);
208 _center = fetchBooleanParam(kParamReformatCenter);
209 _flip = fetchBooleanParam(kParamFlip);
210 _flop = fetchBooleanParam(kParamFlop);
211 _turn = fetchBooleanParam(kParamTurn);
212 assert(_useRoD && _type && _format && _boxSize && _boxFixed && _boxPAR && _scale && _scaleUniform && _preserveBB && _resize && _center && _flip && _flop && _turn);
213
214
215 if (!gHostIsNatron) {
216 // try to guess the output format from the project size
217 // do it only if not Natron otherwise this will override what the host has set in the format when loading
218 double projectPAR = getProjectPixelAspectRatio();
219 OfxPointD projectSize = getProjectSize();
220
221 ///Try to find a format matching the project format in which case we switch to format mode otherwise
222 ///switch to size mode and set the size accordingly
223 bool foundFormat = false;
224 for (int i = 0; i < (int)eParamFormatCount; ++i) {
225 int width = 0, height = 0;
226 double par = -1.;
227 getFormatResolution( (EParamFormat)i, &width, &height, &par );
228 assert(par != -1);
229 if ( (width * par == projectSize.x) && (height == projectSize.y) && (std::abs(par - projectPAR) < 0.01) ) {
230 _type->setValue( (int)eReformatTypeToFormat );
231 _format->setValue( (EParamFormat)i );
232 _boxSize->setValue(width, height);
233 _formatBoxSize->setValue(width, height);
234 _boxPAR->setValue(par);
235 _formatBoxPAR->setValue(par);
236 foundFormat = true;
237 break;
238 }
239 }
240 if (!foundFormat) {
241 _type->setValue( (int)eReformatTypeToBox );
242 _boxSize->setValue(projectSize.x / projectPAR, projectSize.y);
243 _formatBoxSize->setValue(projectSize.x / projectPAR, projectSize.y);
244 _boxPAR->setValue(projectPAR);
245 _formatBoxPAR->setValue(projectPAR);
246 _boxFixed->setValue(true);
247 }
248 }
249
250 // finally
251 syncPrivateData();
252 }
253
254
255 private:
256 virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
257 virtual bool isIdentity(double time) OVERRIDE FINAL;
258 virtual bool getInverseTransformCanonical(double time, int view, double amount, bool invert, Matrix3x3* invtransform) const OVERRIDE FINAL;
259 virtual void changedParam(const InstanceChangedArgs &args, const std::string ¶mName) OVERRIDE FINAL;
260 virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
261
262 /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)263 virtual void syncPrivateData(void) OVERRIDE FINAL
264 {
265 // On Natron, hide the uniform parameter if it is false and not animated,
266 // since uniform scaling is easy through Natron's GUI.
267 // The parameter is kept for backward compatibility.
268 // Fixes https://github.com/MrKepzie/Natron/issues/1204
269 if ( getImageEffectHostDescription()->isNatron &&
270 !_scaleUniform->getValue() &&
271 ( _scaleUniform->getNumKeys() == 0) ) {
272 _scaleUniform->setIsSecretAndDisabled(true);
273 }
274 refreshVisibility();
275 refreshDynamicProps();
276 }
277
278
279 void refreshVisibility();
280
281 void refreshDynamicProps();
282
283 bool getBoxValues(double time, int *w, int *h, double *par, bool *boxFixed) const;
284
285 void getInputFormat(const double time,
286 double* par,
287 OfxRectD* rect) const;
288
289 void getOutputFormat(const double time,
290 double* par,
291 OfxRectD* rect, // the rect to which the input format is mapped
292 OfxRectI* format) const; // the full format (only useful if host supports format, really)
293
294 // NON-GENERIC
295 BooleanParam* _useRoD;
296 ChoiceParam *_type;
297 ChoiceParam *_format;
298 Int2DParam *_formatBoxSize;
299 DoubleParam* _formatBoxPAR;
300 Int2DParam *_boxSize;
301 BooleanParam* _boxFixed;
302 DoubleParam* _boxPAR;
303 Double2DParam *_scale;
304 BooleanParam* _scaleUniform;
305 BooleanParam* _preserveBB;
306 ChoiceParam *_resize;
307 BooleanParam* _center;
308 BooleanParam* _flip;
309 BooleanParam* _flop;
310 BooleanParam* _turn;
311 };
312
313 // override the rod call
314 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)315 ReformatPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
316 OfxRectD &rod)
317 {
318 if ( !_srcClip || !_srcClip->isConnected() ) {
319 return false;
320 }
321
322 bool ret = Transform3x3Plugin::getRegionOfDefinition(args, rod);
323 if ( !ret ||
324 _preserveBB->getValue() ) {
325 return ret;
326 }
327
328 const double time = args.time;
329 // intersect with format RoD
330 OfxRectD rect;
331 OfxRectI format;
332 double par;
333 getOutputFormat(time, &par, &rect, &format);
334 const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
335 OfxRectD formatrod;
336 Coords::toCanonical(format, rsOne, par, &formatrod);
337
338 Coords::rectIntersection(rod, formatrod, &rod);
339
340 return true;
341 } // ReformatPlugin::getRegionOfDefinition
342
343 // overridden is identity
344 bool
isIdentity(const double time)345 ReformatPlugin::isIdentity(const double time)
346 {
347 if ( _center->getValueAtTime(time) || _flip->getValueAtTime(time) ||
348 _flop->getValueAtTime(time) || _turn->getValueAtTime(time) ) {
349 return false;
350 }
351
352 if ( (ResizeEnum)_resize->getValueAtTime(time) == eResizeNone ) {
353 return true;
354 }
355
356 return false;
357 }
358
359 // recturn the input format in pixel units (we use a RectD in case the input format is the RoD)
360 void
getInputFormat(const double time,double * par,OfxRectD * rect) const361 ReformatPlugin::getInputFormat(const double time,
362 double* par,
363 OfxRectD* rect) const
364 {
365 *par = _srcClip->getPixelAspectRatio();
366 #ifdef OFX_EXTENSIONS_NATRON
367 if (gHostSupportsFormat && !_useRoD->getValueAtTime(time)) {
368 OfxRectI format;
369 _srcClip->getFormat(format);
370 if ( !Coords::rectIsEmpty(format) ) {
371 // host returned a non-empty format
372 rect->x1 = format.x1;
373 rect->y1 = format.y1;
374 rect->x2 = format.x2;
375 rect->y2 = format.y2;
376 return;
377 }
378 }
379 // host does not support format
380 #endif
381 OfxRectD srcRod = _srcClip->getRegionOfDefinition(time);
382 const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
383 Coords::toPixelSub(srcRod, rsOne, *par, rect);
384 }
385
386 void
getOutputFormat(const double time,double * par,OfxRectD * rect,OfxRectI * format) const387 ReformatPlugin::getOutputFormat(const double time,
388 double* par,
389 OfxRectD* rect,
390 OfxRectI* format) const
391 {
392 int type_i;
393
394 _type->getValue(type_i);
395 OfxPointI boxSize;
396 double boxPAR;
397 bool boxFixed;
398 getBoxValues(time, &boxSize.x, &boxSize.y, &boxPAR, &boxFixed);
399
400 ResizeEnum resize = (ResizeEnum)_resize->getValueAtTime(time);
401 bool center = _center->getValueAtTime(time);
402 //bool flip = _flip->getValueAtTime(time);
403 //bool flop = _flop->getValueAtTime(time);
404 bool turn = _turn->getValueAtTime(time);
405 // same as getRegionOfDefinition, but without rounding, and without conversion to pixels
406
407
408 if (format && boxFixed) { // the non-boxFixed case is treated at the end of the function
409 format->x1 = format->y1 = 0;
410 format->x2 = boxSize.x;
411 format->y2 = boxSize.y;
412 }
413
414 if ( (boxSize.x == 0) && (boxSize.y == 0) ) {
415 //probably scale is 0
416 rect->x1 = rect->y1 = rect->x2 = rect->y2 = 0;
417 *par = 1.;
418
419 return;
420 }
421 OfxRectD boxRod = { 0., 0., (double)boxSize.x * boxPAR, (double)boxSize.y};
422
423 OfxRectD srcRod;
424 {
425 double par;
426 OfxRectD format;
427 const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
428 getInputFormat(time, &par, &format);
429 Coords::toCanonical(format, rsOne, par, &srcRod);
430 }
431 if ( Coords::rectIsEmpty(srcRod) ) {
432 // degenerate case
433 rect->x1 = rect->y1 = rect->x2 = rect->y2 = 0;
434 *par = 1.;
435
436 return;
437 }
438 double srcw = srcRod.x2 - srcRod.x1;
439 double srch = srcRod.y2 - srcRod.y1;
440 // if turn, inverse both dimensions
441 if (turn) {
442 std::swap(srcw, srch);
443 }
444 // if fit or fill, determine if it should be fit to width or height
445 if (resize == eResizeFit) {
446 if (boxRod.x2 * srch > boxRod.y2 * srcw) {
447 resize = eResizeHeight;
448 } else {
449 resize = eResizeWidth;
450 }
451 } else if (resize == eResizeFill) {
452 if (boxRod.x2 * srch > boxRod.y2 * srcw) {
453 resize = eResizeWidth;
454 } else {
455 resize = eResizeHeight;
456 }
457 }
458
459 OfxRectD dstRod;
460 dstRod.x1 = dstRod.y1 = dstRod.x2 = dstRod.y2 = 0.;
461 if (resize == eResizeNone) {
462 if (center && boxFixed) {
463 // translate the source
464 double xoff = ( (boxRod.x1 + boxRod.x2) - (srcRod.x1 + srcRod.x2) ) / 2;
465 double yoff = ( (boxRod.y1 + boxRod.y2) - (srcRod.y1 + srcRod.y2) ) / 2;
466 dstRod.x1 = srcRod.x1 + xoff;
467 dstRod.x2 = srcRod.x2 + xoff;
468 dstRod.y1 = srcRod.y1 + yoff;
469 dstRod.y2 = srcRod.y2 + yoff;
470 } else {
471 // identity, with RoD = dstRod for flip/flop/turn
472 srcRod = dstRod = boxRod;
473 }
474 } else if (resize == eResizeDistort) {
475 // easy case
476 dstRod.x2 = boxRod.x2;
477 dstRod.y2 = boxRod.y2;
478 } else if (resize == eResizeWidth) {
479 double scale = boxRod.x2 / srcw;
480 dstRod.x2 = boxRod.x2;
481 double dsth = srch * scale;
482 double offset = (center && boxFixed) ? (boxRod.y2 - dsth) / 2 : 0;
483 dstRod.y1 = offset;
484 dstRod.y2 = offset + dsth;
485 } else if (resize == eResizeHeight) {
486 double scale = boxRod.y2 / srch;
487 double dstw = srcw * scale;
488 double offset = (center && boxFixed) ? (boxRod.x2 - dstw) / 2 : 0;
489 dstRod.x1 = offset;
490 dstRod.x2 = offset + dstw;
491 dstRod.y2 = boxRod.y2;
492 }
493 assert( !Coords::rectIsEmpty(dstRod) );
494 *par = boxPAR;
495 const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
496 Coords::toPixelSub(dstRod, rsOne, *par, rect);
497 if (format && !boxFixed) {
498 Coords::toPixelNearest(dstRod, rsOne, *par, format);
499 }
500 } // ReformatPlugin::getOutputFormat
501
502 bool
getInverseTransformCanonical(const double time,const int,const double,const bool invert,Matrix3x3 * invtransform) const503 ReformatPlugin::getInverseTransformCanonical(const double time,
504 const int /*view*/,
505 const double /*amount*/,
506 const bool invert,
507 Matrix3x3* invtransform) const
508 {
509 if ( !_srcClip || !_srcClip->isConnected() ) {
510 return false;
511 }
512
513 OfxRectD srcRod, dstRod;
514 {
515 double par;
516 OfxRectD format;
517 const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
518 getInputFormat(time, &par, &format);
519 Coords::toCanonical(format, rsOne, par, &srcRod);
520 getOutputFormat(time, &par, &format, NULL);
521 Coords::toCanonical(format, rsOne, par, &dstRod);
522 }
523 bool flip = _flip->getValueAtTime(time);
524 bool flop = _flop->getValueAtTime(time);
525 bool turn = _turn->getValueAtTime(time);
526
527 // flip/flop.
528 // be careful, srcRod may be empty after this, because bounds are swapped,
529 // but this is used for transform computation
530 if (flip) {
531 std::swap(srcRod.y1, srcRod.y2);
532 }
533 if (flop) {
534 std::swap(srcRod.x1, srcRod.x2);
535 }
536 if (!invert) {
537 if ( (dstRod.x1 == dstRod.x2) ||
538 ( dstRod.y1 == dstRod.y2) ) {
539 return false;
540 }
541 // now, compute the transform from dstRod to srcRod
542 if (!turn) {
543 // simple case: no rotation
544 // x <- srcRod.x1 + (x - dstRod.x1) * (srcRod.x2 - srcRod.x1) / (dstRod.x2 - dstRod.x1)
545 // y <- srcRod.y1 + (y - dstRod.y1) * (srcRod.y2 - srcRod.y1) / (dstRod.y2 - dstRod.y1)
546 double ax = (srcRod.x2 - srcRod.x1) / (dstRod.x2 - dstRod.x1);
547 double ay = (srcRod.y2 - srcRod.y1) / (dstRod.y2 - dstRod.y1);
548 assert(ax == ax && ay == ay);
549 (*invtransform)(0,0) = ax; (*invtransform)(0,1) = 0; (*invtransform)(0,2) = srcRod.x1 - dstRod.x1 * ax;
550 (*invtransform)(1,0) = 0; (*invtransform)(1,1) = ay; (*invtransform)(1,2) = srcRod.y1 - dstRod.y1 * ay;
551 (*invtransform)(2,0) = 0; (*invtransform)(2,1) = 0; (*invtransform)(2,2) = 1.;
552 } else {
553 // rotation 90 degrees counterclockwise
554 // x <- srcRod.x1 + (y - dstRod.y1) * (srcRod.x2 - srcRod.x1) / (dstRod.y2 - dstRod.y1)
555 // y <- srcRod.y1 + (dstRod.x2 - x) * (srcRod.y2 - srcRod.y1) / (dstRod.x2 - dstRod.x1)
556 double ax = (srcRod.x2 - srcRod.x1) / (dstRod.y2 - dstRod.y1);
557 double ay = (srcRod.y2 - srcRod.y1) / (dstRod.x2 - dstRod.x1);
558 assert(ax == ax && ay == ay);
559 (*invtransform)(0,0) = 0; (*invtransform)(0,1) = ax; (*invtransform)(0,2) = srcRod.x1 - dstRod.y1 * ax;
560 (*invtransform)(1,0) = -ay; (*invtransform)(1,1) = 0; (*invtransform)(1,2) = srcRod.y1 + dstRod.x2 * ay;
561 (*invtransform)(2,0) = 0; (*invtransform)(2,1) = 0; (*invtransform)(2,2) = 1.;
562 }
563 } else { // invert
564 if ( (srcRod.x1 == srcRod.x2) ||
565 ( srcRod.y1 == srcRod.y2) ) {
566 return false;
567 }
568 // now, compute the transform from srcRod to dstRod
569 if (!turn) {
570 // simple case: no rotation
571 // x <- dstRod.x1 + (x - srcRod.x1) * (dstRod.x2 - dstRod.x1) / (srcRod.x2 - srcRod.x1)
572 // y <- dstRod.y1 + (y - srcRod.y1) * (dstRod.y2 - dstRod.y1) / (srcRod.y2 - srcRod.y1)
573 double ax = (dstRod.x2 - dstRod.x1) / (srcRod.x2 - srcRod.x1);
574 double ay = (dstRod.y2 - dstRod.y1) / (srcRod.y2 - srcRod.y1);
575 assert(ax == ax && ay == ay);
576 (*invtransform)(0,0) = ax; (*invtransform)(0,1) = 0; (*invtransform)(0,2) = dstRod.x1 - srcRod.x1 * ax;
577 (*invtransform)(1,0) = 0; (*invtransform)(1,1) = ay; (*invtransform)(1,2) = dstRod.y1 - srcRod.y1 * ay;
578 (*invtransform)(2,0) = 0; (*invtransform)(2,1) = 0; (*invtransform)(2,2) = 1.;
579 } else {
580 // rotation 90 degrees counterclockwise
581 // x <- dstRod.x1 + (srcRod.y2 - y) * (dstRod.x2 - dstRod.x1) / (srcRod.y2 - srcRod.y1)
582 // y <- dstRod.y1 + (x - srcRod.x1) * (dstRod.y2 - dstRod.y1) / (srcRod.x2 - srcRod.x1)
583 double ax = (dstRod.x2 - dstRod.x1) / (srcRod.y2 - srcRod.y1);
584 double ay = (dstRod.y2 - dstRod.y1) / (srcRod.x2 - srcRod.x1);
585 assert(ax == ax && ay == ay);
586 (*invtransform)(0,0) = 0; (*invtransform)(0,1) = -ax; (*invtransform)(0,2) = dstRod.x1 + srcRod.y2 * ax;
587 (*invtransform)(1,0) = ay; (*invtransform)(1,1) = 0; (*invtransform)(1,2) = dstRod.y1 - srcRod.x1 * ay;
588 (*invtransform)(2,0) = 0; (*invtransform)(2,1) = 0; (*invtransform)(2,2) = 1.;
589 }
590 }
591 assert((*invtransform)(0,0) == (*invtransform)(0,0) && (*invtransform)(0,1) == (*invtransform)(0,1) && (*invtransform)(0,2) == (*invtransform)(0,2) &&
592 (*invtransform)(1,0) == (*invtransform)(1,0) && (*invtransform)(1,1) == (*invtransform)(1,1) && (*invtransform)(1,2) == (*invtransform)(1,2) &&
593 (*invtransform)(2,0) == (*invtransform)(2,0) && (*invtransform)(2,1) == (*invtransform)(2,1) && (*invtransform)(2,2) == (*invtransform)(2,2));
594
595 return true;
596 } // ReformatPlugin::getInverseTransformCanonical
597
598 bool
getBoxValues(const double time,int * w,int * h,double * par,bool * boxFixed) const599 ReformatPlugin::getBoxValues(const double time, int *w, int *h, double *par, bool *boxFixed) const
600 {
601 ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
602 assert(w && h && par && boxFixed);
603
604 switch (type) {
605 case eReformatTypeToFormat: {
606 if (gHostIsNatron) {
607 //size & par have been set by natron with the Format choice extension
608 _formatBoxSize->getValue(*w, *h);
609 *par = _formatBoxPAR->getValue();
610 } else {
611 EParamFormat format = (EParamFormat)_format->getValue();
612 assert(0 <= (int)format && (int)format < eParamFormatCount);
613 getFormatResolution(format, w, h, par);
614 assert(*par != -1);
615 }
616 *boxFixed = true;
617 break;
618 }
619 case eReformatTypeToBox: {
620 _boxSize->getValue(*w, *h);
621 *par = _boxPAR->getValue();
622 *boxFixed = _boxFixed->getValue();
623 // nothing to do, the user sets the box
624 return false;
625 break;
626 }
627 case eReformatTypeScale: {
628 OfxPointD scale = _scale->getValue();
629 if ( _scaleUniform->getValue() ) {
630 scale.y = scale.x;
631 }
632 OfxRectD srcRod;
633 if (_srcClip && _srcClip->isConnected()) {
634 *par = _srcClip->getPixelAspectRatio();
635 srcRod = _srcClip->getRegionOfDefinition(time);
636 } else {
637 OfxPointD siz = getProjectSize();
638 OfxPointD off = getProjectOffset();
639 srcRod.x1 = off.x;
640 srcRod.x2 = off.x + siz.x;
641 srcRod.y1 = off.y;
642 srcRod.y2 = off.y + siz.y;
643 *par = getProjectPixelAspectRatio();
644 }
645 // scale the RoD
646 srcRod.x1 *= scale.x;
647 srcRod.x2 *= scale.x;
648 srcRod.y1 *= scale.y;
649 srcRod.y2 *= scale.y;
650 // round to the nearest pixel size
651 OfxRectI srcRodPixel;
652 OfxPointD rs = {1., 1.};
653 Coords::toPixelNearest(srcRod, rs, *par, &srcRodPixel);
654 *w = srcRodPixel.x2 - srcRodPixel.x1;
655 *h = srcRodPixel.y2 - srcRodPixel.y1;
656 *boxFixed = true;
657 }
658 }
659 return true; // box was set
660 } // ReformatPlugin::setBoxValues
661
662 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)663 ReformatPlugin::changedParam(const InstanceChangedArgs &args,
664 const std::string ¶mName)
665 {
666 // must clear persistent message, or render() is not called by Nuke
667 clearPersistentMessage();
668
669 if (paramName == kParamType) {
670 refreshVisibility();
671 }
672 if ( (paramName == kParamType) || (paramName == kParamFormat) || (paramName == kParamScale) || (paramName == kParamScaleUniform) ) {
673 int w, h;
674 double par;
675 bool boxFixed;
676 if ( getBoxValues(args.time, &w, &h, &par, &boxFixed) ) {
677 _boxSize->setValue(w, h);
678 _boxPAR->setValue(par);
679 _boxFixed->setValue(boxFixed);
680 }
681 return;
682 }
683
684 if (paramName == kParamPreserveBoundingBox) {
685 refreshDynamicProps();
686
687 return;
688 }
689
690 return Transform3x3Plugin::changedParam(args, paramName);
691 }
692
693 void
refreshDynamicProps()694 ReformatPlugin::refreshDynamicProps()
695 {
696 setCanTransform( _preserveBB->getValue() );
697 }
698
699 void
refreshVisibility()700 ReformatPlugin::refreshVisibility()
701 {
702 ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
703
704 switch (type) {
705 case eReformatTypeToFormat:
706 _format->setIsSecretAndDisabled(false);
707 _boxSize->setIsSecretAndDisabled(true);
708 _boxPAR->setIsSecretAndDisabled(true);
709 _boxFixed->setIsSecretAndDisabled(true);
710 _scale->setIsSecretAndDisabled(true);
711 _scaleUniform->setIsSecretAndDisabled(true);
712 break;
713
714 case eReformatTypeToBox:
715 _format->setIsSecretAndDisabled(true);
716 _boxSize->setIsSecretAndDisabled(false);
717 _boxPAR->setIsSecretAndDisabled(false);
718 _boxFixed->setIsSecretAndDisabled(false);
719 _scale->setIsSecretAndDisabled(true);
720 _scaleUniform->setIsSecretAndDisabled(true);
721 break;
722
723 case eReformatTypeScale:
724 _format->setIsSecretAndDisabled(true);
725 _boxSize->setIsSecretAndDisabled(true);
726 _boxPAR->setIsSecretAndDisabled(true);
727 _boxFixed->setIsSecretAndDisabled(true);
728 _scale->setIsSecretAndDisabled(false);
729 _scaleUniform->setIsSecretAndDisabled(false);
730 break;
731 }
732 _formatBoxSize->setIsSecretAndDisabled(true);
733 _formatBoxPAR->setIsSecretAndDisabled(true);
734 }
735
736 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)737 ReformatPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
738 {
739 ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
740 double par;
741 OfxRectD rect;
742 OfxRectI format;
743
744 getOutputFormat(0., &par, &rect, &format);
745
746 switch (type) {
747 case eReformatTypeToFormat:
748 case eReformatTypeToBox: {
749 //specific output PAR
750 clipPreferences.setPixelAspectRatio(*_dstClip, par);
751 break;
752 }
753
754 case eReformatTypeScale:
755 // don't change the pixel aspect ratio
756 break;
757 }
758 #ifdef OFX_EXTENSIONS_NATRON
759 clipPreferences.setOutputFormat(format);
760 #endif
761 }
762
763 mDeclarePluginFactory(ReformatPluginFactory, {ofxsThreadSuiteCheck();}, {});
764 void
describe(ImageEffectDescriptor & desc)765 ReformatPluginFactory::describe(ImageEffectDescriptor &desc)
766 {
767 // basic labels
768 desc.setLabel(kPluginName);
769 desc.setPluginGrouping(kPluginGrouping);
770 desc.setPluginDescription(getImageEffectHostDescription()->isNatron ? kPluginDescriptionNatron : kPluginDescription);
771 Transform3x3Describe(desc, false);
772 desc.setSupportsMultiResolution(true);
773 desc.setSupportsMultipleClipPARs(true);
774 gHostCanTransform = false;
775
776 #ifdef OFX_EXTENSIONS_NUKE
777 if (getImageEffectHostDescription()->canTransform) {
778 gHostCanTransform = true;
779 // say the effect implements getTransform(), even though transform concatenation
780 // may be disabled (see ReformatPlugin::refreshDynamicProps())
781 desc.setCanTransform(true);
782 }
783 #endif
784 #ifdef OFX_EXTENSIONS_NATRON
785 if (getImageEffectHostDescription()->isNatron) {
786 gHostIsNatron = true;
787 gHostSupportsFormat = true;
788 }
789 #endif
790 }
791
792 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)793 ReformatPluginFactory::describeInContext(ImageEffectDescriptor &desc,
794 ContextEnum context)
795 {
796 // make some pages and to things in
797 PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, false);
798
799 {
800 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamUseRoD);
801 param->setLabel(kParamUseRoDLabel);
802 param->setHint(kParamUseRoDHint);
803 // for now, only Natron supports OFX format extension
804 param->setEnabled(gHostSupportsFormat);
805 param->setDefault(!gHostSupportsFormat);
806 param->setAnimates(false);
807 if (page) {
808 page->addChild(*param);
809 }
810 }
811
812 // type
813 {
814 ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamType);
815 param->setLabel(kParamTypeLabel);
816 param->setHint(kParamTypeHint);
817 assert(param->getNOptions() == eReformatTypeToFormat);
818 param->appendOption(kParamTypeOptionToFormat);
819 assert(param->getNOptions() == eReformatTypeToBox);
820 param->appendOption(kParamTypeOptionToBox);
821 assert(param->getNOptions() == eReformatTypeScale);
822 param->appendOption(kParamTypeOptionScale);
823 param->setDefault(0);
824 param->setAnimates(false);
825 desc.addClipPreferencesSlaveParam(*param);
826 if (page) {
827 page->addChild(*param);
828 }
829 }
830
831 // format
832 {
833 ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFormat);
834 param->setLabel(kParamFormatLabel);
835 param->setHint(kParamFormatHint);
836 assert(param->getNOptions() == eParamFormatPCVideo);
837 param->appendOption(kParamFormatPCVideoLabel, "", kParamFormatPCVideo);
838 assert(param->getNOptions() == eParamFormatNTSC);
839 param->appendOption(kParamFormatNTSCLabel, "", kParamFormatNTSC);
840 assert(param->getNOptions() == eParamFormatPAL);
841 param->appendOption(kParamFormatPALLabel, "", kParamFormatPAL);
842 assert(param->getNOptions() == eParamFormatNTSC169);
843 param->appendOption(kParamFormatNTSC169Label, "", kParamFormatNTSC169);
844 assert(param->getNOptions() == eParamFormatPAL169);
845 param->appendOption(kParamFormatPAL169Label, "", kParamFormatPAL169);
846 assert(param->getNOptions() == eParamFormatHD720);
847 param->appendOption(kParamFormatHD720Label, "", kParamFormatHD720);
848 assert(param->getNOptions() == eParamFormatHD);
849 param->appendOption(kParamFormatHDLabel, "", kParamFormatHD);
850 assert(param->getNOptions() == eParamFormatUHD4K);
851 param->appendOption(kParamFormatUHD4KLabel, "", kParamFormatUHD4K);
852 assert(param->getNOptions() == eParamFormat1kSuper35);
853 param->appendOption(kParamFormat1kSuper35Label, "", kParamFormat1kSuper35);
854 assert(param->getNOptions() == eParamFormat1kCinemascope);
855 param->appendOption(kParamFormat1kCinemascopeLabel, "", kParamFormat1kCinemascope);
856 assert(param->getNOptions() == eParamFormat2kSuper35);
857 param->appendOption(kParamFormat2kSuper35Label, "", kParamFormat2kSuper35);
858 assert(param->getNOptions() == eParamFormat2kCinemascope);
859 param->appendOption(kParamFormat2kCinemascopeLabel, "", kParamFormat2kCinemascope);
860 assert(param->getNOptions() == eParamFormat2kDCP);
861 param->appendOption(kParamFormat2kDCPLabel, "", kParamFormat2kDCP);
862 assert(param->getNOptions() == eParamFormat4kSuper35);
863 param->appendOption(kParamFormat4kSuper35Label, "", kParamFormat4kSuper35);
864 assert(param->getNOptions() == eParamFormat4kCinemascope);
865 param->appendOption(kParamFormat4kCinemascopeLabel, "", kParamFormat4kCinemascope);
866 assert(param->getNOptions() == eParamFormat4kDCP);
867 param->appendOption(kParamFormat4kDCPLabel, "", kParamFormat4kDCP);
868 assert(param->getNOptions() == eParamFormatSquare256);
869 param->appendOption(kParamFormatSquare256Label, "", kParamFormatSquare256);
870 assert(param->getNOptions() == eParamFormatSquare512);
871 param->appendOption(kParamFormatSquare512Label, "", kParamFormatSquare512);
872 assert(param->getNOptions() == eParamFormatSquare1k);
873 param->appendOption(kParamFormatSquare1kLabel, "", kParamFormatSquare1k);
874 assert(param->getNOptions() == eParamFormatSquare2k);
875 param->appendOption(kParamFormatSquare2kLabel, "", kParamFormatSquare2k);
876 param->setDefault(kParamFormatDefault);
877 param->setHint(kParamFormatHint);
878 param->setAnimates(false);
879 desc.addClipPreferencesSlaveParam(*param);
880 if (page) {
881 page->addChild(*param);
882 }
883 }
884
885 {
886 Int2DParamDescriptor* param = desc.defineInt2DParam(kParamFormatBoxSize);
887 param->setLabel(kParamFormatBoxSizeLabel);
888 param->setHint(kParamFormatBoxSizeHint);
889 param->setDefault(200, 200);
890 param->setIsSecretAndDisabled(true); // secret Natron-specific param
891 param->setAnimates(false);
892 desc.addClipPreferencesSlaveParam(*param);
893 if (page) {
894 page->addChild(*param);
895 }
896 }
897
898 {
899 DoubleParamDescriptor* param = desc.defineDoubleParam(kParamFormatBoxPAR);
900 param->setLabel(kParamFormatBoxPARLabel);
901 param->setHint(kParamFormatBoxPARHint);
902 param->setRange(0., 10);
903 param->setDisplayRange(0.5, 2.);
904 param->setDefault(1.);
905 param->setIsSecretAndDisabled(true); // secret Natron-specific param
906 param->setAnimates(false);
907 desc.addClipPreferencesSlaveParam(*param);
908 if (page) {
909 page->addChild(*param);
910 }
911 }
912
913
914 {
915 Int2DParamDescriptor* param = desc.defineInt2DParam(kParamBoxSize);
916 param->setLabel(kParamBoxSizeLabel);
917 param->setHint(kParamBoxSizeHint);
918 param->setDefault(200, 200);
919 param->setLayoutHint(eLayoutHintNoNewLine, 1);
920 param->setAnimates(false);
921 desc.addClipPreferencesSlaveParam(*param);
922 if (page) {
923 page->addChild(*param);
924 }
925 }
926 {
927 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamBoxFixed);
928 param->setLabel(kParamBoxFixedLabel);
929 param->setHint(kParamBoxFixedHint);
930 param->setDefault(false);
931 param->setAnimates(false);
932 desc.addClipPreferencesSlaveParam(*param);
933 if (page) {
934 page->addChild(*param);
935 }
936 }
937
938 {
939 DoubleParamDescriptor* param = desc.defineDoubleParam(kParamBoxPAR);
940 param->setLabel(kParamBoxPARLabel);
941 param->setHint(kParamBoxPARHint);
942 param->setRange(0., 10);
943 param->setDisplayRange(0.5, 2.);
944 param->setDefault(1.);
945 param->setAnimates(false);
946 desc.addClipPreferencesSlaveParam(*param);
947 if (page) {
948 page->addChild(*param);
949 }
950 }
951
952 // scale
953 {
954 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamScale);
955 param->setLabel(kParamScaleLabel);
956 param->setHint(kParamScaleHint);
957 param->setDoubleType(eDoubleTypeScale);
958 //param->setDimensionLabels("w","h");
959 param->setDefault(1, 1);
960 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX);
961 param->setDisplayRange(0.1, 0.1, 10, 10);
962 param->setIncrement(0.01);
963 param->setUseHostNativeOverlayHandle(false);
964 param->setLayoutHint(eLayoutHintNoNewLine, 1);
965 param->setAnimates(false);
966 desc.addClipPreferencesSlaveParam(*param);
967 if (page) {
968 page->addChild(*param);
969 }
970 }
971
972 // scaleUniform
973 {
974 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamScaleUniform);
975 param->setLabel(kParamScaleUniformLabel);
976 param->setHint(kParamScaleUniformHint);
977 // uniform parameter is false by default on Natron
978 // https://github.com/MrKepzie/Natron/issues/1204
979 param->setDefault(!getImageEffectHostDescription()->isNatron);
980 param->setLayoutHint(eLayoutHintDivider);
981 param->setAnimates(false);
982 desc.addClipPreferencesSlaveParam(*param);
983 if (page) {
984 page->addChild(*param);
985 }
986 }
987
988 // resize
989 {
990 ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamResize);
991 param->setLabel(kParamResizeLabel);
992 param->setHint(kParamResizeHint);
993 assert(param->getNOptions() == eResizeNone);
994 param->appendOption(kParamResizeOptionNone);
995 assert(param->getNOptions() == eResizeWidth);
996 param->appendOption(kParamResizeOptionWidth);
997 assert(param->getNOptions() == eResizeHeight);
998 param->appendOption(kParamResizeOptionHeight);
999 assert(param->getNOptions() == eResizeFit);
1000 param->appendOption(kParamResizeOptionFit);
1001 assert(param->getNOptions() == eResizeFill);
1002 param->appendOption(kParamResizeOptionFill);
1003 assert(param->getNOptions() == eResizeDistort);
1004 param->appendOption(kParamResizeOptionDistort);
1005 param->setDefault( (int)eResizeWidth );
1006 param->setAnimates(false);
1007 desc.addClipPreferencesSlaveParam(*param);
1008 if (page) {
1009 page->addChild(*param);
1010 }
1011 }
1012
1013 // center
1014 {
1015 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamReformatCenter);
1016 param->setLabel(kParamReformatCenterLabel);
1017 param->setHint(kParamReformatCenterHint);
1018 param->setDefault(true);
1019 param->setAnimates(false);
1020 desc.addClipPreferencesSlaveParam(*param);
1021 param->setLayoutHint(eLayoutHintNoNewLine, 1);
1022 if (page) {
1023 page->addChild(*param);
1024 }
1025 }
1026
1027 // flip
1028 {
1029 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamFlip);
1030 param->setLabel(kParamFlipLabel);
1031 param->setHint(kParamFlipHint);
1032 param->setDefault(false);
1033 param->setAnimates(false);
1034 param->setLayoutHint(eLayoutHintNoNewLine, 1);
1035 if (page) {
1036 page->addChild(*param);
1037 }
1038 }
1039
1040 // flop
1041 {
1042 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamFlop);
1043 param->setLabel(kParamFlopLabel);
1044 param->setHint(kParamFlopHint);
1045 param->setDefault(false);
1046 param->setAnimates(false);
1047 param->setLayoutHint(eLayoutHintNoNewLine, 1);
1048 if (page) {
1049 page->addChild(*param);
1050 }
1051 }
1052
1053 // turn
1054 {
1055 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTurn);
1056 param->setLabel(kParamTurnLabel);
1057 param->setHint(kParamTurnHint);
1058 param->setDefault(false);
1059 param->setAnimates(false);
1060 desc.addClipPreferencesSlaveParam(*param);
1061 param->getPropertySet().propSetInt(kOfxParamPropLayoutPadWidth, 1, false);
1062 if (page) {
1063 page->addChild(*param);
1064 }
1065 }
1066
1067 // Preserve bounding box
1068 {
1069 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPreserveBoundingBox);
1070 param->setLabel(kParamPreserveBoundingBoxLabel);
1071 param->setHint(kParamPreserveBoundingBoxHint);
1072 param->setDefault(false);
1073 param->setAnimates(false);
1074 desc.addClipPreferencesSlaveParam(*param);
1075 if (page) {
1076 page->addChild(*param);
1077 }
1078 }
1079
1080 // clamp, filter, black outside
1081 ofxsFilterDescribeParamsInterpolate2D(desc, page, /*blackOutsideDefault*/ false);
1082 } // ReformatPluginFactory::describeInContext
1083
1084 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1085 ReformatPluginFactory::createInstance(OfxImageEffectHandle handle,
1086 ContextEnum /*context*/)
1087 {
1088 return new ReformatPlugin(handle);
1089 }
1090
1091 static ReformatPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1092 mRegisterPluginFactoryInstance(p)
1093
1094 OFXS_NAMESPACE_ANONYMOUS_EXIT
1095