Panda3D
pnm-image-filter.cxx
1 // Filename: pnm-image-filter.cxx
2 // Created by:
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 // The functions in this module support spatial filtering of an image by
16 // convolution with an (almost) arbitrary kernel. There are a broad class of
17 // image filters to which this principle applies, including box filtering,
18 // Bartlett, and Gaussian.
19 
20 // This particular approach breaks the 2-d kernel into two 1-d kernels, which
21 // improves performance at the expense of limiting the types of filters that
22 // may be applied. In general, any of the square-based kernels are still
23 // applicable; the only circle-based kernel that still works with this
24 // approach is Gaussian. Furthermore, the implementation assume that the
25 // kernel is symmetric about zero.
26 
27 // The image is filtered first along one axis, then along the other. This
28 // decreases the complexity of the convolution operation: it is faster to
29 // convolve twice with a one-dimensional kernel than once with a two-
30 // dimensional kernel. In the interim, a temporary matrix of type StoreType
31 // (a numeric type, described below) is built which contains the results
32 // from the first convolution. The entire process is then repeated for
33 // each channel in the image.
34 
35 #include "pandabase.h"
36 #include <math.h>
37 #include "cmath.h"
38 #include "thread.h"
39 
40 #include "pnmImage.h"
41 #include "pfmFile.h"
42 
43 // WorkType is an abstraction that allows the filtering process to be
44 // recompiled to use either floating-point or integer arithmetic. On SGI
45 // machines, there doesn't seem to be much of a performance difference--
46 // if anything, the integer arithmetic is slower--though certainly other
47 // architectures may differ.
48 
49 // A StoreType is a numeric type that is used to store the intermediate values
50 // of the filtering computation; temporary calculations are performed using
51 // WorkTypes. source_max represents the largest value that will be stored in
52 // a StoreType, while filter_max represents the largest value that will
53 // multiplied by it as a weighted filter (source_max * filter_max must fit
54 // comfortably within a WorkType, with room to spare).
55 
56 // Floating-point arithmetic is slightly faster and slightly more precise,
57 // but the main reason to use it is that it is conceptually easy to understand.
58 // All values are scaled in the range 0..1, as they should be.
59 
60 // The biggest reason to use integer arithmetic is space. A table of
61 // StoreTypes must be allocated to match the size of the image. On an SGI,
62 // sizeof(float) is 4, while sizeof(short) is 2 and sizeof(char) is, of
63 // course, 1. Since the source precision is probably 8 bits anyway (there
64 // are usually 8 bits per channel), it doesn't cost any precision at all to
65 // use shorts, and not very much to use chars.
66 
67 // To use double-precision floating point, 8 bytes: (strictly for the neurotic)
68 /*
69 typedef double WorkType;
70 typedef double StoreType;
71 static const WorkType source_max = 1.0;
72 static const WorkType filter_max = 1.0;
73 */
74 
75 // To use single-precision floating point, 4 bytes:
76 typedef float WorkType;
77 typedef float StoreType;
78 static const WorkType source_max = 1.0f;
79 static const WorkType filter_max = 1.0f;
80 
81 /*
82 // To use 16-bit integer arithmetic, 2 bytes:
83 typedef unsigned long WorkType;
84 typedef unsigned short StoreType;
85 static const WorkType source_max = 65535;
86 static const WorkType filter_max = 255;
87 */
88 
89 /*
90 // To use 8-bit integer arithmetic, 1 byte:
91 typedef unsigned long WorkType;
92 typedef unsigned char StoreType;
93 static const WorkType source_max = 255;
94 static const WorkType filter_max = 255;
95 */
96 
97 // filter_row() filters a single row by convolving with a one-dimensional
98 // kernel filter. The kernel is defined by an array of weights in filter[],
99 // where the ith element of filter corresponds to abs(d * scale), if scale>1.0,
100 // and abs(d), if scale<=1.0, where d is the offset from the center and varies
101 // from -filter_width to filter_width.
102 
103 // Note that filter_width is not necessarily the length of the array; it is
104 // the radius of interest of the filter function. The array may need to be
105 // larger (by a factor of scale), to adequately cover all the values.
106 
107 static void
108 filter_row(StoreType dest[], int dest_len,
109  const StoreType source[], int source_len,
110  float scale, // == dest_len / source_len
111  const WorkType filter[],
112  float filter_width) {
113  // If we are expanding the row (scale > 1.0), we need to look at a
114  // fractional granularity. Hence, we scale our filter index by
115  // scale. If we are compressing (scale < 1.0), we don't need to
116  // fiddle with the filter index, so we leave it at one.
117 
118  float iscale;
119  if (scale < 1.0f) {
120  iscale = 1.0f;
121  filter_width /= scale;
122  } else {
123  iscale = scale;
124  }
125 
126  for (int dest_x = 0; dest_x < dest_len; dest_x++) {
127  // The additional offset of 0.5 keeps the pixel centered.
128  float center = (dest_x + 0.5f) / scale - 0.5f;
129 
130  // left and right are the starting and ending ranges of the radius of
131  // interest of the filter function. We need to apply the filter to each
132  // value in this range.
133  int left = max((int)cfloor(center - filter_width), 0);
134  int right = min((int)cceil(center + filter_width), source_len - 1);
135 
136  // right_center is the point just to the right of the center. This
137  // allows us to flip the sign of the offset when we cross the center point.
138  int right_center = (int)cceil(center);
139 
140  WorkType net_weight = 0;
141  WorkType net_value = 0;
142 
143  int index, source_x;
144 
145  // This loop is broken into two pieces--the left of center and the right
146  // of center--so we don't have to incur the overhead of calling fabs()
147  // each time through the loop.
148  for (source_x = left; source_x < right_center; source_x++) {
149  index = (int)(iscale * (center - source_x) + 0.5f);
150  net_value += filter[index] * source[source_x];
151  net_weight += filter[index];
152  }
153 
154  for (; source_x <= right; source_x++) {
155  index = (int)(iscale * (source_x - center) + 0.5f);
156  net_value += filter[index] * source[source_x];
157  net_weight += filter[index];
158  }
159 
160  if (net_weight > 0) {
161  dest[dest_x] = (StoreType)(net_value / net_weight);
162  } else {
163  dest[dest_x] = 0;
164  }
165  }
167 }
168 
169 // As above, but we also accept an array of weight values per
170 // element, to support scaling a sparse array (as in a PfmFile).
171 static void
172 filter_sparse_row(StoreType dest[], StoreType dest_weight[], int dest_len,
173  const StoreType source[], const StoreType source_weight[], int source_len,
174  float scale, // == dest_len / source_len
175  const WorkType filter[],
176  float filter_width) {
177  // If we are expanding the row (scale > 1.0), we need to look at a
178  // fractional granularity. Hence, we scale our filter index by
179  // scale. If we are compressing (scale < 1.0), we don't need to
180  // fiddle with the filter index, so we leave it at one.
181 
182  float iscale;
183  if (scale < 1.0) {
184  iscale = 1.0;
185  filter_width /= scale;
186  } else {
187  iscale = scale;
188  }
189 
190  for (int dest_x = 0; dest_x < dest_len; dest_x++) {
191  // The additional offset of 0.5 keeps the pixel centered.
192  float center = (dest_x + 0.5f) / scale - 0.5f;
193 
194  // left and right are the starting and ending ranges of the radius of
195  // interest of the filter function. We need to apply the filter to each
196  // value in this range.
197  int left = max((int)cfloor(center - filter_width), 0);
198  int right = min((int)cceil(center + filter_width), source_len - 1);
199 
200  // right_center is the point just to the right of the center. This
201  // allows us to flip the sign of the offset when we cross the center point.
202  int right_center = (int)cceil(center);
203 
204  WorkType net_weight = 0;
205  WorkType net_value = 0;
206 
207  int index, source_x;
208 
209  // This loop is broken into two pieces--the left of center and the right
210  // of center--so we don't have to incur the overhead of calling fabs()
211  // each time through the loop.
212  for (source_x = left; source_x < right_center; source_x++) {
213  index = (int)(iscale * (center - source_x) + 0.5f);
214  net_value += filter[index] * source[source_x] * source_weight[source_x];
215  net_weight += filter[index] * source_weight[source_x];
216  }
217 
218  for (; source_x <= right; source_x++) {
219  index = (int)(iscale * (source_x - center) + 0.5f);
220  net_value += filter[index] * source[source_x] * source_weight[source_x];
221  net_weight += filter[index] * source_weight[source_x];
222  }
223 
224  if (net_weight > 0) {
225  dest[dest_x] = (StoreType)(net_value / net_weight);
226  } else {
227  dest[dest_x] = 0;
228  }
229  dest_weight[dest_x] = (StoreType)net_weight;
230  }
232 }
233 
234 
235 // The various filter functions are called before each axis scaling to build
236 // an kernel array suitable for the given scaling factor. Given a scaling
237 // ratio of the axis (dest_len / source_len), and a width parameter supplied
238 // by the user, they must build an array of filter values (described above)
239 // and also set the radius of interest of the filter function.
240 
241 // The values of the elements of filter must completely cover the range
242 // 0..filter_max; the array must have enough elements to include all indices
243 // corresponding to values in the range -filter_width to filter_width.
244 
245 typedef void FilterFunction(float scale, float width,
246  WorkType *&filter, float &filter_width);
247 
248 static void
249 box_filter_impl(float scale, float width,
250  WorkType *&filter, float &filter_width) {
251  float fscale;
252  if (scale < 1.0) {
253  // If we are compressing the image, we want to expand the range of
254  // the filter function to prevent dropping below the Nyquist rate.
255  // Hence, we divide by scale.
256  fscale = 1.0 / scale;
257 
258  } else {
259  // If we are expanding the image, we want to increase the granularity
260  // of the filter function since we will need to access fractional cel
261  // values. Hence, we multiply by scale.
262  fscale = scale;
263  }
264  filter_width = width;
265  int actual_width = (int)cceil((filter_width + 1) * fscale) + 1;
266 
267  filter = (WorkType *)PANDA_MALLOC_ARRAY(actual_width * sizeof(WorkType));
268 
269  for (int i = 0; i < actual_width; i++) {
270  filter[i] = (i <= filter_width * fscale) ? filter_max : 0;
271  }
272 }
273 
274 static void
275 gaussian_filter_impl(float scale, float width,
276  WorkType *&filter, float &filter_width) {
277  float fscale;
278  if (scale < 1.0) {
279  // If we are compressing the image, we want to expand the range of
280  // the filter function to prevent dropping below the Nyquist rate.
281  // Hence, we divide by scale (to make fscale larger).
282  fscale = 1.0 / scale;
283  } else {
284 
285  // If we are expanding the image, we want to increase the granularity
286  // of the filter function since we will need to access fractional cel
287  // values. Hence, we multiply by scale (to make fscale larger).
288  fscale = scale;
289  }
290 
291  float sigma = width/2;
292  filter_width = 3.0 * sigma;
293  int actual_width = (int)cceil((filter_width + 1) * fscale);
294 
295  // G(x, y) = (1/(2 pi sigma^2)) * exp( - (x^2 + y^2) / (2 sigma^2))
296 
297  // (We can throw away the initial factor, since these weights will all
298  // be normalized; and we're only computing a 1-dimensional function,
299  // so we can ignore the y^2.)
300 
301  filter = (WorkType *)PANDA_MALLOC_ARRAY(actual_width * sizeof(WorkType));
302  float div = 2 * sigma * sigma;
303 
304  for (int i = 0; i < actual_width; i++) {
305  float x = i / fscale;
306  filter[i] = (WorkType)(filter_max * exp(-x*x / div));
307  // The highest value of the exp function in this range is always 1.0,
308  // at index value 0. Thus, we scale the whole range by filter_max,
309  // to produce a filter in the range [0..filter_max].
310  }
311 }
312 
313 
314 // We have a function, defined in pnm-image-filter-core.cxx, that will scale
315 // an image in both X and Y directions for a particular channel, by setting
316 // up the temporary matrix appropriately and calling the above functions.
317 
318 // What we really need is a series of such functions, one for each channel,
319 // and also one to scale by X first, and one to scale by Y first. This sounds
320 // a lot like a C++ template: we want to compile the same function several
321 // times to work on slightly different sorts of things each time. However,
322 // the things we want to vary are the particular member functions of PNMImage
323 // that we call (e.g. Red(), Green(), etc.), and we can't declare a template
324 // of member functions, only of types.
325 
326 // It's doable using templates. It would involve the declaration of
327 // lots of silly little functor objects. This is much more compact
328 // and no more difficult to read.
329 
330 // The function in pnm-image-filter-core.cxx uses macros to access the member
331 // functions of PNMImage. Hence, we only need to redefine those macros
332 // with each instance of the function to cause each instance to operate on
333 // the correct member.
334 
335 
336 // These instances scale by X first, then by Y.
337 
338 #define FUNCTION_NAME filter_red_xy
339 #define IMAGETYPE PNMImage
340 #define ASIZE get_x_size
341 #define BSIZE get_y_size
342 #define GETVAL(a, b, channel) get_red(a, b)
343 #define SETVAL(a, b, channel, v) set_red(a, b, v)
344 #include "pnm-image-filter-core.cxx"
345 #undef SETVAL
346 #undef GETVAL
347 #undef BSIZE
348 #undef ASIZE
349 #undef IMAGETYPE
350 #undef FUNCTION_NAME
351 
352 #define FUNCTION_NAME filter_green_xy
353 #define IMAGETYPE PNMImage
354 #define ASIZE get_x_size
355 #define BSIZE get_y_size
356 #define GETVAL(a, b, channel) get_green(a, b)
357 #define SETVAL(a, b, channel, v) set_green(a, b, v)
358 #include "pnm-image-filter-core.cxx"
359 #undef SETVAL
360 #undef GETVAL
361 #undef BSIZE
362 #undef ASIZE
363 #undef IMAGETYPE
364 #undef FUNCTION_NAME
365 
366 #define FUNCTION_NAME filter_blue_xy
367 #define IMAGETYPE PNMImage
368 #define ASIZE get_x_size
369 #define BSIZE get_y_size
370 #define GETVAL(a, b, channel) get_blue(a, b)
371 #define SETVAL(a, b, channel, v) set_blue(a, b, v)
372 #include "pnm-image-filter-core.cxx"
373 #undef SETVAL
374 #undef GETVAL
375 #undef BSIZE
376 #undef ASIZE
377 #undef IMAGETYPE
378 #undef FUNCTION_NAME
379 
380 #define FUNCTION_NAME filter_gray_xy
381 #define IMAGETYPE PNMImage
382 #define ASIZE get_x_size
383 #define BSIZE get_y_size
384 #define GETVAL(a, b, channel) get_bright(a, b)
385 #define SETVAL(a, b, channel, v) set_xel(a, b, v)
386 #include "pnm-image-filter-core.cxx"
387 #undef SETVAL
388 #undef GETVAL
389 #undef BSIZE
390 #undef ASIZE
391 #undef IMAGETYPE
392 #undef FUNCTION_NAME
393 
394 #define FUNCTION_NAME filter_alpha_xy
395 #define IMAGETYPE PNMImage
396 #define ASIZE get_x_size
397 #define BSIZE get_y_size
398 #define GETVAL(a, b, channel) get_alpha(a, b)
399 #define SETVAL(a, b, channel, v) set_alpha(a, b, v)
400 #include "pnm-image-filter-core.cxx"
401 #undef SETVAL
402 #undef GETVAL
403 #undef BSIZE
404 #undef ASIZE
405 #undef IMAGETYPE
406 #undef FUNCTION_NAME
407 
408 
409 // These instances scale by Y first, then by X.
410 
411 #define FUNCTION_NAME filter_red_yx
412 #define IMAGETYPE PNMImage
413 #define ASIZE get_y_size
414 #define BSIZE get_x_size
415 #define GETVAL(a, b, channel) get_red(b, a)
416 #define SETVAL(a, b, channel, v) set_red(b, a, v)
417 #include "pnm-image-filter-core.cxx"
418 #undef SETVAL
419 #undef GETVAL
420 #undef BSIZE
421 #undef ASIZE
422 #undef IMAGETYPE
423 #undef FUNCTION_NAME
424 
425 #define FUNCTION_NAME filter_green_yx
426 #define IMAGETYPE PNMImage
427 #define ASIZE get_y_size
428 #define BSIZE get_x_size
429 #define GETVAL(a, b, channel) get_green(b, a)
430 #define SETVAL(a, b, channel, v) set_green(b, a, v)
431 #include "pnm-image-filter-core.cxx"
432 #undef SETVAL
433 #undef GETVAL
434 #undef BSIZE
435 #undef ASIZE
436 #undef IMAGETYPE
437 #undef FUNCTION_NAME
438 
439 #define FUNCTION_NAME filter_blue_yx
440 #define IMAGETYPE PNMImage
441 #define ASIZE get_y_size
442 #define BSIZE get_x_size
443 #define GETVAL(a, b, channel) get_blue(b, a)
444 #define SETVAL(a, b, channel, v) set_blue(b, a, v)
445 #include "pnm-image-filter-core.cxx"
446 #undef SETVAL
447 #undef GETVAL
448 #undef BSIZE
449 #undef ASIZE
450 #undef IMAGETYPE
451 #undef FUNCTION_NAME
452 
453 #define FUNCTION_NAME filter_gray_yx
454 #define IMAGETYPE PNMImage
455 #define ASIZE get_y_size
456 #define BSIZE get_x_size
457 #define GETVAL(a, b, channel) get_bright(b, a)
458 #define SETVAL(a, b, channel, v) set_xel(b, a, v)
459 #include "pnm-image-filter-core.cxx"
460 #undef SETVAL
461 #undef GETVAL
462 #undef BSIZE
463 #undef ASIZE
464 #undef IMAGETYPE
465 #undef FUNCTION_NAME
466 
467 #define FUNCTION_NAME filter_alpha_yx
468 #define IMAGETYPE PNMImage
469 #define ASIZE get_y_size
470 #define BSIZE get_x_size
471 #define GETVAL(a, b, channel) get_alpha(b, a)
472 #define SETVAL(a, b, channel, v) set_alpha(b, a, v)
473 #include "pnm-image-filter-core.cxx"
474 #undef SETVAL
475 #undef GETVAL
476 #undef BSIZE
477 #undef ASIZE
478 #undef IMAGETYPE
479 #undef FUNCTION_NAME
480 
481 
482 // filter_image pulls everything together, and filters one image into
483 // another. Both images can be the same with no ill effects.
484 static void
485 filter_image(PNMImage &dest, const PNMImage &source,
486  float width, FilterFunction *make_filter) {
487 
488  // We want to scale by the smallest destination axis first, for a
489  // slight performance gain.
490 
491  // In the PNMImage case (unlike the PfmFile case), the channel
492  // parameter is not used. We *could* use it to avoid the
493  // replication of quite so many functions, but we replicate them
494  // anyway, for another tiny performance gain.
495 
496  if (dest.get_x_size() <= dest.get_y_size()) {
497  if (dest.is_grayscale() || source.is_grayscale()) {
498  filter_gray_xy(dest, source, width, make_filter, 0);
499  } else {
500  filter_red_xy(dest, source, width, make_filter, 0);
501  filter_green_xy(dest, source, width, make_filter, 0);
502  filter_blue_xy(dest, source, width, make_filter, 0);
503  }
504 
505  if (dest.has_alpha() && source.has_alpha()) {
506  filter_alpha_xy(dest, source, width, make_filter, 0);
507  }
508 
509  } else {
510  if (dest.is_grayscale() || source.is_grayscale()) {
511  filter_gray_yx(dest, source, width, make_filter, 0);
512  } else {
513  filter_red_yx(dest, source, width, make_filter, 0);
514  filter_green_yx(dest, source, width, make_filter, 0);
515  filter_blue_yx(dest, source, width, make_filter, 0);
516  }
517 
518  if (dest.has_alpha() && source.has_alpha()) {
519  filter_alpha_yx(dest, source, width, make_filter, 0);
520  }
521  }
522 }
523 
524 ////////////////////////////////////////////////////////////////////
525 // Function: PNMImage::box_filter_from
526 // Access: Public
527 // Description: Makes a resized copy of the indicated image into this
528 // one using the indicated filter. The image to be
529 // copied is squashed and stretched to match the
530 // dimensions of the current image, applying the
531 // appropriate filter to perform the stretching.
532 ////////////////////////////////////////////////////////////////////
533 void PNMImage::
534 box_filter_from(float width, const PNMImage &copy) {
535  filter_image(*this, copy, width, &box_filter_impl);
536 }
537 
538 ////////////////////////////////////////////////////////////////////
539 // Function: PNMImage::gaussian_filter_from
540 // Access: Public
541 // Description: Makes a resized copy of the indicated image into this
542 // one using the indicated filter. The image to be
543 // copied is squashed and stretched to match the
544 // dimensions of the current image, applying the
545 // appropriate filter to perform the stretching.
546 ////////////////////////////////////////////////////////////////////
547 void PNMImage::
548 gaussian_filter_from(float width, const PNMImage &copy) {
549  filter_image(*this, copy, width, &gaussian_filter_impl);
550 }
551 
552 // Now we do it again, this time for PfmFile. In this case we also
553 // need to support the sparse variants, since PfmFiles can be
554 // incomplete. However, we don't need to have a different function
555 // for each channel.
556 
557 #define FUNCTION_NAME filter_pfm_xy
558 #define IMAGETYPE PfmFile
559 #define ASIZE get_x_size
560 #define BSIZE get_y_size
561 #define GETVAL(a, b, channel) get_channel(a, b, channel)
562 #define SETVAL(a, b, channel, v) set_channel(a, b, channel, v)
563 #include "pnm-image-filter-core.cxx"
564 #undef SETVAL
565 #undef GETVAL
566 #undef BSIZE
567 #undef ASIZE
568 #undef IMAGETYPE
569 #undef FUNCTION_NAME
570 
571 #define FUNCTION_NAME filter_pfm_yx
572 #define IMAGETYPE PfmFile
573 #define ASIZE get_y_size
574 #define BSIZE get_x_size
575 #define GETVAL(a, b, channel) get_channel(b, a, channel)
576 #define SETVAL(a, b, channel, v) set_channel(b, a, channel, v)
577 #include "pnm-image-filter-core.cxx"
578 #undef SETVAL
579 #undef GETVAL
580 #undef BSIZE
581 #undef ASIZE
582 #undef IMAGETYPE
583 #undef FUNCTION_NAME
584 
585 
586 #define FUNCTION_NAME filter_pfm_sparse_xy
587 #define IMAGETYPE PfmFile
588 #define ASIZE get_x_size
589 #define BSIZE get_y_size
590 #define HASVAL(a, b) has_point(a, b)
591 #define GETVAL(a, b, channel) get_channel(a, b, channel)
592 #define SETVAL(a, b, channel, v) set_channel(a, b, channel, v)
593 #include "pnm-image-filter-sparse-core.cxx"
594 #undef SETVAL
595 #undef GETVAL
596 #undef HASVAL
597 #undef BSIZE
598 #undef ASIZE
599 #undef IMAGETYPE
600 #undef FUNCTION_NAME
601 
602 #define FUNCTION_NAME filter_pfm_sparse_yx
603 #define IMAGETYPE PfmFile
604 #define ASIZE get_y_size
605 #define BSIZE get_x_size
606 #define HASVAL(a, b) has_point(b, a)
607 #define GETVAL(a, b, channel) get_channel(b, a, channel)
608 #define SETVAL(a, b, channel, v) set_channel(b, a, channel, v)
609 #include "pnm-image-filter-sparse-core.cxx"
610 #undef SETVAL
611 #undef GETVAL
612 #undef HASVAL
613 #undef BSIZE
614 #undef ASIZE
615 #undef IMAGETYPE
616 #undef FUNCTION_NAME
617 
618 
619 // filter_image pulls everything together, and filters one image into
620 // another. Both images can be the same with no ill effects.
621 static void
622 filter_image(PfmFile &dest, const PfmFile &source,
623  float width, FilterFunction *make_filter) {
624  int num_channels = min(dest.get_num_channels(), source.get_num_channels());
625 
626  if (source.has_no_data_value()) {
627  // We need to use the sparse variant.
628  if (dest.get_x_size() <= dest.get_y_size()) {
629  for (int ci = 0; ci < num_channels; ++ci) {
630  filter_pfm_sparse_xy(dest, source, width, make_filter, ci);
631  }
632 
633  } else {
634  for (int ci = 0; ci < num_channels; ++ci) {
635  filter_pfm_sparse_yx(dest, source, width, make_filter, ci);
636  }
637  }
638  } else {
639  // We can use the slightly faster fully-specified variant.
640  if (dest.get_x_size() <= dest.get_y_size()) {
641  for (int ci = 0; ci < num_channels; ++ci) {
642  filter_pfm_xy(dest, source, width, make_filter, ci);
643  }
644 
645  } else {
646  for (int ci = 0; ci < num_channels; ++ci) {
647  filter_pfm_yx(dest, source, width, make_filter, ci);
648  }
649  }
650  }
651 }
652 
653 ////////////////////////////////////////////////////////////////////
654 // Function: PfmFile::box_filter_from
655 // Access: Public
656 // Description: Makes a resized copy of the indicated image into this
657 // one using the indicated filter. The image to be
658 // copied is squashed and stretched to match the
659 // dimensions of the current image, applying the
660 // appropriate filter to perform the stretching.
661 ////////////////////////////////////////////////////////////////////
662 void PfmFile::
663 box_filter_from(float width, const PfmFile &copy) {
664  filter_image(*this, copy, width, &box_filter_impl);
665 }
666 
667 ////////////////////////////////////////////////////////////////////
668 // Function: PfmFile::gaussian_filter_from
669 // Access: Public
670 // Description: Makes a resized copy of the indicated image into this
671 // one using the indicated filter. The image to be
672 // copied is squashed and stretched to match the
673 // dimensions of the current image, applying the
674 // appropriate filter to perform the stretching.
675 ////////////////////////////////////////////////////////////////////
676 void PfmFile::
677 gaussian_filter_from(float width, const PfmFile &copy) {
678  filter_image(*this, copy, width, &gaussian_filter_impl);
679 }
680 
681 //
682 // The following functions are support for quick_box_filter().
683 //
684 
685 static INLINE void
686 box_filter_xel(const PNMImage &img,
687  int x, int y, float x_contrib, float y_contrib,
688  LColorf &color, float &pixel_count) {
689 
690  float contrib = x_contrib * y_contrib;
691  color += img.get_xel_a(x, y) * contrib;
692  pixel_count += contrib;
693 }
694 
695 static INLINE void
696 box_filter_line(const PNMImage &image,
697  float x0, int y, float x1, float y_contrib,
698  LColorf &color, float &pixel_count) {
699  int x = (int)x0;
700  // Get the first (partial) xel
701  box_filter_xel(image, x, y, (float)(x+1)-x0, y_contrib,
702  color, pixel_count);
703 
704  int x_last = (int)x1;
705  if (x < x_last) {
706  x++;
707  while (x < x_last) {
708  // Get each consecutive (complete) xel
709  box_filter_xel(image, x, y, 1.0f, y_contrib,
710  color, pixel_count);
711  x++;
712  }
713 
714  // Get the final (partial) xel
715  float x_contrib = x1 - (float)x_last;
716  if (x_contrib > 0.0001f && x < image.get_x_size()) {
717  box_filter_xel(image, x, y, x_contrib, y_contrib,
718  color, pixel_count);
719  }
720  }
721 }
722 
723 static LColorf
724 box_filter_region(const PNMImage &image,
725  float x0, float y0, float x1, float y1) {
726  LColorf color = LColorf::zero();
727  float pixel_count = 0.0f;
728 
729  assert(y0 >= 0 && y1 >= 0);
730 
731  int y = (int)y0;
732  // Get the first (partial) row
733  box_filter_line(image, x0, y, x1, (float)(y+1)-y0,
734  color, pixel_count);
735 
736  int y_last = (int)y1;
737  if (y < y_last) {
738  y++;
739  while (y < y_last) {
740  // Get each consecutive (complete) row
741  box_filter_line(image, x0, y, x1, 1.0f,
742  color, pixel_count);
743  y++;
744  }
745 
746  // Get the final (partial) row
747  float y_contrib = y1 - (float)y_last;
748  if (y_contrib > 0.0001f && y < image.get_y_size()) {
749  box_filter_line(image, x0, y, x1, y_contrib,
750  color, pixel_count);
751  }
752  }
753 
754  //cerr << pixel_count << "\n";
755  color /= pixel_count;
756  return color;
757 }
758 
759 ////////////////////////////////////////////////////////////////////
760 // Function: PNMImage::quick_filter_from
761 // Access: Public
762 // Description: Resizes from the given image, with a fixed radius of
763 // 0.5. This is a very specialized and simple algorithm
764 // that doesn't handle dropping below the Nyquist rate
765 // very well, but is quite a bit faster than the more
766 // general box_filter(), above. If borders are
767 // specified, they will further restrict the size of the
768 // resulting image. There's no point in using
769 // quick_box_filter() on a single image.
770 ////////////////////////////////////////////////////////////////////
771 void PNMImage::
772 quick_filter_from(const PNMImage &from, int xborder, int yborder) {
773  int from_xs = from.get_x_size();
774  int from_ys = from.get_y_size();
775 
776  int to_xs = get_x_size() - xborder;
777  int to_ys = get_y_size() - yborder;
778 
779  int to_xoff = xborder / 2;
780  int to_yoff = yborder / 2;
781 
782  float from_x0, from_x1, from_y0, from_y1;
783  int to_x, to_y;
784 
785  float x_scale = (float)from_xs / (float)to_xs;
786  float y_scale = (float)from_ys / (float)to_ys;
787 
788  LColorf color;
789 
790  from_y0 = max(0, -to_yoff) * y_scale;
791  for (to_y = max(0, -to_yoff);
792  to_y < min(to_ys, get_y_size()-to_yoff);
793  to_y++) {
794  from_y1 = (to_y+1) * y_scale;
795 
796  from_x0 = max(0, -to_xoff) * x_scale;
797  for (to_x = max(0, -to_xoff);
798  to_x < min(to_xs, get_x_size()-to_xoff);
799  to_x++) {
800  from_x1 = (to_x+1) * x_scale;
801 
802  // Now the box from (from_x0, from_y0) - (from_x1, from_y1)
803  // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y).
804  color = box_filter_region(from,
805  from_x0, from_y0, from_x1, from_y1);
806 
807  set_xel_a(to_xoff + to_x, to_yoff + to_y, color);
808 
809  from_x0 = from_x1;
810  }
811  from_y0 = from_y1;
813  }
814 }
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:68
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition: thread.I:263
int get_y_size() const
Returns the number of pixels in the Y direction.
void quick_filter_from(const PNMImage &copy, int xborder=0, int yborder=0)
Resizes from the given image, with a fixed radius of 0.5.
int get_x_size() const
Returns the number of pixels in the X direction.
void gaussian_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:720
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component, or with a special extension, 2- or 4-component.
Definition: pfmFile.h:34
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
void box_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition: pnmImage.I:789
bool has_no_data_value() const
Returns whether a "no data" value has been established by set_no_data_value().
Definition: pfmFile.I:534
void gaussian_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
void box_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
This is the base class for all three-component vectors and points.
Definition: lvecBase4.h:111
int get_num_channels() const
Returns the number of channels in the image.
static bool is_grayscale(ColorType color_type)
This static variant of is_grayscale() returns true if the indicated image type represents a grayscale...
static const LVecBase4f & zero()
Returns a zero-length vector.
Definition: lvecBase4.h:493