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  * OIIOResize plugin.
21  * Resize images using OIIO.
22  */
23 
24 #include <cfloat> // DBL_MAX
25 #include <limits>
26 #include <algorithm>
27 
28 #include "ofxsMacros.h"
29 
30 #include "OIIOGlobal.h"
31 GCC_DIAG_OFF(unused-parameter)
32 /*
33    unfortunately, OpenImageIO/imagebuf.h includes OpenImageIO/thread.h,
34    which includes boost/thread.hpp,
35    which includes boost/system/error_code.hpp,
36    which requires the library boost_system to get the symbol boost::system::system_category().
37 
38    the following define prevents including error_code.hpp, which is not used anyway.
39  */
40 #define OPENIMAGEIO_THREAD_H
41 #include <OpenImageIO/imagebuf.h>
42 #include <OpenImageIO/imagebufalgo.h>
43 #include <OpenImageIO/filter.h>
44 GCC_DIAG_ON(unused-parameter)
45 
46 #include "ofxsProcessing.H"
47 #include "ofxsThreadSuite.h"
48 #include "ofxsCopier.h"
49 #include "ofxsFormatResolution.h"
50 #include "ofxsCoords.h"
51 
52 #include "IOUtility.h"
53 
54 using namespace OFX;
55 
56 using std::string;
57 
58 OFXS_NAMESPACE_ANONYMOUS_ENTER
59 
60 #define kPluginName "ResizeOIIO"
61 #define kPluginGrouping "Transform"
62 #define kPluginDescription  "Resize input stream, using OpenImageIO.\n" \
63     "Note that only full images can be rendered, so it may be slower for interactive editing than the Reformat plugin.\n" \
64     "However, the rendering algorithms are different between Reformat and Resize: Resize applies 1-dimensional filters in the horizontal and vertical directins, whereas Reformat resamples the image, so in some cases this plugin may give more visually pleasant results than Reformat.\n" \
65     "This plugin does not concatenate transforms (as opposed to Reformat)."
66 
67 #define kPluginIdentifier "fr.inria.openfx.OIIOResize"
68 // History:
69 // version 1.0: initial version
70 // version 2.0: add the "default" filter, which is blackman-harris when increasing resolution, lanczos3 when decreasing resolution
71 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
72 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
73 
74 #define kSupportsTiles 0
75 #define kSupportsMultiResolution 1
76 #define kSupportsRenderScale 1
77 #define kRenderThreadSafety eRenderFullySafe
78 
79 #define kParamType "type"
80 #define kParamTypeLabel "Type"
81 #define kParamTypeHint "Format: Converts between formats, the image is resized to fit in the target format. " \
82     "Size: Scales to fit into a box of a given width and height. " \
83     "Scale: Scales the image."
84 #define kParamTypeOptionFormat "Format", "", "format"
85 #define kParamTypeOptionSize "Size", "", "size"
86 #define kParamTypeOptionScale "Scale", "", "scale"
87 
88 enum ResizeTypeEnum
89 {
90     eResizeTypeFormat = 0,
91     eResizeTypeSize,
92     eResizeTypeScale,
93 };
94 
95 #define kParamFormat "format"
96 #define kParamFormatLabel "Format"
97 #define kParamFormatHint "The output format"
98 
99 #define kParamSize "size"
100 #define kParamSizeLabel "Size"
101 #define kParamSizeHint "The output size"
102 
103 #define kParamPreservePAR "preservePAR"
104 #define kParamPreservePARLabel "Preserve PAR"
105 #define kParamPreservePARHint "Preserve Pixel Aspect Ratio (PAR). When checked, one direction will be clipped."
106 
107 #define kParamScale "scale"
108 #define kParamScaleLabel "Scale"
109 #define kParamScaleHint "The scale factor to apply to the image."
110 
111 #define kParamFilter "filter"
112 #define kParamFilterLabel "Filter"
113 #define kParamFilterHint "The filter used to resize. Lanczos3 is great for downscaling and blackman-harris is great for upscaling."
114 #define kParamFilterOptionImpulse "Impulse", "No interpolation.", "impulse"
115 #define kParamFilterOptionDefault "Default", "blackman-harris when increasing resolution, lanczos3 when decreasing resolution.", "default"
116 
117 #define kSrcClipChanged "srcClipChanged"
118 
119 OIIO_NAMESPACE_USING
120 
121 class OIIOResizePlugin
122     : public ImageEffect
123 {
124 public:
125 
126     OIIOResizePlugin(OfxImageEffectHandle handle);
127 
128     virtual ~OIIOResizePlugin();
129 
130     /* Override the render */
131     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
132 
133     /* override is identity */
134     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
135 
136     /* override changedParam */
137     virtual void changedParam(const InstanceChangedArgs &args, const string &paramName) OVERRIDE FINAL;
138     virtual void changedClip(const InstanceChangedArgs &args, const string &clipName) OVERRIDE;
139 
140     /* override changed clip */
141     //virtual void changedClip(const InstanceChangedArgs &args, const string &clipName) OVERRIDE FINAL;
142 
143     // override the rod call
144     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
145 
146     // override the roi call
147     virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;
148     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
149 
150 private:
151 
152     template <typename PIX, int nComps>
153     void renderInternal(const RenderArguments &args, TypeDesc srcType, const Image* srcImg, TypeDesc dstType, Image* dstImg);
154 
155     void fillWithBlack(PixelProcessorFilterBase & processor,
156                        const OfxRectI &renderWindow,
157                        void *dstPixelData,
158                        const OfxRectI& dstBounds,
159                        PixelComponentEnum dstPixelComponents,
160                        int dstPixelComponentCount,
161                        BitDepthEnum dstPixelDepth,
162                        int dstRowBytes);
163 
164     // do not need to delete these, the ImageEffect is managing them for us
165     Clip *_dstClip;
166     Clip *_srcClip;
167     ChoiceParam *_type;
168     ChoiceParam *_format;
169     ChoiceParam *_filter;
170     Int2DParam *_size;
171     Double2DParam *_scale;
172     BooleanParam *_preservePAR;
173     BooleanParam* _srcClipChanged; // set to true the first time the user connects src
174 };
175 
OIIOResizePlugin(OfxImageEffectHandle handle)176 OIIOResizePlugin::OIIOResizePlugin(OfxImageEffectHandle handle)
177     : ImageEffect(handle)
178     , _dstClip(NULL)
179     , _srcClip(NULL)
180     , _type(NULL)
181     , _format(NULL)
182     , _filter(NULL)
183     , _size(NULL)
184     , _scale(NULL)
185     , _preservePAR(NULL)
186     , _srcClipChanged(NULL)
187 {
188     _dstClip = fetchClip(kOfxImageEffectOutputClipName);
189     assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGBA ||
190                          _dstClip->getPixelComponents() == ePixelComponentRGB ||
191                          _dstClip->getPixelComponents() == ePixelComponentAlpha) );
192     _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
193     assert( (!_srcClip && getContext() == eContextGenerator) ||
194             ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() == ePixelComponentRGBA ||
195                            _srcClip->getPixelComponents() == ePixelComponentRGB ||
196                            _srcClip->getPixelComponents() == ePixelComponentAlpha) ) );
197 
198     _type = fetchChoiceParam(kParamType);
199     _format = fetchChoiceParam(kParamFormat);
200     _filter = fetchChoiceParam(kParamFilter);
201     _size = fetchInt2DParam(kParamSize);
202     _scale = fetchDouble2DParam(kParamScale);
203     _preservePAR = fetchBooleanParam(kParamPreservePAR);
204     _srcClipChanged = fetchBooleanParam(kSrcClipChanged);
205 
206     assert(_type && _format &&  _filter && _size && _scale && _preservePAR);
207 
208     int type_i;
209     _type->getValue(type_i);
210     ResizeTypeEnum type = (ResizeTypeEnum)type_i;
211     switch (type) {
212     case eResizeTypeFormat:
213         //specific output format
214         _size->setIsSecretAndDisabled(true);
215         _preservePAR->setIsSecretAndDisabled(true);
216         _scale->setIsSecretAndDisabled(true);
217         _format->setIsSecretAndDisabled(false);
218         break;
219 
220     case eResizeTypeSize:
221         //size
222         _size->setIsSecretAndDisabled(false);
223         _preservePAR->setIsSecretAndDisabled(false);
224         _scale->setIsSecretAndDisabled(true);
225         _format->setIsSecretAndDisabled(true);
226         break;
227 
228     case eResizeTypeScale:
229         //scaled
230         _size->setIsSecretAndDisabled(true);
231         _preservePAR->setIsSecretAndDisabled(true);
232         _scale->setIsSecretAndDisabled(false);
233         _format->setIsSecretAndDisabled(true);
234         break;
235     }
236 
237     initOIIOThreads();
238 }
239 
~OIIOResizePlugin()240 OIIOResizePlugin::~OIIOResizePlugin()
241 {
242 }
243 
244 /* Override the render */
245 void
render(const RenderArguments & args)246 OIIOResizePlugin::render(const RenderArguments &args)
247 {
248     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
249     if ( !dst.get() ) {
250         throwSuiteStatusException(kOfxStatFailed);
251 
252         return;
253     }
254     if ( (dst->getRenderScale().x != args.renderScale.x) ||
255          ( dst->getRenderScale().y != args.renderScale.y) ||
256          ( dst->getField() != args.fieldToRender) ) {
257         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
258         throwSuiteStatusException(kOfxStatFailed);
259 
260         return;
261     }
262 
263     auto_ptr<const Image> src( _srcClip->fetchImage(args.time) );
264     if ( src.get() ) {
265         BitDepthEnum dstBitDepth       = dst->getPixelDepth();
266         PixelComponentEnum dstComponents  = dst->getPixelComponents();
267         assert(dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentRGBA ||
268                dstComponents == ePixelComponentAlpha);
269 
270         BitDepthEnum srcBitDepth      = src->getPixelDepth();
271         PixelComponentEnum srcComponents = src->getPixelComponents();
272         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
273             throwSuiteStatusException(kOfxStatErrImageFormat);
274 
275             return;
276         }
277 
278         if (dstComponents == ePixelComponentRGBA) {
279             switch (dstBitDepth) {
280             case eBitDepthUByte: {
281                 renderInternal<unsigned char, 4>( args, TypeDesc::UCHAR, src.get(), TypeDesc::UCHAR, dst.get() );
282                 break;
283             }
284             case eBitDepthUShort: {
285                 renderInternal<unsigned short, 4>( args, TypeDesc::USHORT, src.get(), TypeDesc::USHORT, dst.get() );
286                 break;
287             }
288             case eBitDepthFloat: {
289                 renderInternal<float, 4>( args, TypeDesc::FLOAT, src.get(), TypeDesc::FLOAT, dst.get() );
290                 break;
291             }
292             default:
293                 throwSuiteStatusException(kOfxStatErrUnsupported);
294 
295                 return;
296             }
297         } else if (dstComponents == ePixelComponentRGB) {
298             switch (dstBitDepth) {
299             case eBitDepthUByte: {
300                 renderInternal<unsigned char, 3>( args, TypeDesc::UCHAR, src.get(), TypeDesc::UCHAR, dst.get() );
301                 break;
302             }
303             case eBitDepthUShort: {
304                 renderInternal<unsigned short, 3>( args, TypeDesc::USHORT, src.get(), TypeDesc::USHORT, dst.get() );
305                 break;
306             }
307             case eBitDepthFloat: {
308                 renderInternal<float, 3>( args, TypeDesc::FLOAT, src.get(), TypeDesc::FLOAT, dst.get() );
309                 break;
310             }
311             default:
312                 throwSuiteStatusException(kOfxStatErrUnsupported);
313 
314                 return;
315             }
316         } else {
317             assert(dstComponents == ePixelComponentAlpha);
318             switch (dstBitDepth) {
319             case eBitDepthUByte: {
320                 renderInternal<unsigned char, 1>( args, TypeDesc::UCHAR, src.get(), TypeDesc::UCHAR, dst.get() );
321                 break;
322             }
323             case eBitDepthUShort: {
324                 renderInternal<unsigned short, 1>( args, TypeDesc::USHORT, src.get(), TypeDesc::USHORT, dst.get() );
325                 break;
326             }
327             case eBitDepthFloat: {
328                 renderInternal<float, 1>( args, TypeDesc::FLOAT, src.get(), TypeDesc::FLOAT, dst.get() );
329                 break;
330             }
331             default:
332                 throwSuiteStatusException(kOfxStatErrUnsupported);
333 
334                 return;
335             }
336         }
337     } else { //!src.get()
338         void* dstPixelData;
339         OfxRectI dstBounds;
340         PixelComponentEnum dstComponents;
341         BitDepthEnum dstBitDepth;
342         int dstRowBytes;
343         getImageData(dst.get(), &dstPixelData, &dstBounds, &dstComponents, &dstBitDepth, &dstRowBytes);
344         int dstPixelComponentCount = dst->getPixelComponentCount();
345 
346         assert(dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentRGBA ||
347                dstComponents == ePixelComponentAlpha);
348 
349         if (dstComponents == ePixelComponentRGBA) {
350             switch (dstBitDepth) {
351             case eBitDepthUByte: {
352                 BlackFiller<unsigned char> proc(*this, 4);
353                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
354                 break;
355             }
356             case eBitDepthUShort: {
357                 BlackFiller<unsigned short> proc(*this, 4);
358                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
359                 break;
360             }
361             case eBitDepthFloat: {
362                 BlackFiller<float> proc(*this, 4);
363                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
364                 break;
365             }
366             default:
367                 throwSuiteStatusException(kOfxStatErrUnsupported);
368 
369                 return;
370             }
371         } else if (dstComponents == ePixelComponentRGB) {
372             switch (dstBitDepth) {
373             case eBitDepthUByte: {
374                 BlackFiller<unsigned char> proc(*this, 3);
375                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
376                 break;
377             }
378             case eBitDepthUShort: {
379                 BlackFiller<unsigned short> proc(*this, 3);
380                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
381                 break;
382             }
383             case eBitDepthFloat: {
384                 BlackFiller<float> proc(*this, 3);
385                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
386                 break;
387             }
388             default:
389                 throwSuiteStatusException(kOfxStatErrUnsupported);
390 
391                 return;
392             }
393         } else {
394             assert(dstComponents == ePixelComponentAlpha);
395             switch (dstBitDepth) {
396             case eBitDepthUByte: {
397                 BlackFiller<unsigned char> proc(*this, 1);
398                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
399                 break;
400             }
401             case eBitDepthUShort: {
402                 BlackFiller<unsigned short> proc(*this, 1);
403                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
404                 break;
405             }
406             case eBitDepthFloat: {
407                 BlackFiller<float> proc(*this, 1);
408                 fillWithBlack(proc, args.renderWindow, dstPixelData, dstBounds, dstComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
409                 break;
410             }
411             default:
412                 throwSuiteStatusException(kOfxStatErrUnsupported);
413 
414                 return;
415             }
416         }
417     }
418 } // OIIOResizePlugin::render
419 
420 template <typename PIX, int nComps>
421 void
renderInternal(const RenderArguments &,TypeDesc srcType,const Image * srcImg,TypeDesc dstType,Image * dstImg)422 OIIOResizePlugin::renderInternal(const RenderArguments & /*args*/,
423                                  TypeDesc srcType,
424                                  const Image* srcImg,
425                                  TypeDesc dstType,
426                                  Image* dstImg)
427 {
428     ImageSpec srcSpec(srcType);
429     const OfxRectI srcBounds = srcImg->getBounds();
430 
431     srcSpec.x = srcBounds.x1;
432     srcSpec.y = srcBounds.y1;
433     srcSpec.width = srcBounds.x2 - srcBounds.x1;
434     srcSpec.height = srcBounds.y2 - srcBounds.y1;
435     srcSpec.nchannels = nComps;
436     srcSpec.full_x = srcSpec.x;
437     srcSpec.full_y = srcSpec.y;
438     srcSpec.full_width = srcSpec.width;
439     srcSpec.full_height = srcSpec.height;
440     srcSpec.default_channel_names();
441 
442     const ImageBuf srcBuf( "src", srcSpec, const_cast<void*>( srcImg->getPixelAddress(srcBounds.x1, srcBounds.y1) ) );
443 
444 
445     ///This code assumes that the dstImg has the target size hence that we don't support tiles
446     const OfxRectI dstBounds = dstImg->getBounds();
447     ImageSpec dstSpec(dstType);
448     dstSpec.x = dstBounds.x1;
449     dstSpec.y = dstBounds.y1;
450     dstSpec.width = dstBounds.x2 - dstBounds.x1;
451     dstSpec.height = dstBounds.y2 - dstBounds.y1;
452     dstSpec.nchannels = nComps;
453     dstSpec.full_x = dstSpec.x;
454     dstSpec.full_y = dstSpec.y;
455     dstSpec.full_width = dstSpec.width;
456     dstSpec.full_height = dstSpec.height;
457     dstSpec.default_channel_names();
458 
459     ImageBuf dstBuf( "dst", dstSpec, dstImg->getPixelAddress(dstBounds.x1, dstBounds.y1) );
460     int filter;
461     _filter->getValue(filter);
462 
463     if (filter == 0) {
464         ///Use nearest neighboor
465         if ( !ImageBufAlgo::resample( dstBuf, srcBuf, /*interpolate*/ false, ROI::All(), MultiThread::getNumCPUs() ) ) {
466             setPersistentMessage( Message::eMessageError, "", dstBuf.geterror() );
467         }
468     } else {
469         assert(srcSpec.full_width && srcSpec.full_height);
470         float wratio = float(dstSpec.full_width) / float(srcSpec.full_width);
471         float hratio = float(dstSpec.full_height) / float(srcSpec.full_height);
472         const int num_filters = Filter2D::num_filters();
473         ///interpolate using the selected filter
474         FilterDesc fd;
475         filter -= 1;
476         if (filter < num_filters) {
477             Filter2D::get_filterdesc(filter, &fd);
478         } else {
479             string filtername;
480             // "default" filter
481             // No filter name supplied -- pick a good default
482             // see imgbufalgo_xform.cpp:477
483             if (wratio > 1.0f || hratio > 1.0f) {
484                 filtername = "blackman-harris";
485             } else {
486                 filtername = "lanczos3";
487             }
488             filter = 0;
489             Filter2D::get_filterdesc(filter, &fd);
490             while (fd.name != filtername) {
491                 ++filter;
492                 Filter2D::get_filterdesc(filter, &fd);
493             }
494         }
495         // older versions of OIIO 1.2 don't have ImageBufAlgo::resize(dstBuf, srcBuf, fd.name, fd.width)
496         float w = fd.width * std::max(1.0f, wratio);
497         float h = fd.width * std::max(1.0f, hratio);
498         auto_ptr<Filter2D> filter( Filter2D::create(fd.name, w, h) );
499 
500         if ( !ImageBufAlgo::resize( dstBuf, srcBuf, filter.get(), ROI::All(), MultiThread::getNumCPUs() ) ) {
501             setPersistentMessage( Message::eMessageError, "", dstBuf.geterror() );
502         }
503     }
504 } // OIIOResizePlugin::renderInternal
505 
506 void
fillWithBlack(PixelProcessorFilterBase & processor,const OfxRectI & renderWindow,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstPixelDepth,int dstRowBytes)507 OIIOResizePlugin::fillWithBlack(PixelProcessorFilterBase & processor,
508                                 const OfxRectI &renderWindow,
509                                 void *dstPixelData,
510                                 const OfxRectI& dstBounds,
511                                 PixelComponentEnum dstPixelComponents,
512                                 int dstPixelComponentCount,
513                                 BitDepthEnum dstPixelDepth,
514                                 int dstRowBytes)
515 {
516     // set the images
517     processor.setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstPixelDepth, dstRowBytes);
518 
519     // set the render window
520     processor.setRenderWindow(renderWindow);
521 
522     // Call the base class process member, this will call the derived templated process code
523     processor.process();
524 }
525 
526 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)527 OIIOResizePlugin::isIdentity(const IsIdentityArguments &args,
528                              Clip * &identityClip,
529                              double & /*identityTime*/
530                              , int& /*view*/, std::string& /*plane*/)
531 {
532     int type_i;
533     _type->getValue(type_i);
534     ResizeTypeEnum type = (ResizeTypeEnum)type_i;
535     switch (type) {
536     case eResizeTypeFormat: {
537         OfxRectD srcRoD = _srcClip->getRegionOfDefinition(args.time);
538         double srcPAR = _srcClip->getPixelAspectRatio();
539         int index;
540         _format->getValue(index);
541         double par = 1.;
542         int w = 0, h = 0;
543         getFormatResolution( (EParamFormat)index, &w, &h, &par );
544         if (srcPAR != par) {
545             return false;
546         }
547         OfxPointD rsOne;
548         rsOne.x = rsOne.y = 1.;
549         OfxRectI srcRoDPixel;
550         Coords::toPixelEnclosing(srcRoD, rsOne, srcPAR, &srcRoDPixel);
551         if ( ( srcRoDPixel.x1 == 0) && ( srcRoDPixel.y1 == 0) && ( srcRoDPixel.x2 == (int)w) && ( srcRoD.y2 == (int)h) ) {
552             identityClip = _srcClip;
553 
554             return true;
555         }
556 
557         return false;
558         break;
559     }
560     case eResizeTypeSize: {
561         OfxRectD srcRoD = _srcClip->getRegionOfDefinition(args.time);
562         double srcPAR = _srcClip->getPixelAspectRatio();
563         OfxPointD rsOne;
564         rsOne.x = rsOne.y = 1.;
565         OfxRectI srcRoDPixel;
566         Coords::toPixelEnclosing(srcRoD, rsOne, srcPAR, &srcRoDPixel);
567 
568         int w, h;
569         _size->getValue(w, h);
570         if ( ( srcRoDPixel.x1 == 0) && ( srcRoDPixel.y1 == 0) && ( srcRoDPixel.x2 == w) && ( srcRoDPixel.y2 == h) ) {
571             identityClip = _srcClip;
572 
573             return true;
574         }
575 
576         return false;
577         break;
578     }
579     case eResizeTypeScale: {
580         double sx, sy;
581         _scale->getValue(sx, sy);
582         if ( ( sx == 1.) && ( sy == 1.) ) {
583             identityClip = _srcClip;
584 
585             return true;
586         }
587 
588         return false;
589         break;
590     }
591     } // switch
592 
593     return false;
594 } // OIIOResizePlugin::isIdentity
595 
596 void
changedParam(const InstanceChangedArgs &,const string & paramName)597 OIIOResizePlugin::changedParam(const InstanceChangedArgs & /*args*/,
598                                const string &paramName)
599 {
600     // must clear persistent message, or render() is not called by Nuke after an error
601     clearPersistentMessage();
602     if (paramName == kParamType) {
603         int type_i;
604         _type->getValue(type_i);
605         ResizeTypeEnum type = (ResizeTypeEnum)type_i;
606         switch (type) {
607         case eResizeTypeFormat:
608             //specific output format
609             _size->setIsSecretAndDisabled(true);
610             _preservePAR->setIsSecretAndDisabled(true);
611             _scale->setIsSecretAndDisabled(true);
612             _format->setIsSecretAndDisabled(false);
613             break;
614 
615         case eResizeTypeSize:
616             //size
617             _size->setIsSecretAndDisabled(false);
618             _preservePAR->setIsSecretAndDisabled(false);
619             _scale->setIsSecretAndDisabled(true);
620             _format->setIsSecretAndDisabled(true);
621             break;
622 
623         case eResizeTypeScale:
624             //scaled
625             _size->setIsSecretAndDisabled(true);
626             _preservePAR->setIsSecretAndDisabled(true);
627             _scale->setIsSecretAndDisabled(false);
628             _format->setIsSecretAndDisabled(true);
629             break;
630         }
631     }
632 }
633 
634 void
changedClip(const InstanceChangedArgs & args,const string & clipName)635 OIIOResizePlugin::changedClip(const InstanceChangedArgs &args,
636                               const string &clipName)
637 {
638     if ( (clipName == kOfxImageEffectSimpleSourceClipName) && (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
639         _srcClipChanged->setValue(true);
640         OfxRectD srcRod = _srcClip->getRegionOfDefinition(args.time);
641         double srcpar = _srcClip->getPixelAspectRatio();
642 
643         ///Try to find a format matching the project format in which case we switch to format mode otherwise
644         ///switch to size mode and set the size accordingly
645         bool foundFormat = false;
646         for (int i = (int)eParamFormatPCVideo; i < (int)eParamFormatSquare2k; ++i) {
647             int w, h;
648             double par;
649             getFormatResolution( (EParamFormat)i, &w, &h, &par );
650             if ( ( w == (srcRod.x2 - srcRod.x1) ) && ( h == (srcRod.y2 - srcRod.y1) ) && (par == srcpar) ) {
651                 _format->setValue( (EParamFormat)i );
652                 _type->setValue( (int)eResizeTypeFormat );
653                 foundFormat = true;
654             }
655         }
656         _size->setValue( (int)srcRod.x2 - srcRod.x1, (int)srcRod.y2 - srcRod.y1 );
657         if (!foundFormat) {
658             _type->setValue( (int)eResizeTypeSize );
659         }
660     }
661 }
662 
663 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)664 OIIOResizePlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
665                                         OfxRectD &rod)
666 {
667     int type_i;
668 
669     _type->getValue(type_i);
670     ResizeTypeEnum type = (ResizeTypeEnum)type_i;
671     switch (type) {
672     case eResizeTypeFormat: {
673         //specific output format
674         int index;
675         _format->getValue(index);
676         double par = 1.;
677         int w = 0, h = 0;
678         getFormatResolution( (EParamFormat)index, &w, &h, &par );
679         OfxRectI rodPixel;
680         rodPixel.x1 = rodPixel.y1 = 0;
681         rodPixel.x2 = w;
682         rodPixel.y2 = h;
683         OfxPointD rsOne;
684         rsOne.x = rsOne.y = 1.;
685         Coords::toCanonical(rodPixel, rsOne, par, &rod);
686         break;
687     }
688 
689     case eResizeTypeSize: {
690         //size
691         int w, h;
692         _size->getValue(w, h);
693         bool preservePar;
694         _preservePAR->getValue(preservePar);
695         if (preservePar) {
696             OfxRectD srcRoD = _srcClip->getRegionOfDefinition(args.time);
697             double srcW = srcRoD.x2 - srcRoD.x1;
698             double srcH = srcRoD.y2 - srcRoD.y1;
699 
700             ///Don't crash if we were provided weird RoDs
701             if ( ( srcH < 1) || ( srcW < 1) ) {
702                 return false;
703             }
704             if ( (double)w / srcW < (double)h / srcH ) {
705                 ///Keep the given width, recompute the height
706                 h = (int)(srcH * w / srcW);
707             } else {
708                 ///Keep the given height,recompute the width
709                 w = (int)(srcW * h / srcH);
710             }
711         }
712         rod.x1 = 0;
713         rod.y1 = 0;
714         rod.x2 = w;
715         rod.y2 = h;
716         break;
717     }
718 
719     case eResizeTypeScale: {
720         //scaled
721         OfxRectD srcRoD = _srcClip->getRegionOfDefinition(args.time);
722         double sx, sy;
723         _scale->getValue(sx, sy);
724         srcRoD.x1 *= sx;
725         srcRoD.y1 *= sy;
726         srcRoD.x2 *= sx;
727         srcRoD.y2 *= sy;
728         rod.x1 = std::min(srcRoD.x1, srcRoD.x2 - 1);
729         rod.x2 = std::max(srcRoD.x1 + 1, srcRoD.x2);
730         rod.y1 = std::min(srcRoD.y1, srcRoD.y2 - 1);
731         rod.y2 = std::max(srcRoD.y1 + 1, srcRoD.y2);
732         break;
733     }
734     } // switch
735 
736     return true;
737 } // OIIOResizePlugin::getRegionOfDefinition
738 
739 // override the roi call
740 void
getRegionsOfInterest(const RegionsOfInterestArguments & args,RegionOfInterestSetter & rois)741 OIIOResizePlugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
742                                        RegionOfInterestSetter &rois)
743 {
744     if (!kSupportsTiles) {
745         // The effect requires full images to render any region
746 
747         if ( _srcClip && _srcClip->isConnected() ) {
748             OfxRectD srcRoD = _srcClip->getRegionOfDefinition(args.time);
749             rois.setRegionOfInterest(*_srcClip, srcRoD);
750         }
751     }
752 }
753 
754 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)755 OIIOResizePlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
756 {
757     double par = 1.;
758     int w = 0, h = 0;
759     bool setFormat = false;
760     ResizeTypeEnum type = (ResizeTypeEnum)_type->getValue();
761 
762     switch (type) {
763     case eResizeTypeFormat: {
764         //specific output format
765         int index;
766         _format->getValue(index);
767         getFormatResolution( (EParamFormat)index, &w, &h, &par );
768         clipPreferences.setPixelAspectRatio(*_dstClip, par);
769         setFormat = true;
770         break;
771     }
772     case eResizeTypeSize:
773         _size->getValue(w, h);
774         setFormat = true;
775         break;
776     case eResizeTypeScale:
777         // don't change the pixel aspect ratio
778         break;
779     }
780     if (setFormat) {
781 #ifdef OFX_EXTENSIONS_NATRON
782         OfxRectI format = { 0, 0, w, h };
783         clipPreferences.setOutputFormat(format);
784 #endif
785     }
786 }
787 
788 mDeclarePluginFactoryVersioned(OIIOResizePluginFactory, {ofxsThreadSuiteCheck();}, {});
789 
790 
791 /** @brief The basic describe function, passed a plugin descriptor */
792 template<unsigned int majorVersion>
793 void
describe(ImageEffectDescriptor & desc)794 OIIOResizePluginFactory<majorVersion>::describe(ImageEffectDescriptor &desc)
795 {
796     if (majorVersion < kPluginVersionMajor) {
797         desc.setIsDeprecated(true);
798     }
799     // basic labels
800     desc.setLabel(kPluginName);
801     desc.setPluginGrouping(kPluginGrouping);
802     desc.setPluginDescription(kPluginDescription);
803 
804     // add the supported contexts
805     desc.addSupportedContext(eContextGeneral);
806     desc.addSupportedContext(eContextFilter);
807 
808     // add supported pixel depths
809     desc.addSupportedBitDepth(eBitDepthUByte);
810     desc.addSupportedBitDepth(eBitDepthUShort);
811     desc.addSupportedBitDepth(eBitDepthFloat);
812 
813     ///We don't support tiles: we can only resize the whole RoD at once
814     desc.setSupportsTiles(kSupportsTiles);
815 
816     desc.setSupportsMultipleClipPARs(true); // plugin may setPixelAspectRatio on output clip
817 
818     ///We do support multiresolution
819     desc.setSupportsMultiResolution(kSupportsMultiResolution);
820 
821     desc.setRenderThreadSafety(kRenderThreadSafety);
822 
823     ///Don't let the host multi-thread
824     desc.setHostFrameThreading(true);
825 
826 #ifdef OFX_EXTENSIONS_NUKE
827     // ask the host to render all planes
828     desc.setPassThroughForNotProcessedPlanes(ePassThroughLevelRenderAllRequestedPlanes);
829 #endif
830 
831     //Openfx-misc got the Reformat node which is much faster, but Resize still gives better quality
832     //desc.setIsDeprecated(true);
833 }
834 
835 /** @brief The describe in context function, passed a plugin descriptor and a context */
836 template<unsigned int majorVersion>
837 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)838 OIIOResizePluginFactory<majorVersion>::describeInContext(ImageEffectDescriptor &desc,
839                                            ContextEnum /*context*/)
840 {
841     // Source clip only in the filter context
842     // create the mandated source clip
843     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
844 
845     srcClip->addSupportedComponent(ePixelComponentRGBA);
846     srcClip->addSupportedComponent(ePixelComponentRGB);
847     srcClip->addSupportedComponent(ePixelComponentAlpha);
848     srcClip->setTemporalClipAccess(false);
849     srcClip->setSupportsTiles(kSupportsTiles);
850     srcClip->setIsMask(false);
851 
852     // create the mandated output clip
853     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
854     dstClip->addSupportedComponent(ePixelComponentRGBA);
855     dstClip->addSupportedComponent(ePixelComponentRGB);
856     dstClip->addSupportedComponent(ePixelComponentAlpha);
857     dstClip->setSupportsTiles(kSupportsTiles);
858 
859     // make some pages and to things in
860     PageParamDescriptor *page = desc.definePageParam("Controls");
861 
862     {
863         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamType);
864         param->setLabel(kParamTypeLabel);
865         param->setHint(kParamTypeHint);
866         assert(param->getNOptions() == eResizeTypeFormat);
867         param->appendOption(kParamTypeOptionFormat);
868         assert(param->getNOptions() == eResizeTypeSize);
869         param->appendOption(kParamTypeOptionSize);
870         assert(param->getNOptions() == eResizeTypeScale);
871         param->appendOption(kParamTypeOptionScale);
872         param->setDefault(0);
873         param->setAnimates(false);
874         desc.addClipPreferencesSlaveParam(*param);
875         if (page) {
876             page->addChild(*param);
877         }
878     }
879     {
880         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFormat);
881         param->setLabel(kParamFormatLabel);
882         assert(param->getNOptions() == eParamFormatPCVideo);
883         param->appendOption(kParamFormatPCVideoLabel, "", kParamFormatPCVideo);
884         assert(param->getNOptions() == eParamFormatNTSC);
885         param->appendOption(kParamFormatNTSCLabel, "", kParamFormatNTSC);
886         assert(param->getNOptions() == eParamFormatPAL);
887         param->appendOption(kParamFormatPALLabel, "", kParamFormatPAL);
888         assert(param->getNOptions() == eParamFormatNTSC169);
889         param->appendOption(kParamFormatNTSC169Label, "", kParamFormatNTSC169);
890         assert(param->getNOptions() == eParamFormatPAL169);
891         param->appendOption(kParamFormatPAL169Label, "", kParamFormatPAL169);
892         assert(param->getNOptions() == eParamFormatHD720);
893         param->appendOption(kParamFormatHD720Label, "", kParamFormatHD720);
894         assert(param->getNOptions() == eParamFormatHD);
895         param->appendOption(kParamFormatHDLabel, "", kParamFormatHD);
896         assert(param->getNOptions() == eParamFormatUHD4K);
897         param->appendOption(kParamFormatUHD4KLabel, "", kParamFormatUHD4K);
898         assert(param->getNOptions() == eParamFormat1kSuper35);
899         param->appendOption(kParamFormat1kSuper35Label, "", kParamFormat1kSuper35);
900         assert(param->getNOptions() == eParamFormat1kCinemascope);
901         param->appendOption(kParamFormat1kCinemascopeLabel, "", kParamFormat1kCinemascope);
902         assert(param->getNOptions() == eParamFormat2kSuper35);
903         param->appendOption(kParamFormat2kSuper35Label, "", kParamFormat2kSuper35);
904         assert(param->getNOptions() == eParamFormat2kCinemascope);
905         param->appendOption(kParamFormat2kCinemascopeLabel, "", kParamFormat2kCinemascope);
906         assert(param->getNOptions() == eParamFormat2kDCP);
907         param->appendOption(kParamFormat2kDCPLabel, "", kParamFormat2kDCP);
908         assert(param->getNOptions() == eParamFormat4kSuper35);
909         param->appendOption(kParamFormat4kSuper35Label, "", kParamFormat4kSuper35);
910         assert(param->getNOptions() == eParamFormat4kCinemascope);
911         param->appendOption(kParamFormat4kCinemascopeLabel, "", kParamFormat4kCinemascope);
912         assert(param->getNOptions() == eParamFormat4kDCP);
913         param->appendOption(kParamFormat4kDCPLabel, "", kParamFormat4kDCP);
914         assert(param->getNOptions() == eParamFormatSquare256);
915         param->appendOption(kParamFormatSquare256Label, "", kParamFormatSquare256);
916         assert(param->getNOptions() == eParamFormatSquare512);
917         param->appendOption(kParamFormatSquare512Label, "", kParamFormatSquare512);
918         assert(param->getNOptions() == eParamFormatSquare1k);
919         param->appendOption(kParamFormatSquare1kLabel, "", kParamFormatSquare1k);
920         assert(param->getNOptions() == eParamFormatSquare2k);
921         param->appendOption(kParamFormatSquare2kLabel, "", kParamFormatSquare2k);
922         param->setDefault(0);
923         param->setHint(kParamFormatHint);
924         param->setAnimates(false);
925         desc.addClipPreferencesSlaveParam(*param);
926         if (page) {
927             page->addChild(*param);
928         }
929     }
930     {
931         Int2DParamDescriptor* param = desc.defineInt2DParam(kParamSize);
932         param->setLabel(kParamSizeLabel);
933         param->setHint(kParamSizeHint);
934         param->setDefault(200, 200);
935         param->setDisplayRange(0, 0, 10000, 10000);
936         param->setAnimates(false);
937         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
938         param->setRange( 1, 1, std::numeric_limits<int>::max(), std::numeric_limits<int>::max() );
939         param->setLayoutHint(eLayoutHintNoNewLine, 1);
940         if (page) {
941             page->addChild(*param);
942         }
943     }
944 
945     {
946         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPreservePAR);
947         param->setLabel(kParamPreservePARLabel);
948         param->setHint(kParamPreservePARHint);
949         param->setAnimates(false);
950         param->setDefault(false);
951         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
952         param->setDefault(true);
953         if (page) {
954             page->addChild(*param);
955         }
956     }
957     {
958         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamScale);
959         param->setHint(kParamScaleHint);
960         param->setLabel(kParamScaleLabel);
961         param->setAnimates(true);
962         //param->setIsSecretAndDisabled(true); // done in the plugin constructor
963         param->setDoubleType(eDoubleTypeScale);
964         param->setDefault(1., 1.);
965         param->setRange(0., 0., DBL_MAX, DBL_MAX);
966         param->setIncrement(0.05);
967         if (page) {
968             page->addChild(*param);
969         }
970     }
971     {
972         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamFilter);
973         param->setLabel(kParamFilterLabel);
974         param->setHint(kParamFilterHint);
975         param->setAnimates(false);
976         param->appendOption(kParamFilterOptionImpulse);
977         int nFilters = Filter2D::num_filters();
978         int defIndex = 0;
979         for (int i = 0; i < nFilters; ++i) {
980             FilterDesc f;
981             Filter2D::get_filterdesc(i, &f);
982             param->appendOption(f.name);
983             if ( !strcmp(f.name, "lanczos3") ) {
984                 defIndex = i + 1; // +1 because we added the "impulse" option
985             }
986         }
987         if (majorVersion > 1) {
988             param->appendOption(kParamFilterOptionDefault);
989             defIndex = nFilters + 1;
990         }
991         param->setDefault(defIndex);
992         if (page) {
993             page->addChild(*param);
994         }
995     }
996 
997     // srcClipChanged
998     {
999         BooleanParamDescriptor* param = desc.defineBooleanParam(kSrcClipChanged);
1000         param->setDefault(false);
1001         param->setIsSecretAndDisabled(true); // always secret
1002         param->setAnimates(false);
1003         param->setEvaluateOnChange(false);
1004         if (page) {
1005             page->addChild(*param);
1006         }
1007     }
1008 } // OIIOResizePluginFactory::describeInContext
1009 
1010 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
1011 template<unsigned int majorVersion>
1012 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1013 OIIOResizePluginFactory<majorVersion>::createInstance(OfxImageEffectHandle handle,
1014                                         ContextEnum /*context*/)
1015 {
1016     return new OIIOResizePlugin(handle);
1017 }
1018 
1019 // Declare old versions for backward compatibility.
1020 // They don't have the "default" filter option
1021 static OIIOResizePluginFactory<1> p1(kPluginIdentifier, 0);
1022 mRegisterPluginFactoryInstance(p1)
1023 
1024 static OIIOResizePluginFactory<kPluginVersionMajor> p2(kPluginIdentifier, kPluginVersionMinor);
1025 mRegisterPluginFactoryInstance(p2)
1026 
1027 OFXS_NAMESPACE_ANONYMOUS_EXIT
1028