Panda3D
Loading...
Searching...
No Matches
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
40using std::max;
41using 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/*
70typedef double WorkType;
71typedef double StoreType;
72static const WorkType source_max = 1.0;
73static const WorkType filter_max = 1.0;
74*/
75
76// To use single-precision floating point, 4 bytes:
77typedef float WorkType;
78typedef float StoreType;
79static const WorkType source_max = 1.0f;
80static const WorkType filter_max = 1.0f;
81
82/*
83// To use 16-bit integer arithmetic, 2 bytes:
84typedef unsigned long WorkType;
85typedef unsigned short StoreType;
86static const WorkType source_max = 65535;
87static const WorkType filter_max = 255;
88*/
89
90/*
91// To use 8-bit integer arithmetic, 1 byte:
92typedef unsigned long WorkType;
93typedef unsigned char StoreType;
94static const WorkType source_max = 255;
95static 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
108static void
109filter_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).
172static void
173filter_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
246typedef void FilterFunction(float scale, float width,
247 WorkType *&filter, float &filter_width);
248
249static void
250box_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
275static void
276gaussian_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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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.
485static void
486filter_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 */
532box_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 */
543gaussian_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)
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)
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.
615static void
616filter_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 */
654box_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 */
665gaussian_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
671static INLINE void
672box_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
681static INLINE void
682box_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
709static LColorf
710box_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 */
754quick_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}
int get_x_size() const
Returns the number of pixels in the X direction.
static bool is_grayscale(ColorType color_type)
This static variant of is_grayscale() returns true if the indicated image type represents a grayscale...
get_num_channels
Returns the number of channels in the image.
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
int get_y_size() const
Returns the number of pixels in the Y direction.
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
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 set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition pnmImage.I:675
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.
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition pnmImage.I:608
void box_filter_from(float radius, const PNMImage &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition pfmFile.h:31
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 PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
void box_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.