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) 2024
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 COP2_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_CHOICELIST_SINGLE, fadeItems);
67 
68 
69 static PRM_Range wipeRange(PRM_RANGE_RESTRICTED, 0.0f,
71 static PRM_Range boostRange(PRM_RANGE_UI, 0.0f, PRM_RANGE_UI, 1.0f);
72 static PRM_Range blurRange(PRM_RANGE_RESTRICTED, 0.0f, PRM_RANGE_UI, 20.0f);
73 static PRM_Default boostValue(0.9);
74 static PRM_Default boostBlur(9);
75 
78 {
80 
81  // comp page.
82  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[0], PRMoneDefaults, 0,
83  &wipeRange),
84  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[1], PRMzeroDefaults, 0,
85  &wipeRange),
86  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[2], &boostValue, 0,
87  &boostRange),
88  PRM_Template(PRM_FLT, TOOL_PARM, 1, &names[3], &boostBlur, 0,
89  &blurRange),
91  &fadeMenu),
92  PRM_Template(),
93 };
94 
98 
101 
102 const char * COP2_MultiInputWipe::myInputLabels[] =
103 {
104  "Wipe A",
105  "Wipe B",
106  0
107 };
108 
109 OP_Node *
111  const char *name,
112  OP_Operator *op)
113 {
114  return new COP2_MultiInputWipe(net, name, op);
115 }
116 
117 COP2_MultiInputWipe::COP2_MultiInputWipe(OP_Network *parent,
118  const char *name,
119  OP_Operator *entry)
120  : COP2_MultiBase(parent, name, entry)
121 {
122 }
123 
125 {
126 }
127 
128 namespace HDK_Sample {
129 
131 {
132 public:
135 
136  // Stashed parameters and data
137  float myFaderA;
138  float myFaderB;
139  float myBoostA;
140  float myBoostB;
143  float myBlurA;
144  float myBlurB;
145  bool myPassA;
146  bool myPassB;
147 };
148 
149 }
150 
153  int xres, int , int , int )
154 {
156  int fademode;
157  float blur;
158  float boost;
159 
160  // Compute the cross fader values and stash them in the context data
161  data->myFaderA = evalFloat("fadera",0,t);
162  data->myFaderB = evalFloat("faderb",0,t);
163 
164  fademode = evalInt("fademode", 0, t);
165 
166  if(fademode == FADE_SQUARE)
167  {
168  data->myFaderA *= data->myFaderA;
169  data->myFaderB *= data->myFaderB;
170  }
171  else if(fademode == FADE_ROOT)
172  {
173  data->myFaderA = SYSsqrt(data->myFaderA);
174  data->myFaderB = SYSsqrt(data->myFaderB);
175  }
176 
177  // See if we can pass through one input or the other, without modification.
178  data->myPassA = (data->myFaderA == 1.0f && data->myFaderB == 0.0f);
179  data->myPassB = (data->myFaderB == 1.0f && data->myFaderA == 0.0f);
180 
181  // determine how much of a boost each image will be getting.
182  boost = evalFloat("boostval", 0, t) * 0.5f;
183  data->myBoostA = data->myFaderB * boost;
184  data->myBoostB = data->myFaderA * boost;
185 
186  // Blur needs to be adjusted if we aren't cooking at full res.
187  blur = evalFloat("bloomblur", 0, t) * getXScaleFactor(xres);
188 
189  // Set up how much each input will be blurred
190  data->myBlurA = data->myFaderB * blur;
191  data->myBlurB = data->myFaderA * blur;
192  data->myBlurRadA = (int)SYSceil(data->myBlurA * 0.5f);
193  data->myBlurRadB = (int)SYSceil(data->myBlurB * 0.5f);
194 
195  return data;
196 }
197 
198 
199 void
201 {
203  static_cast<cop2_MultiInputWipeData *>(context.data());
204  bool init = false;
205  int x1,y1,x2,y2;
206  int ix1, ix2, iy1, iy2;
207 
208  x1 = 0;
209  y1 = 0;
210  x2 = context.myXres-1;
211  y2 = context.myYres-1;
212 
213  // The image bounds are a union of all input bounds.
214  for(int i=0; i<nInputs(); i++)
215  {
216  if(getInputBounds(i, context, ix1, iy1, ix2, iy2))
217  {
218  if(!init)
219  {
220  x1 = ix1;
221  y1 = iy1;
222  x2 = ix2;
223  y2 = iy2;
224  init = true;
225  }
226  else
227  {
228  if(ix1 < x1) x1 = ix1;
229  if(ix2 > x2) x2 = ix2;
230  if(iy1 < y1) y1 = iy1;
231  if(iy2 > y2) y2 = iy2;
232  }
233  }
234  }
235 
236  // Finally, we need to expand the bounds by the maximum blur radius.
237  if(!data->myPassA && !data->myPassB)
238  {
239  int brad = SYSmax(data->myBlurRadA, data->myBlurRadB);
240  x1 -= brad;
241  y1 -= brad;
242  x2 += brad;
243  y2 += brad;
244  }
245 
246  context.setImageBounds(x1,y1,x2,y2);
247 }
248 
249 void
251  COP2_CookAreaInfo &output_area,
252  const COP2_CookAreaList &input_areas,
253  COP2_CookAreaList &needed_areas)
254 {
256  COP2_Context *context;
258 
259  // If we're bypassed then we don't depend on any of the other inputs.
260  if (getBypass())
261  {
262  area = makeOutputAreaDependOnMyPlane(0, output_area, input_areas,
263  needed_areas);
264  return;
265  }
266 
267  context = output_area.getNodeContextData();
268  cdata = static_cast<cop2_MultiInputWipeData *>(context->data());
269 
270  // Add a dependency on the first wipe input.
271  if(!cdata->myPassB)
272  {
273  area = makeOutputAreaDependOnMyPlane(0, output_area, input_areas,
274  needed_areas);
275  // The area needs to be expanded by the blur radius for input A
276  if(area)
277  area->expandNeededArea( cdata->myBlurRadA, cdata->myBlurRadA,
278  cdata->myBlurRadA, cdata->myBlurRadA);
279  }
280 
281  // Add a dependency on the second wipe input, and expand its area as well.
282  if(!cdata->myPassA)
283  {
284  area = makeOutputAreaDependOnMyPlane(1, output_area, input_areas,
285  needed_areas);
286  if(area)
287  area->expandNeededArea( cdata->myBlurRadB, cdata->myBlurRadB,
288  cdata->myBlurRadB, cdata->myBlurRadB);
289  }
290 }
291 
292 int
294  const TIL_Plane *plane, int,
295  int, float t,
296  int xstart, int ystart)
297 {
299  static_cast<cop2_MultiInputWipeData *>(context.data());
300  const TIL_Sequence *inputseq = 0;
301 
302  // If one of the faders is at 1 and the other at 0, we might be able to
303  // pass one of the images through as-is.
304  if(data->myPassA)
305  inputseq = inputInfo(0);
306  else if(data->myPassB)
307  inputseq = inputInfo(1);
308 
309  if(inputseq)
310  {
311  // Now check if it's possible to pass the tile through as-is, based on
312  // whether the input tile and output tile match exactly.
313  const TIL_Plane *inputplane = inputseq->getPlane(plane->getName());
314  if(inputplane)
315  {
316  int xres,yres;
317  int ixres, iyres;
318 
319  mySequence.getRes(xres,yres);
320  inputseq->getRes(ixres,iyres);
321 
322  if(plane->isCompatible(*inputplane) && // planes match
323  ixres == xres && iyres == yres && // resolutions match
324  inputseq->getImageIndex(t) != -1 && // within frame range
325  isTileAlignedWithInput(0,context, xstart,ystart)) // tiles match
326  {
327  return 1;
328  }
329  }
330  }
331 
332  // Otherwise, cook.
333  return 0;
334 }
335 
336 void
338  const TIL_Plane *plane, int array_index,
339  float t, int xstart, int ystart,
340  TIL_TileList *&tiles,
341  int block, bool *mask, bool *blocked)
342 {
343  // This method is only called if passThrough() returns non-zero.
345  static_cast<cop2_MultiInputWipeData *>(context.data());
346  bool iblocked = false;
347 
348  // If we're not crossfading, just return one of the images as-is.
349  if(data->myPassA)
350  {
351  tiles = passInputTile(0,context, plane, array_index, t, xstart,
352  ystart, block, &iblocked, mask);
353  }
354  else if(data->myPassB)
355  {
356  tiles = passInputTile(1, context, plane, array_index, t, xstart,
357  ystart, block, &iblocked, mask);
358 
359  }
360 
361  // In the rare case that a tile could not be accessed, check if it was
362  // blocked and set our blocked flag, if passed in.
363  if(!tiles && iblocked && blocked)
364  *blocked = true;
365 }
366 
367 OP_ERROR
369 {
371  static_cast<cop2_MultiInputWipeData *>(context.data());
372  TIL_Region *aregion, *bregion;
373  int arad, brad;
374  TIL_Plane fpplane(*context.myPlane);
375  bool init = false;
376 
377  arad = data->myBlurRadA;
378  brad = data->myBlurRadB;
379 
380  // Request 32b FP data from the inputs
381  fpplane.setScoped(1);
382  fpplane.setFormat(TILE_FLOAT32);
383 
384  // Grab a region from input 1, run the operation on it, and release it.
385  if(data->myFaderA != 0.0f)
386  {
387  aregion = inputRegion(0, context, &fpplane, 0, context.getTime(),
388  tilelist->myX1 - arad,
389  tilelist->myY1 - arad,
390  tilelist->myX2 + arad,
391  tilelist->myY2 + arad, TIL_HOLD);
392  if(aregion)
393  {
394  boostAndBlur(tilelist, aregion, data->myFaderA,
395  data->myBoostA, arad, data->myBlurA, false);
396  init = true;
397  releaseRegion(aregion);
398  }
399  }
400 
401  // Repeat for input 2.
402  if(data->myFaderB != 0.0f)
403  {
404  bregion = inputRegion(1, context, &fpplane, 0, context.getTime(),
405  tilelist->myX1 - brad,
406  tilelist->myY1 - brad,
407  tilelist->myX2 + brad,
408  tilelist->myY2 + brad, TIL_HOLD);
409  if(bregion)
410  {
411  boostAndBlur(tilelist, bregion, data->myFaderB,
412  data->myBoostB, brad, data->myBlurB, init);
413  init = true;
414  releaseRegion(bregion);
415  }
416  }
417 
418  // If neither input was processed (A=0, B=0), clear the tiles to constant
419  // black.
420  if(!init)
421  tilelist->clearToBlack();
422 
423  return error();
424 }
425 
426 void
427 COP2_MultiInputWipe::boostAndBlur(TIL_TileList *tiles, TIL_Region *input,
428  float fade, float boost, int rad, float blur,
429  bool add)
430 {
431  int ti, x,y, i,j, idx;
432  int w,h;
433  int stride;
434  TIL_Tile *itr;
435  float *src, *scan;
436  float vedge;
437  float sum, hsum;
438  float *dest = NULL;
439  bool alloced = false;
440 
441  // Set up some constants for the blurring
442  const float iblur = fade / ((1.0f + blur) * (1.0f + blur));
443  const float edge = 1.0f - (rad - blur * 0.5f);
444 
445  // set up tile dimensions and the stride of the input region.
446  w = tiles->myX2 - tiles->myX1 + 1;
447  h = tiles->myY2 - tiles->myY1 + 1;
448 
449  stride = w + rad * 2;
450 
451  // Boost the input region. Unlike input tiles, you can modify data in
452  // input regions, as long you have not explicitly requested a shared region.
453  if(boost != 0.0f)
454  {
455  for(i=0; i<PLANE_MAX_VECTOR_SIZE; i++)
456  {
457  src = (float *) input->getImageData(i);
458  if(src)
459  for(y=0; y<(h+rad*2) * stride; y++)
460  *src++ += boost;
461  }
462  }
463 
464  // When assigning values on the first pass, it is not necessary to fetch the
465  // image data from the tile. We won't be using it and it'll be overwritten.
466  // So, the dest array must be allocated, as getTileInFP() won't be called
467  // to allocate it for us.
468  if(!add)
469  {
470  dest = new float[w*h];
471  alloced = true;
472  }
473 
474  // Iterate over each component in the tilelist.
475  FOR_EACH_UNCOOKED_TILE(tiles, itr, ti)
476  {
477  // Grab the image data from the region. It is FP32, as requested in
478  // cookMyTile().
479  src = ((float *) input->getImageData(ti)) + rad;
480 
481  if(add)
482  {
483  // Grab each tile in FP format. 'dest' may be allocated; if so, we
484  // need to free it, but it can be reused by getTileInFP for the next
485  // component.
486  if(getTileInFP(tiles, dest, ti))
487  alloced = true;
488  }
489  else
490  {
491  // It is being assigned, ignoring the previous tile data, so just
492  // zero out the array.
493  memset(dest, 0, sizeof(float)*w*h);
494  }
495 
496  // Process each pixel in the tile.
497  for(idx=0, y=0; y<h; y++)
498  {
499  for(x=0; x<w; x++, idx++)
500  {
501  sum = 0.0f;
502 
503  // for each pixel, blur the surroundings
504  scan = src+x;
505  for(i=-rad; i<=rad; i++)
506  {
507  vedge = (i == -rad || i == rad) ? edge : 1.0f;
508 
509  hsum = scan[-rad] * edge;
510  if(rad)
511  hsum += scan[rad] * edge;
512 
513  for(j=-rad+1; j<rad; j++)
514  hsum += scan[j];
515 
516  sum += hsum * vedge;
517  scan += stride;
518  }
519 
520  // Assign explicitly or add to the previous result, depending
521  // on the pass.
522  dest[idx] += sum * iblur;
523  }
524 
525  src += stride;
526  }
527 
528  // Write the FP result to the tile.
529  writeFPtoTile(tiles, dest, ti);
530  }
531 
532  // If dest was allocated by getTileInFP() or by this method, free it.
533  // (If getTileInFP() returned false, it is likely that the tile was
534  // already a FP32 tile, and freeing dest would crash as this is a direct
535  // pointer to the tile data.)
536  if(alloced)
537  delete [] dest;
538 }
539 
540 void
542 {
543  table->addOperator(new OP_Operator("hdk_multiwipe",
544  "HDK Multi Input Wipe",
547  2,
548  2,
550  0, // not generator
552 }
#define SYSmax(a, b)
Definition: SYS_Math.h:1538
typedef int(APIENTRYP RE_PFNGLXSWAPINTERVALSGIPROC)(int)
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)
void writeFPtoTile(TIL_TileList *tilelist, float *&src, int index)
PRM_API const PRM_Type PRM_FLT
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:1345
#define TILE_FLOAT32
Definition: TIL_Defines.h:69
GLboolean * data
Definition: glcorearb.h:131
OP_ERROR error() override
Definition: COP2_Node.h:483
#define FOR_EACH_UNCOOKED_TILE(list, tile, i)
Definition: TIL_Defines.h:192
PRM_API const PRM_Type PRM_ORD
fpreal getTime() const
Definition: COP2_Context.h:77
COP2_MULTI_SWITCHER(5,"Blend")
fpreal evalFloat(int pi, int vi, fpreal t) const
UT_ErrorSeverity
Definition: UT_Error.h:25
void setImageBounds(int x1, int y1, int x2, int y2)
void getRes(int &x, int &y) const
Definition: TIL_Sequence.h:95
GLint y
Definition: glcorearb.h:103
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
static OP_VariablePair myVariablePair
Definition: COP2_Node.h:663
#define FADE_SQUARE
GLdouble GLdouble x2
Definition: glad.h:2349
const TIL_Sequence * inputInfo(int input)
virtual unsigned nInputs() const
GLfloat f
Definition: glcorearb.h:1926
void * getImageData(int index)
#define POPUP_PARM
Definition: COP2_Common.h:27
void computeImageBounds(COP2_Context &context) override
bool expandNeededArea(int pixels_left, int pixels_down, int pixels_right, int pixels_up)
const char * getName() const
Definition: TIL_Plane.h:61
GLint GLenum GLboolean GLsizei stride
Definition: glcorearb.h:872
GLdouble y1
Definition: glad.h:2349
float getXScaleFactor(int xres) const
GLint GLuint mask
Definition: glcorearb.h:124
#define PLANE_MAX_VECTOR_SIZE
Definition: TIL_Defines.h:167
COP2_ContextData * data()
static OP_TemplatePair myTemplatePair
OP_ERROR cookMyTile(COP2_Context &context, TIL_TileList *tiles) override
GLuint const GLchar * name
Definition: glcorearb.h:786
GLint GLenum GLint x
Definition: glcorearb.h:409
bool getInputBounds(int input, COP2_Context &context, int &x1, int &y1, int &x2, int &y2)
Definition: COP2_Node.h:1767
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
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)
GLdouble t
Definition: glad.h:2397
void releaseRegion(TIL_Region *, int output=0)
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) override
#define TOOL_PARM
Definition: COP2_Common.h:26
GLint j
Definition: glad.h:2733
static OP_VariablePair myVariablePair
PRM_API PRM_Default PRMoneDefaults[]
PRM_API PRM_Name PRMswitcherName
GLfloat GLfloat GLfloat GLfloat h
Definition: glcorearb.h:2002
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()
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)
static OP_Node * myConstructor(OP_Network *, const char *, OP_Operator *)
void clearToBlack(bool markconstant=true)
void setFormat(TIL_DataFormat format)
COP2_ContextData * newContextData(const TIL_Plane *p, int array_index, float t, int xres, int yres, int thread, int max_threads) override
void getInputDependenciesForOutputArea(COP2_CookAreaInfo &output_area, const COP2_CookAreaList &input_areas, COP2_CookAreaList &needed_areas) override
GLubyte GLubyte GLubyte GLubyte w
Definition: glcorearb.h:857
int passThrough(COP2_Context &context, const TIL_Plane *plane, int comp_index, int array_index, float t, int xstart, int ystart) override
const TIL_Plane * myPlane
Definition: COP2_Context.h:74
exint evalInt(int pi, int vi, fpreal t) const
ImageBuf OIIO_API add(Image_or_Const A, Image_or_Const B, ROI roi={}, int nthreads=0)
bool getBypass() const
Definition: OP_Node.h:1331
GLdouble GLdouble GLdouble y2
Definition: glad.h:2349
TIL_Plane * getPlane(int index)
exint getImageIndex(double t, int clamp_range=1, int round_off=SEQUENCE_NEAREST) const
PRM_API PRM_Default PRMzeroDefaults[]
#define FADE_ROOT
Definition: format.h:895
SYS_API fpreal32 SYSceil(fpreal32 val)
GA_API const UT_StringHolder area
void setScoped(int enable)
Definition: TIL_Plane.h:106
bool isCompatible(const TIL_Plane &) const
GLenum src
Definition: glcorearb.h:1793