HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
RAY_DemoEdgeDetectFilter.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  * This is a sample pixel filter to do edge detection
27  */
28 
29 #include <UT/UT_DSOVersion.h>
30 
32 #include <RAY/RAY_SpecialChannel.h>
33 #include <UT/UT_Args.h>
34 #include <UT/UT_StackBuffer.h>
35 #include <SYS/SYS_Floor.h>
36 #include <SYS/SYS_Math.h>
37 
38 using namespace HDK_Sample;
39 
41 allocPixelFilter(const char *name)
42 {
43  // NOTE: We could use name to distinguish between multiple pixel filters,
44  // in the same library, but we only have one.
45  return new RAY_DemoEdgeDetectFilter();
46 }
47 
49  : mySamplesPerPixelX(1) // Initialized just in case; value shouldn't be used
50  , mySamplesPerPixelY(1) // Initialized just in case; value shouldn't be used
51  , myUseColourGradient(true)
52  , myUseZGradient(true)
53  , myUseOpID(true)
54  , myColourGradientThreshold(0.1f)
55  , myZGradientThreshold(0.005f)
56  , myColourGradientWidth(3.0f)
57  , myZGradientWidth(3.0f)
58  , myOpIDWidth(3.0f)
59 {
60 }
61 
63 {
64 }
65 
68 {
69  // In this case, all of our members can be default-copy-constructed,
70  // so we don't need to write a copy constructor implementation.
72  return pf;
73 }
74 
75 void
76 RAY_DemoEdgeDetectFilter::setArgs(int argc, const char *const argv[])
77 {
78  UT_Args args;
79  args.initialize(argc, argv);
80  args.stripOptions("c:o:s:w:z:");
81 
82  // e.g. default values correspond with:
83  // -c 0.1 -w 3.0 -z 0.005 -s 3.0 -o 3.0
84  // To disable any of the 3 detections, set one of the corresponding
85  // parameters to a negative number, like -1
86 
87  if (args.found('c'))
88  {
89  myColourGradientThreshold = args.fargp('c');
90  myUseColourGradient = (myColourGradientThreshold >= 0);
91  if (myUseColourGradient && args.found('w'))
92  {
93  myColourGradientWidth = args.fargp('w');
94  if (myColourGradientWidth < 0)
95  myUseColourGradient = false;
96  // NOTE: You could add support for widths < 1.0 by taking the max
97  // of the gradients within a pixel.
98  // The upper limit is just to avoid accidents.
99  myColourGradientWidth = SYSclamp(myColourGradientWidth, 1.0f, 1024.0f);
100  }
101  }
102  if (args.found('o'))
103  {
104  myOpIDWidth = args.fargp('o');
105  myUseOpID = (myOpIDWidth >= 0);
106  myOpIDWidth = SYSclamp(myOpIDWidth, 1.0f, 1024.0f);
107  }
108  if (args.found('z'))
109  {
110  myZGradientThreshold = args.fargp('z');
111  myUseZGradient = (myZGradientThreshold >= 0);
112  if (myUseZGradient && args.found('s'))
113  {
114  myZGradientWidth = args.fargp('s');
115  if (myZGradientWidth < 0)
116  myUseZGradient = false;
117  // NOTE: You could add support for widths < 1.0 by taking the max
118  // of the gradients within a pixel.
119  // The upper limit is just to avoid accidents.
120  myZGradientWidth = SYSclamp(myZGradientWidth, 1.0f, 1024.0f);
121  }
122  }
123 }
124 
125 void
127 {
128  // NOTE: You could add support for different x and y filter widths,
129  // which might be useful for non-square pixels.
130  float filterwidth = SYSmax(myUseColourGradient ? myColourGradientWidth : 1,
131  myUseZGradient ? myZGradientWidth : 1,
132  myUseOpID ? myOpIDWidth : 1);
133  x = filterwidth;
134  y = filterwidth;
135 }
136 
137 void
139 {
140  if (myUseOpID)
142  if (myUseZGradient)
144 }
145 
146 namespace {
147 float RAYcomputeSumX2(int samplesperpixel,float width,int &halfsamplewidth)
148 {
149  float sumx2 = 0;
150  if (samplesperpixel & 1)
151  {
152  // NOTE: This omits the middle sample
153  halfsamplewidth = (int)SYSfloor(float(samplesperpixel)*0.5f*width);
154 
155  // There's a close form for this sum, but I figured I'd write it out in full,
156  // since it's not a bottleneck.
157  for (int i = -halfsamplewidth; i <= halfsamplewidth; ++i)
158  {
159  float x = float(i)/float(samplesperpixel);
160  sumx2 += x*x;
161  }
162  }
163  else
164  {
165  halfsamplewidth = (int)SYSfloor(float(samplesperpixel)*0.5f*width + 0.5f);
166 
167  // There's a close form for this sum, but I figured I'd write it out in full,
168  // since it's not a bottleneck.
169  for (int i = -halfsamplewidth; i < halfsamplewidth; ++i)
170  {
171  float x = (float(i)+0.5f)/float(samplesperpixel);
172  sumx2 += x*x;
173  }
174  }
175  return sumx2;
176 }
177 }
178 
179 void
180 RAY_DemoEdgeDetectFilter::prepFilter(int samplesperpixelx, int samplesperpixely)
181 {
182  mySamplesPerPixelX = samplesperpixelx;
183  mySamplesPerPixelY = samplesperpixely;
184 
185  // We can precompute coefficients here
186  myColourSumX2 = RAYcomputeSumX2(mySamplesPerPixelX, myColourGradientWidth, myColourSamplesHalfX);
187  myColourSumY2 = RAYcomputeSumX2(mySamplesPerPixelY, myColourGradientWidth, myColourSamplesHalfY);
188  myZSumX2 = RAYcomputeSumX2(mySamplesPerPixelX, myZGradientWidth, myZSamplesHalfX);
189  myZSumY2 = RAYcomputeSumX2(mySamplesPerPixelY, myZGradientWidth, myZSamplesHalfY);
190  myOpIDSamplesHalfX = (int)SYSfloor(float(mySamplesPerPixelX)*0.5f*myOpIDWidth + ((mySamplesPerPixelX & 1) ? 0.0f : 0.5f));
191  myOpIDSamplesHalfY = (int)SYSfloor(float(mySamplesPerPixelY)*0.5f*myOpIDWidth + ((mySamplesPerPixelY & 1) ? 0.0f : 0.5f));
192 }
193 
194 void
196  float *destination,
197  int vectorsize,
198  const RAY_SampleBuffer &source,
199  int channel,
200  int sourcewidth,
201  int sourceheight,
202  int destwidth,
203  int destheight,
204  int destxoffsetinsource,
205  int destyoffsetinsource,
206  const RAY_Imager &imager) const
207 {
208  const float *const colourdata = myUseColourGradient
209  ? getSampleData(source, channel)
210  : NULL;
211  const float *const zdata = myUseZGradient
213  : NULL;
214  const float *const opiddata = myUseOpID
216  : NULL;
217 
218  UT_ASSERT(myUseColourGradient == (colourdata != NULL));
219  UT_ASSERT(myUseZGradient == (zdata != NULL));
220  UT_ASSERT(myUseOpID == (opiddata != NULL));
221 
222  // The gradient computations can be made much faster by separating x and y,
223  // using a temporary buffer, but this is implemented the slow way.
224  // In fact, nothing in here is optimized.
225 
226  for (int desty = 0; desty < destheight; ++desty)
227  {
228  for (int destx = 0; destx < destwidth; ++destx)
229  {
230  bool isedge = false;
231 
232  // First, compute the sample bounds of the pixel
233  const int sourcefirstx = destxoffsetinsource + destx*mySamplesPerPixelX;
234  const int sourcefirsty = destyoffsetinsource + desty*mySamplesPerPixelY;
235  const int sourcelastx = sourcefirstx + mySamplesPerPixelX-1;
236  const int sourcelasty = sourcefirsty + mySamplesPerPixelY-1;
237  // Find the first sample to read for colour and z gradients
238  const int sourcefirstcx = sourcefirstx + (mySamplesPerPixelX>>1) - myColourSamplesHalfX;
239  const int sourcefirstcy = sourcefirsty + (mySamplesPerPixelY>>1) - myColourSamplesHalfY;
240  const int sourcefirstzx = sourcefirstx + (mySamplesPerPixelX>>1) - myZSamplesHalfX;
241  const int sourcefirstzy = sourcefirsty + (mySamplesPerPixelY>>1) - myZSamplesHalfY;
242  const int sourcefirstox = sourcefirstx + (mySamplesPerPixelX>>1) - myOpIDSamplesHalfX;
243  const int sourcefirstoy = sourcefirsty + (mySamplesPerPixelY>>1) - myOpIDSamplesHalfY;
244  // Find the last sample to read for colour and z gradients
245  const int sourcelastcx = sourcefirstx + ((mySamplesPerPixelX-1)>>1) + myColourSamplesHalfX;
246  const int sourcelastcy = sourcefirsty + ((mySamplesPerPixelY-1)>>1) + myColourSamplesHalfY;
247  const int sourcelastzx = sourcefirstx + ((mySamplesPerPixelX-1)>>1) + myZSamplesHalfX;
248  const int sourcelastzy = sourcefirsty + ((mySamplesPerPixelY-1)>>1) + myZSamplesHalfY;
249  const int sourcelastox = sourcefirstx + ((mySamplesPerPixelX-1)>>1) + myOpIDSamplesHalfX;
250  const int sourcelastoy = sourcefirsty + ((mySamplesPerPixelY-1)>>1) + myOpIDSamplesHalfY;
251  // Find the first and last that will be read
252  int sourcefirstrx = sourcefirstx;
253  int sourcefirstry = sourcefirsty;
254  int sourcelastrx = sourcelastx;
255  int sourcelastry = sourcelasty;
256  if (myUseColourGradient)
257  {
258  sourcefirstrx = SYSmin(sourcefirstrx, sourcefirstcx);
259  sourcefirstry = SYSmin(sourcefirstry, sourcefirstcy);
260  sourcelastrx = SYSmax(sourcelastrx, sourcelastcx);
261  sourcelastry = SYSmax(sourcelastry, sourcelastcy);
262  }
263  if (myUseZGradient)
264  {
265  sourcefirstrx = SYSmin(sourcefirstrx, sourcefirstzx);
266  sourcefirstry = SYSmin(sourcefirstry, sourcefirstzy);
267  sourcelastrx = SYSmax(sourcelastrx, sourcelastzx);
268  sourcelastry = SYSmax(sourcelastry, sourcelastzy);
269  }
270  if (myUseOpID)
271  {
272  sourcefirstrx = SYSmin(sourcefirstrx, sourcefirstox);
273  sourcefirstry = SYSmin(sourcefirstry, sourcefirstoy);
274  sourcelastrx = SYSmax(sourcelastrx, sourcelastox);
275  sourcelastry = SYSmax(sourcelastry, sourcelastoy);
276  }
277 
278  // Initialize data for use by each of the edge detection methods
279  bool opidset = false;
280  float opid;
281 
282  UT_StackBuffer<float> colourgradientx(vectorsize);
283  UT_StackBuffer<float> colourgradienty(vectorsize);
284  if (myUseColourGradient)
285  {
286  for (int i = 0; i < vectorsize; ++i)
287  colourgradientx[i] = 0;
288  for (int i = 0; i < vectorsize; ++i)
289  colourgradienty[i] = 0;
290  }
291 
292  // The z channel only has one component, so its gradient doesn't need arrays.
293  float zgradientx = 0;
294  float zgradienty = 0;
295  float zaverage = 0;
296  bool hasfarz = false;
297  bool hasnonfarz = false;
298 
299  for (int sourcey = sourcefirstry; sourcey <= sourcelastry && !isedge; ++sourcey)
300  {
301  for (int sourcex = sourcefirstrx; sourcex <= sourcelastrx; ++sourcex)
302  {
303  int sourcei = sourcex + sourcewidth*sourcey;
304 
305  if (myUseOpID && sourcex >= sourcefirstox && sourcex <= sourcelastox && sourcey >= sourcefirstoy && sourcey <= sourcelastoy)
306  {
307  // Found edge if not all of the op IDs of the samples match
308  if (!opidset)
309  {
310  opid = opiddata[sourcei];
311  opidset = true;
312  }
313  else if (opid != opiddata[sourcei])
314  {
315  isedge = true;
316  break;
317  }
318  }
319 
320  if (myUseColourGradient || myUseZGradient)
321  {
322  // Find (x,y) of sample relative to *middle* of pixel
323  float x = (float(sourcex) - 0.5f*float(sourcelastx + sourcefirstx))/float(mySamplesPerPixelX);
324  float y = (float(sourcey) - 0.5f*float(sourcelasty + sourcefirsty))/float(mySamplesPerPixelY);
325 
326  if (myUseColourGradient && sourcex >= sourcefirstcx && sourcex <= sourcelastcx && sourcey >= sourcefirstcy && sourcey <= sourcelastcy)
327  {
328  for (int i = 0; i < vectorsize; ++i)
329  colourgradientx[i] += x*colourdata[vectorsize*sourcei + i];
330  for (int i = 0; i < vectorsize; ++i)
331  colourgradienty[i] += y*colourdata[vectorsize*sourcei + i];
332  }
333  if (myUseZGradient && sourcex >= sourcefirstzx && sourcex <= sourcelastzx && sourcey >= sourcefirstzy && sourcey <= sourcelastzy)
334  {
335  // Special case for if nothing was hit, since may sum past FLT_MAX,
336  // and then values won't cancel out to get a gradient of zero.
337  bool farz = (zdata[sourcei] >= 1.0e37);
338  hasfarz |= farz;
339  hasnonfarz |= !farz;
340  if (hasfarz && hasnonfarz)
341  {
342  isedge = true;
343  break;
344  }
345  if (!farz)
346  {
347  zgradientx += x*zdata[sourcei];
348  zgradienty += y*zdata[sourcei];
349  zaverage += zdata[sourcei];
350  }
351  }
352  }
353  }
354  }
355 
356  if (!isedge)
357  {
358  if (myUseColourGradient)
359  {
360  int nx = sourcelastcx-sourcefirstcx+1;
361  int ny = sourcelastcy-sourcefirstcy+1;
362  for (int i = 0; i < vectorsize; ++i)
363  colourgradientx[i] /= (ny*myColourSumX2);
364  float mag2x = 0;
365  for (int i = 0; i < vectorsize; ++i)
366  mag2x += colourgradientx[i]*colourgradientx[i];
367 
368  for (int i = 0; i < vectorsize; ++i)
369  colourgradienty[i] /= (nx*myColourSumY2);
370  float mag2y = 0;
371  for (int i = 0; i < vectorsize; ++i)
372  mag2y += colourgradienty[i]*colourgradienty[i];
373 
374  if ((mag2x + mag2y) >= myColourGradientThreshold*myColourGradientThreshold)
375  isedge = true;
376  }
377  if (!isedge && myUseZGradient && hasnonfarz)
378  {
379  int nx = sourcelastzx-sourcefirstzx+1;
380  int ny = sourcelastzy-sourcefirstzy+1;
381  zaverage /= float(nx)*float(ny);
382  zgradientx /= (ny*myZSumX2*zaverage);
383  zgradienty /= (nx*myZSumY2*zaverage);
384  float mag2x = zgradientx*zgradientx;
385  float mag2y = zgradienty*zgradienty;
386 
387  if ((mag2x + mag2y) >= myZGradientThreshold*myZGradientThreshold)
388  isedge = true;
389  }
390  }
391 
392  float value = isedge ? 1.0f : 0.0f;
393  for (int i = 0; i < vectorsize; ++i, ++destination)
394  *destination = value;
395  }
396  }
397 }
#define SYSmax(a, b)
Definition: SYS_Math.h:1538
typedef int(APIENTRYP RE_PFNGLXSWAPINTERVALSGIPROC)(int)
RAY_PixelFilter * clone() const override
void stripOptions(const char *options)
int found(int opt) const
Definition: UT_Args.h:61
GLint y
Definition: glcorearb.h:103
void prepFilter(int samplesperpixelx, int samplesperpixely) override
static int getSpecialChannelIdx(const RAY_Imager &imager, RAY_SpecialChannel channel)
Get the channel index of the specified special channel.
GLfloat f
Definition: glcorearb.h:1926
fpreal fargp(int opt, int which=0) const
Definition: UT_Args.h:82
UT_Vector3T< T > SYSclamp(const UT_Vector3T< T > &v, const UT_Vector3T< T > &min, const UT_Vector3T< T > &max)
Definition: UT_Vector3.h:1057
GLsizei GLsizei GLchar * source
Definition: glcorearb.h:803
RAY_PixelFilter * allocPixelFilter(const char *name)
SYS_API fpreal32 SYSfloor(fpreal32 val)
GLuint const GLchar * name
Definition: glcorearb.h:786
GLint GLenum GLint x
Definition: glcorearb.h:409
void filter(float *destination, int vectorsize, const RAY_SampleBuffer &source, int channel, int sourcewidth, int sourceheight, int destwidth, int destheight, int destxoffsetinsource, int destyoffsetinsource, const RAY_Imager &imager) const override
void getFilterWidth(float &x, float &y) const override
static void addSpecialChannel(RAY_Imager &imager, RAY_SpecialChannel channel)
Indicate that the specified special channel must be added for this filter.
GLbyte ny
Definition: glad.h:2247
**If you just want to fire and args
Definition: thread.h:609
GLint GLsizei width
Definition: glcorearb.h:103
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
Definition: core.h:1131
void initialize(int argc, const char *const argv[])
void addNeededSpecialChannels(RAY_Imager &imager) override
#define SYSmin(a, b)
Definition: SYS_Math.h:1539
static const float * getSampleData(const RAY_SampleBuffer &source, int channel)
Get the data for the channel with the specified index.
void setArgs(int argc, const char *const argv[]) override