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 }
167 Thread::consider_yield();
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 }
232 Thread::consider_yield();
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)
587 #include "pnm-image-filter-sparse-core.cxx"
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)
603 #include "pnm-image-filter-sparse-core.cxx"
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;
794 Thread::consider_yield();
795 }
796 }
PNMImageHeader::get_x_size
int get_x_size() const
Returns the number of pixels in the X direction.
Definition: pnmImageHeader.I:144
PNMImageHeader::is_grayscale
static bool is_grayscale(ColorType color_type)
This static variant of is_grayscale() returns true if the indicated image type represents a grayscale...
Definition: pnmImageHeader.I:86
PNMImageHeader::get_num_channels
get_num_channels
Returns the number of channels in the image.
Definition: pnmImageHeader.h:60
PNMImageHeader::has_alpha
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
Definition: pnmImageHeader.I:106
PNMImageHeader::get_y_size
int get_y_size() const
Returns the number of pixels in the Y direction.
Definition: pnmImageHeader.I:153
PNMImage
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
PNMImage::gaussian_filter_from
void gaussian_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
Definition: pnm-image-filter.cxx:543
PNMImage::set_xel_a
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition: pnmImage.I:675
PNMImage::quick_filter_from
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.
Definition: pnm-image-filter.cxx:754
PNMImage::get_xel_a
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:608
PNMImage::box_filter_from
void box_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
Definition: pnm-image-filter.cxx:532
PfmFile
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
PfmFile::has_no_data_value
bool has_no_data_value() const
Returns whether a "no data" value has been established by set_no_data_value().
Definition: pfmFile.I:433
PfmFile::gaussian_filter_from
void gaussian_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
Definition: pnm-image-filter.cxx:665
PfmFile::box_filter_from
void box_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
Definition: pnm-image-filter.cxx:654
Thread::consider_yield
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
cmath.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pandabase.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pfmFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnm-image-filter-core.cxx
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnm-image-filter-sparse-core.cxx
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
thread.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.