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
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
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 }
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
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 }
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
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
275 static 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)
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
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 */
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
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 */
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
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
681 static 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
709 static 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 */
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.