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