Ptex
PtexSeparableKernel.h
Go to the documentation of this file.
1 #ifndef PtexSeparableKernel_h
2 #define PtexSeparableKernel_h
3 
4 /*
5 PTEX SOFTWARE
6 Copyright 2009 Disney Enterprises, Inc. All rights reserved
7 
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are
10 met:
11 
12  * Redistributions of source code must retain the above copyright
13  notice, this list of conditions and the following disclaimer.
14 
15  * Redistributions in binary form must reproduce the above copyright
16  notice, this list of conditions and the following disclaimer in
17  the documentation and/or other materials provided with the
18  distribution.
19 
20  * The names "Disney", "Walt Disney Pictures", "Walt Disney Animation
21  Studios" or the names of its contributors may NOT be used to
22  endorse or promote products derived from this software without
23  specific prior written permission from Walt Disney Pictures.
24 
25 Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND
26 CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
27 BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
28 FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED.
29 IN NO EVENT SHALL WALT DISNEY PICTURES, THE COPYRIGHT HOLDER OR
30 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND BASED ON ANY
34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
37 */
38 
39 #include <assert.h>
40 #include <algorithm>
41 #include <numeric>
42 #include "Ptexture.h"
43 #include "PtexUtils.h"
44 
45 // Separable convolution kernel
46 class PtexSeparableKernel : public Ptex {
47  public:
48  Res res; // resolution that kernel was built for
49  int u, v; // uv offset within face data
50  int uw, vw; // kernel width
51  float* ku; // kernel weights in u
52  float* kv; // kernel weights in v
53  static const int kmax = 10; // max kernel width
54  float kubuff[kmax];
55  float kvbuff[kmax];
56 
58  : res(0), u(0), v(0), uw(0), vw(0), ku(kubuff), kv(kvbuff) {}
59 
61  {
62  set(k.res, k.u, k.v, k.uw, k.vw, k.ku, k.kv);
63  }
64 
66  {
67  set(k.res, k.u, k.v, k.uw, k.vw, k.ku, k.kv);
68  return *this;
69  }
70 
71  void set(Res resVal,
72  int uVal, int vVal,
73  int uwVal, int vwVal,
74  const float* kuVal, const float* kvVal)
75  {
76  assert(uwVal <= kmax && vwVal <= kmax);
77  res = resVal;
78  u = uVal;
79  v = vVal;
80  uw = uwVal;
81  vw = vwVal;
82  memcpy(kubuff, kuVal, sizeof(*ku)*uw);
83  memcpy(kvbuff, kvVal, sizeof(*kv)*vw);
84  ku = kubuff;
85  kv = kvbuff;
86  }
87 
88  void stripZeros()
89  {
90  while (ku[0] == 0) { ku++; u++; uw--; }
91  while (ku[uw-1] == 0) { uw--; }
92  while (kv[0] == 0) { kv++; v++; vw--; }
93  while (kv[vw-1] == 0) { vw--; }
94  assert(uw > 0 && vw > 0);
95  }
96 
97  float weight() const
98  {
99  return accumulate(ku, uw) * accumulate(kv, vw);
100  }
101 
102  void mergeL(BorderMode mode)
103  {
104  int w = -u;
105  if (mode != m_black)
106  ku[w] += accumulate(ku, w);
107  ku += w;
108  uw -= w;
109  u = 0;
110  }
111 
112  void mergeR(BorderMode mode)
113  {
114  int w = uw + u - res.u();
115  float* kp = ku + uw - w;
116  if (mode != m_black)
117  kp[-1] += accumulate(kp, w);
118  uw -= w;
119  }
120 
121  void mergeB(BorderMode mode)
122  {
123  int w = -v;
124  if (mode != m_black)
125  kv[w] += accumulate(kv, w);
126  kv += w;
127  vw -= w;
128  v = 0;
129  }
130 
131  void mergeT(BorderMode mode)
132  {
133  int w = vw + v - res.v();
134  float* kp = kv + vw - w;
135  if (mode != m_black)
136  kp[-1] += accumulate(kp, w);
137  vw -= w;
138  }
139 
141  {
142  // split off left piece of width w into k
143  int w = -u;
144 
145  if (w < uw) {
146  // normal case - split off a portion
147  // res u v uw vw ku kv
148  k.set(res, res.u()-w, v, w, vw, ku, kv);
149 
150  // update local
151  u = 0;
152  uw -= w;
153  ku += w;
154  }
155  else {
156  // entire kernel is split off
157  k = *this;
158  k.u += res.u();
159  u = 0; uw = 0;
160  }
161  }
162 
164  {
165  // split off right piece of width w into k
166  int w = u + uw - res.u();
167 
168  if (w < uw) {
169  // normal case - split off a portion
170  // res u v uw vw ku kv
171  k.set(res, 0, v, w, vw, ku + uw - w, kv);
172 
173  // update local
174  uw -= w;
175  }
176  else {
177  // entire kernel is split off
178  k = *this;
179  k.u -= res.u();
180  u = 0; uw = 0;
181  }
182  }
183 
185  {
186  // split off bottom piece of width w into k
187  int w = -v;
188  if (w < vw) {
189  // normal case - split off a portion
190  // res u v uw vw ku kv
191  k.set(res, u, res.v()-w, uw, w, ku, kv);
192 
193  // update local
194  v = 0;
195  vw -= w;
196  kv += w;
197  }
198  else {
199  // entire kernel is split off
200  k = *this;
201  k.v += res.v();
202  v = 0; vw = 0;
203  }
204  }
205 
207  {
208  // split off top piece of width w into k
209  int w = v + vw - res.v();
210  if (w < vw) {
211  // normal case - split off a portion
212  // res u v uw vw ku kv
213  k.set(res, u, 0, uw, w, ku, kv + vw - w);
214 
215  // update local
216  vw -= w;
217  }
218  else {
219  // entire kernel is split off
220  k = *this;
221  k.v -= res.v();
222  v = 0; vw = 0;
223  }
224  }
225 
226  void flipU()
227  {
228  u = res.u() - u - uw;
229  std::reverse(ku, ku+uw);
230  }
231 
232  void flipV()
233  {
234  v = res.v() - v - vw;
235  std::reverse(kv, kv+vw);
236  }
237 
238  void swapUV()
239  {
240  res.swapuv();
241  std::swap(u, v);
242  std::swap(uw, vw);
243  std::swap(ku, kv);
244  }
245 
246  void rotate(int rot)
247  {
248  // rotate kernel 'rot' steps ccw
249  switch (rot & 3) {
250  default: return;
251  case 1: flipU(); swapUV(); break;
252  case 2: flipU(); flipV(); break;
253  case 3: flipV(); swapUV(); break;
254  }
255  }
256 
257  bool adjustMainToSubface(int eid)
258  {
259  // to adjust the kernel for the subface, we must adjust the res down and offset the uv coords
260  // however, if the res is already zero, we must upres the kernel first
261  if (res.ulog2 == 0) upresU();
262  if (res.vlog2 == 0) upresV();
263 
264  if (res.ulog2 > 0) res.ulog2--;
265  if (res.vlog2 > 0) res.vlog2--;
266 
267  // offset uv coords and determine whether target subface is the primary one
268  bool primary = 0;
269  int resu = res.u(), resv = res.v();
270  switch (eid&3) {
271  case e_bottom:
272  primary = (u < resu);
273  v -= resv;
274  if (!primary) u -= resu;
275  break;
276  case e_right:
277  primary = (v < resv);
278  if (!primary) v -= resv;
279  break;
280  case e_top:
281  primary = (u >= resu);
282  if (primary) u -= resu;
283  break;
284  case e_left:
285  primary = (v >= resv);
286  u -= resu;
287  if (primary) v -= resv;
288  break;
289  }
290  return primary;
291  }
292 
293  void adjustSubfaceToMain(int eid)
294  {
295  switch (eid&3) {
296  case e_bottom: v += res.v(); break;
297  case e_right: break;
298  case e_top: u += res.u(); break;
299  case e_left: u += res.u(); v += res.v(); break;
300  }
301  res.ulog2++; res.vlog2++;
302  }
303 
304  void downresU()
305  {
306  float* src = ku;
307  float* dst = ku;
308 
309  // skip odd leading sample (if any)
310  if (u & 1) {
311  dst++;
312  src++;
313  uw--;
314  }
315 
316  // combine even pairs
317  for (int i = uw/2; i > 0; i--) {
318  *dst++ = src[0] + src[1];
319  src += 2;
320  }
321 
322  // copy odd trailing sample (if any)
323  if (uw & 1) {
324  *dst++ = *src++;
325  }
326 
327  // update state
328  u /= 2;
329  uw = int(dst - ku);
330  res.ulog2--;
331  }
332 
333  void downresV()
334  {
335  float* src = kv;
336  float* dst = kv;
337 
338  // skip odd leading sample (if any)
339  if (v & 1) {
340  dst++;
341  src++;
342  vw--;
343  }
344 
345  // combine even pairs
346  for (int i = vw/2; i > 0; i--) {
347  *dst++ = src[0] + src[1];
348  src += 2;
349  }
350 
351  // copy odd trailing sample (if any)
352  if (vw & 1) {
353  *dst++ = *src++;
354  }
355 
356  // update state
357  v /= 2;
358  vw = int(dst - kv);
359  res.vlog2--;
360  }
361 
362  void upresU()
363  {
364  float* src = ku + uw-1;
365  float* dst = ku + uw*2-2;
366  for (int i = uw; i > 0; i--) {
367  dst[0] = dst[1] = *src-- / 2;
368  dst -=2;
369  }
370  uw *= 2;
371  u *= 2;
372  res.ulog2++;
373  }
374 
375  void upresV()
376  {
377  float* src = kv + vw-1;
378  float* dst = kv + vw*2-2;
379  for (int i = vw; i > 0; i--) {
380  dst[0] = dst[1] = *src-- / 2;
381  dst -=2;
382  }
383  vw *= 2;
384  v *= 2;
385  res.vlog2++;
386  }
387 
388  float makeSymmetric(float initialWeight)
389  {
390  assert(u == 0 && v == 0);
391 
392  // downres higher-res dimension until equal
393  if (res.ulog2 > res.vlog2) {
394  do { downresU(); } while(res.ulog2 > res.vlog2);
395  }
396  else if (res.vlog2 > res.ulog2) {
397  do { downresV(); } while (res.vlog2 > res.ulog2);
398  }
399 
400  // truncate excess samples in longer dimension
401  uw = vw = PtexUtils::min(uw, vw);
402 
403  // combine corresponding u and v samples and compute new kernel weight
404  float newWeight = 0;
405  for (int i = 0; i < uw; i++) {
406  float sum = ku[i] + kv[i];
407  ku[i] = kv[i] = sum;
408  newWeight += sum;
409  }
410  newWeight *= newWeight; // equivalent to k.weight() ( = sum(ku)*sum(kv) )
411 
412  // compute scale factor to compensate for weight change
413  float scale = newWeight == 0 ? 1.f : initialWeight / newWeight;
414 
415  // Note: a sharpening kernel (like Mitchell) can produce
416  // negative weights which may cancel out when adding the two
417  // kernel axes together, and this can cause the compensation
418  // scale factor to spike up. We expect the scale factor to be
419  // less than one in "normal" cases (i.e. ku*kv <= (ku+kv)^2 if ku
420  // and kv are both positive), so clamping to -1..1 will have
421  // no effect on positive kernels. If there are negative
422  // weights, the clamping will just limit the amount of
423  // sharpening happening at the corners, and the result will
424  // still be smooth.
425 
426  // clamp scale factor to -1..1 range
427  if (scale >= 1) {
428  // scale by 1 (i.e. do nothing)
429  }
430  else {
431  if (scale < -1) {
432  // a negative scale means the original kernel had an overall negative weight
433  // after making symmetric, the kernel will always be positive
434  // scale ku by -1
435  // note: choice of u is arbitrary; we could have scaled u or v (but not both)
436  for (int i = 0; i < uw; i++) ku[i] *= -1;
437  newWeight = -newWeight;
438  }
439  else {
440  // scale ku to restore initialWeight (again, choice of u instead of v is arbitrary)
441  for (int i = 0; i < uw; i++) ku[i] *= scale;
442  newWeight = initialWeight;
443  }
444  }
445  return newWeight;
446  }
447 
448  void apply(float* dst, void* data, DataType dt, int nChan, int nTxChan)
449  {
450  // dispatch specialized apply function
451  ApplyFn fn = applyFunctions[(nChan!=nTxChan)*20 + ((unsigned)nChan<=4)*nChan*4 + dt];
452  fn(*this, dst, data, nChan, nTxChan);
453  }
454 
455  void applyConst(float* dst, void* data, DataType dt, int nChan)
456  {
457  PtexUtils::applyConst(weight(), dst, data, dt, nChan);
458  }
459 
460  private:
461  typedef void (*ApplyFn)(PtexSeparableKernel& k, float* dst, void* data, int nChan, int nTxChan);
462  typedef void (*ApplyConstFn)(float weight, float* dst, void* data, int nChan);
465  static inline float accumulate(const float* p, int n)
466  {
467  float result = 0;
468  for (const float* e = p + n; p != e; p++) result += *p;
469  return result;
470  }
471 };
472 
473 #endif
Left edge, from UV (0,1) to (0,0)
Definition: Ptexture.h:103
void set(Res resVal, int uVal, int vVal, int uwVal, int vwVal, const float *kuVal, const float *kvVal)
static const int kmax
Common data structures and enums used throughout the API.
Definition: Ptexture.h:73
void apply(float *dst, void *data, DataType dt, int nChan, int nTxChan)
void applyConst(float *dst, void *data, DataType dt, int nChan)
Top edge, from UV (1,1) to (0,1)
Definition: Ptexture.h:102
static float accumulate(const float *p, int n)
DataType
Type of data stored in texture file.
Definition: Ptexture.h:83
void mergeL(BorderMode mode)
static T min(T a, T b)
Definition: PtexUtils.h:116
void mergeB(BorderMode mode)
PtexSeparableKernel & operator=(const PtexSeparableKernel &k)
bool adjustMainToSubface(int eid)
float makeSymmetric(float initialWeight)
int v() const
V resolution in texels.
Definition: Ptexture.h:181
static ApplyFn applyFunctions[40]
Bottom edge, from UV (0,0) to (1,0)
Definition: Ptexture.h:100
static ApplyConstFn applyConstFunctions[20]
BorderMode
How to handle mesh border when filtering.
Definition: Ptexture.h:91
int u() const
U resolution in texels.
Definition: Ptexture.h:178
int8_t vlog2
log base 2 of v resolution, in texels
Definition: Ptexture.h:163
void mergeR(BorderMode mode)
void adjustSubfaceToMain(int eid)
void(* ApplyFn)(PtexSeparableKernel &k, float *dst, void *data, int nChan, int nTxChan)
void mergeT(BorderMode mode)
void(* ApplyConstFn)(float weight, float *dst, void *data, int nChan)
texel beyond border are assumed to be black
Definition: Ptexture.h:93
void splitL(PtexSeparableKernel &k)
void swapuv()
Swap the u and v resolution values in place.
Definition: Ptexture.h:205
void splitB(PtexSeparableKernel &k)
int8_t ulog2
log base 2 of u resolution, in texels
Definition: Ptexture.h:162
Right edge, from UV (1,0) to (1,1)
Definition: Ptexture.h:101
void splitT(PtexSeparableKernel &k)
Pixel resolution of a given texture.
Definition: Ptexture.h:161
PtexSeparableKernel(const PtexSeparableKernel &k)
Public API classes for reading, writing, caching, and filtering Ptex files.
void splitR(PtexSeparableKernel &k)
static void applyConst(float weight, float *dst, void *data, Ptex::DataType dt, int nChan)
Definition: PtexUtils.h:204