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