HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
COP2_MultiInputWipe.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * Sample multi-input wipe COP
27  * Produces an overexposure/bloom dip-to-white transition effect between two
28  * sequences using crossfader controls.
29  */
30 #include <UT/UT_DSOVersion.h>
31 #include <OP/OP_OperatorTable.h>
32 #include <PRM/PRM_Include.h>
33 
34 #include <SYS/SYS_Floor.h>
35 #include <SYS/SYS_Math.h>
36 
37 #include <TIL/TIL_Plane.h>
38 #include <TIL/TIL_Region.h>
39 #include <TIL/TIL_Tile.h>
40 #include <COP2/COP2_CookAreaInfo.h>
41 #include "COP2_MultiInputWipe.h"
42 
43 using namespace HDK_Sample;
44 
45 COP_MULTI_SWITCHER(5, "Blend");
46 
47 static PRM_Name names[] =
48 {
49  PRM_Name("fadera", "A Fader"),
50  PRM_Name("faderb", "B Fader"),
51  PRM_Name("boostval", "Overexposure Boost"),
52  PRM_Name("bloomblur", "Bloom Blur"),
53  PRM_Name("fademode", "Fade Rate"),
54 };
55 
56 #define FADE_LINEAR 0
57 #define FADE_SQUARE 1
58 #define FADE_ROOT 2
59 static PRM_Name fadeItems[] =
60 {
61  PRM_Name("linear", "Linear"),
62  PRM_Name("squared", "Squared"),
63  PRM_Name("root", "Square Root"),
64  PRM_Name(0),
65 };
66 static PRM_ChoiceList fadeMenu((PRM_ChoiceListType)
68 
69 
70 static PRM_Range wipeRange(PRM_RANGE_RESTRICTED, 0.0f,
72 static PRM_Range boostRange(PRM_RANGE_UI, 0.0f, PRM_RANGE_UI, 1.0f);
73 static PRM_Range blurRange(PRM_RANGE_RESTRICTED, 0.0f, PRM_RANGE_UI, 20.0f);
74 static PRM_Default boostValue(0.9);
75 static PRM_Default boostBlur(9);
76 
79 {
81 
82  // comp page.
83  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[0], PRMoneDefaults, 0,
84  &wipeRange),
85  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[1], PRMzeroDefaults, 0,
86  &wipeRange),
87  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[2], &boostValue, 0,
88  &boostRange),
89  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[3], &boostBlur, 0,
90  &blurRange),
92  &fadeMenu),
93  PRM_Template(),
94 };
95 
99 
102 
103 const char * COP2_MultiInputWipe::myInputLabels[] =
104 {
105  "Wipe A",
106  "Wipe B",
107  0
108 };
109 
110 OP_Node *
112  const char *name,
113  OP_Operator *op)
114 {
115  return new COP2_MultiInputWipe(net, name, op);
116 }
117 
118 COP2_MultiInputWipe::COP2_MultiInputWipe(OP_Network *parent,
119  const char *name,
120  OP_Operator *entry)
121  : COP2_MultiBase(parent, name, entry)
122 {
123 }
124 
126 {
127 }
128 
129 namespace HDK_Sample {
130 
132 {
133 public:
136 
137  // Stashed parameters and data
138  float myFaderA;
139  float myFaderB;
140  float myBoostA;
141  float myBoostB;
144  float myBlurA;
145  float myBlurB;
146  bool myPassA;
147  bool myPassB;
148 };
149 
150 }
151 
154  int xres, int , int , int )
155 {
157  int fademode;
158  float blur;
159  float boost;
160 
161  // Compute the cross fader values and stash them in the context data
162  data->myFaderA = evalFloat("fadera",0,t);
163  data->myFaderB = evalFloat("faderb",0,t);
164 
165  fademode = evalInt("fademode", 0, t);
166 
167  if(fademode == FADE_SQUARE)
168  {
169  data->myFaderA *= data->myFaderA;
170  data->myFaderB *= data->myFaderB;
171  }
172  else if(fademode == FADE_ROOT)
173  {
174  data->myFaderA = SYSsqrt(data->myFaderA);
175  data->myFaderB = SYSsqrt(data->myFaderB);
176  }
177 
178  // See if we can pass through one input or the other, without modification.
179  data->myPassA = (data->myFaderA == 1.0f && data->myFaderB == 0.0f);
180  data->myPassB = (data->myFaderB == 1.0f && data->myFaderA == 0.0f);
181 
182  // determine how much of a boost each image will be getting.
183  boost = evalFloat("boostval", 0, t) * 0.5f;
184  data->myBoostA = data->myFaderB * boost;
185  data->myBoostB = data->myFaderA * boost;
186 
187  // Blur needs to be adjusted if we aren't cooking at full res.
188  blur = evalFloat("bloomblur", 0, t) * getXScaleFactor(xres);
189 
190  // Set up how much each input will be blurred
191  data->myBlurA = data->myFaderB * blur;
192  data->myBlurB = data->myFaderA * blur;
193  data->myBlurRadA = (int)SYSceil(data->myBlurA * 0.5f);
194  data->myBlurRadB = (int)SYSceil(data->myBlurB * 0.5f);
195 
196  return data;
197 }
198 
199 
200 void
202 {
204  static_cast<cop2_MultiInputWipeData *>(context.data());
205  bool init = false;
206  int x1,y1,x2,y2;
207  int ix1, ix2, iy1, iy2;
208 
209  x1 = 0;
210  y1 = 0;
211  x2 = context.myXres-1;
212  y2 = context.myYres-1;
213 
214  // The image bounds are a union of all input bounds.
215  for(int i=0; i<nInputs(); i++)
216  {
217  if(getInputBounds(i, context, ix1, iy1, ix2, iy2))
218  {
219  if(!init)
220  {
221  x1 = ix1;
222  y1 = iy1;
223  x2 = ix2;
224  y2 = iy2;
225  init = true;
226  }
227  else
228  {
229  if(ix1 < x1) x1 = ix1;
230  if(ix2 > x2) x2 = ix2;
231  if(iy1 < y1) y1 = iy1;
232  if(iy2 > y2) y2 = iy2;
233  }
234  }
235  }
236 
237  // Finally, we need to expand the bounds by the maximum blur radius.
238  if(!data->myPassA && !data->myPassB)
239  {
240  int brad = SYSmax(data->myBlurRadA, data->myBlurRadB);
241  x1 -= brad;
242  y1 -= brad;
243  x2 += brad;
244  y2 += brad;
245  }
246 
247  context.setImageBounds(x1,y1,x2,y2);
248 }
249 
250 void
252  COP2_CookAreaInfo &output_area,
253  const COP2_CookAreaList &input_areas,
254  COP2_CookAreaList &needed_areas)
255 {
257  COP2_Context *context;
259 
260  // If we're bypassed then we don't depend on any of the other inputs.
261  if (getBypass())
262  {
263  area = makeOutputAreaDependOnMyPlane(0, output_area, input_areas,
264  needed_areas);
265  return;
266  }
267 
268  context = output_area.getNodeContextData();
269  cdata = static_cast<cop2_MultiInputWipeData *>(context->data());
270 
271  // Add a dependency on the first wipe input.
272  if(!cdata->myPassB)
273  {
274  area = makeOutputAreaDependOnMyPlane(0, output_area, input_areas,
275  needed_areas);
276  // The area needs to be expanded by the blur radius for input A
277  if(area)
278  area->expandNeededArea( cdata->myBlurRadA, cdata->myBlurRadA,
279  cdata->myBlurRadA, cdata->myBlurRadA);
280  }
281 
282  // Add a dependency on the second wipe input, and expand its area as well.
283  if(!cdata->myPassA)
284  {
285  area = makeOutputAreaDependOnMyPlane(1, output_area, input_areas,
286  needed_areas);
287  if(area)
288  area->expandNeededArea( cdata->myBlurRadB, cdata->myBlurRadB,
289  cdata->myBlurRadB, cdata->myBlurRadB);
290  }
291 }
292 
293 int
295  const TIL_Plane *plane, int,
296  int, float t,
297  int xstart, int ystart)
298 {
300  static_cast<cop2_MultiInputWipeData *>(context.data());
301  const TIL_Sequence *inputseq = 0;
302 
303  // If one of the faders is at 1 and the other at 0, we might be able to
304  // pass one of the images through as-is.
305  if(data->myPassA)
306  inputseq = inputInfo(0);
307  else if(data->myPassB)
308  inputseq = inputInfo(1);
309 
310  if(inputseq)
311  {
312  // Now check if it's possible to pass the tile through as-is, based on
313  // whether the input tile and output tile match exactly.
314  const TIL_Plane *inputplane = inputseq->getPlane(plane->getName());
315  if(inputplane)
316  {
317  int xres,yres;
318  int ixres, iyres;
319 
320  mySequence.getRes(xres,yres);
321  inputseq->getRes(ixres,iyres);
322 
323  if(plane->isCompatible(*inputplane) && // planes match
324  ixres == xres && iyres == yres && // resolutions match
325  inputseq->getImageIndex(t) != -1 && // within frame range
326  isTileAlignedWithInput(0,context, xstart,ystart)) // tiles match
327  {
328  return 1;
329  }
330  }
331  }
332 
333  // Otherwise, cook.
334  return 0;
335 }
336 
337 void
339  const TIL_Plane *plane, int array_index,
340  float t, int xstart, int ystart,
341  TIL_TileList *&tiles,
342  int block, bool *mask, bool *blocked)
343 {
344  // This method is only called if passThrough() returns non-zero.
346  static_cast<cop2_MultiInputWipeData *>(context.data());
347  bool iblocked = false;
348 
349  // If we're not crossfading, just return one of the images as-is.
350  if(data->myPassA)
351  {
352  tiles = passInputTile(0,context, plane, array_index, t, xstart,
353  ystart, block, &iblocked, mask);
354  }
355  else if(data->myPassB)
356  {
357  tiles = passInputTile(1, context, plane, array_index, t, xstart,
358  ystart, block, &iblocked, mask);
359 
360  }
361 
362  // In the rare case that a tile could not be accessed, check if it was
363  // blocked and set our blocked flag, if passed in.
364  if(!tiles && iblocked && blocked)
365  *blocked = true;
366 }
367 
368 OP_ERROR
370 {
372  static_cast<cop2_MultiInputWipeData *>(context.data());
373  TIL_Region *aregion, *bregion;
374  int arad, brad;
375  TIL_Plane fpplane(*context.myPlane);
376  bool init = false;
377 
378  arad = data->myBlurRadA;
379  brad = data->myBlurRadB;
380 
381  // Request 32b FP data from the inputs
382  fpplane.setScoped(1);
383  fpplane.setFormat(TILE_FLOAT32);
384 
385  // Grab a region from input 1, run the operation on it, and release it.
386  if(data->myFaderA != 0.0f)
387  {
388  aregion = inputRegion(0, context, &fpplane, 0, context.getTime(),
389  tilelist->myX1 - arad,
390  tilelist->myY1 - arad,
391  tilelist->myX2 + arad,
392  tilelist->myY2 + arad, TIL_HOLD);
393  if(aregion)
394  {
395  boostAndBlur(tilelist, aregion, data->myFaderA,
396  data->myBoostA, arad, data->myBlurA, false);
397  init = true;
398  releaseRegion(aregion);
399  }
400  }
401 
402  // Repeat for input 2.
403  if(data->myFaderB != 0.0f)
404  {
405  bregion = inputRegion(1, context, &fpplane, 0, context.getTime(),
406  tilelist->myX1 - brad,
407  tilelist->myY1 - brad,
408  tilelist->myX2 + brad,
409  tilelist->myY2 + brad, TIL_HOLD);
410  if(bregion)
411  {
412  boostAndBlur(tilelist, bregion, data->myFaderB,
413  data->myBoostB, brad, data->myBlurB, init);
414  init = true;
415  releaseRegion(bregion);
416  }
417  }
418 
419  // If neither input was processed (A=0, B=0), clear the tiles to constant
420  // black.
421  if(!init)
422  tilelist->clearToBlack();
423 
424  return error();
425 }
426 
427 void
428 COP2_MultiInputWipe::boostAndBlur(TIL_TileList *tiles, TIL_Region *input,
429  float fade, float boost, int rad, float blur,
430  bool add)
431 {
432  int ti, x,y, i,j, idx;
433  int w,h;
434  int stride;
435  TIL_Tile *itr;
436  float *src, *scan;
437  float vedge;
438  float sum, hsum;
439  float *dest = NULL;
440  bool alloced = false;
441 
442  // Set up some constants for the blurring
443  const float iblur = fade / ((1.0f + blur) * (1.0f + blur));
444  const float edge = 1.0f - (rad - blur * 0.5f);
445 
446  // set up tile dimensions and the stride of the input region.
447  w = tiles->myX2 - tiles->myX1 + 1;
448  h = tiles->myY2 - tiles->myY1 + 1;
449 
450  stride = w + rad * 2;
451 
452  // Boost the input region. Unlike input tiles, you can modify data in
453  // input regions, as long you have not explicitly requested a shared region.
454  if(boost != 0.0f)
455  {
456  for(i=0; i<PLANE_MAX_VECTOR_SIZE; i++)
457  {
458  src = (float *) input->getImageData(i);
459  if(src)
460  for(y=0; y<(h+rad*2) * stride; y++)
461  *src++ += boost;
462  }
463  }
464 
465  // When assigning values on the first pass, it is not necessary to fetch the
466  // image data from the tile. We won't be using it and it'll be overwritten.
467  // So, the dest array must be allocated, as getTileInFP() won't be called
468  // to allocate it for us.
469  if(!add)
470  {
471  dest = new float[w*h];
472  alloced = true;
473  }
474 
475  // Iterate over each component in the tilelist.
476  FOR_EACH_UNCOOKED_TILE(tiles, itr, ti)
477  {
478  // Grab the image data from the region. It is FP32, as requested in
479  // cookMyTile().
480  src = ((float *) input->getImageData(ti)) + rad;
481 
482  if(add)
483  {
484  // Grab each tile in FP format. 'dest' may be allocated; if so, we
485  // need to free it, but it can be reused by getTileInFP for the next
486  // component.
487  if(getTileInFP(tiles, dest, ti))
488  alloced = true;
489  }
490  else
491  {
492  // It is being assigned, ignoring the previous tile data, so just
493  // zero out the array.
494  memset(dest, 0, sizeof(float)*w*h);
495  }
496 
497  // Process each pixel in the tile.
498  for(idx=0, y=0; y<h; y++)
499  {
500  for(x=0; x<w; x++, idx++)
501  {
502  sum = 0.0f;
503 
504  // for each pixel, blur the surroundings
505  scan = src+x;
506  for(i=-rad; i<=rad; i++)
507  {
508  vedge = (i == -rad || i == rad) ? edge : 1.0f;
509 
510  hsum = scan[-rad] * edge;
511  if(rad)
512  hsum += scan[rad] * edge;
513 
514  for(j=-rad+1; j<rad; j++)
515  hsum += scan[j];
516 
517  sum += hsum * vedge;
518  scan += stride;
519  }
520 
521  // Assign explicitly or add to the previous result, depending
522  // on the pass.
523  dest[idx] += sum * iblur;
524  }
525 
526  src += stride;
527  }
528 
529  // Write the FP result to the tile.
530  writeFPtoTile(tiles, dest, ti);
531  }
532 
533  // If dest was allocated by getTileInFP() or by this method, free it.
534  // (If getTileInFP() returned false, it is likely that the tile was
535  // already a FP32 tile, and freeing dest would crash as this is a direct
536  // pointer to the tile data.)
537  if(alloced)
538  delete [] dest;
539 }
540 
541 void
543 {
544  table->addOperator(new OP_Operator("hdk_multiwipe",
545  "HDK Multi Input Wipe",
548  2,
549  2,
551  0, // not generator
553 }
#define SYSmax(a, b)
Definition: SYS_Math.h:1367
TIL_TileList * passInputTile(int input_index, COP2_Context &context, const TIL_Plane *plane, int array_index, float t, int xstart, int ystart, int block, bool *blocked, bool *mask, COP2_Node *fromTile=0)
PRM_API PRM_Default PRMzeroDefaults[]
void writeFPtoTile(TIL_TileList *tilelist, float *&src, int index)
COP_MULTI_SWITCHER(5,"Blend")
PRM_API const PRM_Type PRM_FLT
virtual COP2_ContextData * newContextData(const TIL_Plane *p, int array_index, float t, int xres, int yres, int thread, int max_threads)
virtual int passThrough(COP2_Context &context, const TIL_Plane *plane, int comp_index, int array_index, float t, int xstart, int ystart)
bool isTileAlignedWithInput(int input, COP2_Context &context, int tilex, int tiley, COP2_Node *with_node=0)
static const char * myInputLabels[]
TIL_Sequence mySequence
Definition: COP2_Node.h:1304
#define TILE_FLOAT32
Definition: TIL_Defines.h:68
#define FOR_EACH_UNCOOKED_TILE(list, tile, i)
Definition: TIL_Defines.h:181
PRM_API const PRM_Type PRM_ORD
fpreal getTime() const
Definition: COP2_Context.h:77
fpreal evalFloat(int pi, int vi, fpreal t) const
GLint GLuint mask
Definition: glcorearb.h:123
UT_ErrorSeverity
Definition: UT_Error.h:25
virtual void getInputDependenciesForOutputArea(COP2_CookAreaInfo &output_area, const COP2_CookAreaList &input_areas, COP2_CookAreaList &needed_areas)
void setImageBounds(int x1, int y1, int x2, int y2)
void getRes(int &x, int &y) const
Definition: TIL_Sequence.h:93
PRM_API PRM_Default PRMoneDefaults[]
GLint y
Definition: glcorearb.h:102
PRM_ChoiceListType
virtual void computeImageBounds(COP2_Context &context)
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
static OP_VariablePair myVariablePair
Definition: COP2_Node.h:628
png_uint_32 i
Definition: png.h:2877
#define FADE_SQUARE
const TIL_Sequence * inputInfo(int input)
virtual unsigned nInputs() const
GLfloat f
Definition: glcorearb.h:1925
void * getImageData(int index)
#define POPUP_PARM
Definition: COP2_Common.h:27
bool expandNeededArea(int pixels_left, int pixels_down, int pixels_right, int pixels_up)
int getBypass() const
Definition: OP_Node.h:1287
const char * getName() const
Definition: TIL_Plane.h:56
GLint GLenum GLboolean GLsizei stride
Definition: glcorearb.h:871
float getXScaleFactor(int xres) const
#define PLANE_MAX_VECTOR_SIZE
Definition: TIL_Defines.h:156
COP2_ContextData * data()
static OP_TemplatePair myTemplatePair
GLboolean * data
Definition: glcorearb.h:130
PRM_API PRM_Name PRMswitcherName
GLuint const GLchar * name
Definition: glcorearb.h:785
virtual OP_ERROR cookMyTile(COP2_Context &context, TIL_TileList *tiles)
bool getInputBounds(int input, COP2_Context &context, int &x1, int &y1, int &x2, int &y2)
Definition: COP2_Node.h:1723
PRM_API const PRM_Type PRM_SWITCHER
COP2_CookAreaInfo * makeOutputAreaDependOnMyPlane(int input, COP2_CookAreaInfo &output_area, const COP2_CookAreaList &input_areas, COP2_CookAreaList &needed_areas)
virtual void passThroughTiles(COP2_Context &context, const TIL_Plane *plane, int array_index, float t, int xstart, int ystart, TIL_TileList *&tile, int block=1, bool *mask=0, bool *blocked=0)
void releaseRegion(TIL_Region *, int output=0)
virtual OP_ERROR error()
Definition: COP2_Node.h:453
#define TOOL_PARM
Definition: COP2_Common.h:26
static OP_VariablePair myVariablePair
GLfloat GLfloat GLfloat GLfloat h
Definition: glcorearb.h:2001
TIL_Region * inputRegion(int input_index, COP2_Context &context, const TIL_Plane *plane, int array_index, float t, int xstart, int ystart, int xend, int yend, TIL_RegionExtend hold=TIL_BLACK, int share=1, void *regionmem[PLANE_MAX_VECTOR_SIZE]=0, bool correct_aspect=true, bool correct_bounds=true, int scan_alignment=0)
COP2_Context * getNodeContextData()
typedef int
Definition: png.h:1175
static OP_TemplatePair myTemplatePair
static PRM_Template myTemplateList[]
void newCop2Operator(OP_OperatorTable *table)
bool getTileInFP(TIL_TileList *tilelist, float *&dest, int index, void *dtile=0)
GLint GLenum GLint x
Definition: glcorearb.h:408
static OP_Node * myConstructor(OP_Network *, const char *, OP_Operator *)
void clearToBlack(bool markconstant=true)
void setFormat(TIL_DataFormat format)
GLubyte GLubyte GLubyte GLubyte w
Definition: glcorearb.h:856
const TIL_Plane * myPlane
Definition: COP2_Context.h:74
exint evalInt(int pi, int vi, fpreal t) const
TIL_Plane * getPlane(int index)
exint getImageIndex(double t, int clamp_range=1, int round_off=SEQUENCE_NEAREST) const
#define FADE_ROOT
SYS_API fpreal32 SYSceil(fpreal32 val)
GA_API const UT_StringHolder area
void setScoped(int enable)
Definition: TIL_Plane.h:96
bool isCompatible(const TIL_Plane &) const
GLenum src
Definition: glcorearb.h:1792