1 /*
2 * Modification History
3 *
4 * 2000-December-21 Jason Rohrer
5 * Created.
6 *
7 * 2001-January-6 Jason Rohrer
8 * Added a getRadius function for completeness.
9 *
10 * 2006-August-22 Jason Rohrer
11 * Fixed major bug: sum for box must start at zero.
12 *
13 * 2006-September-7 Jason Rohrer
14 * Optimized inner loop.
15 * Optimized more, avoiding summing over entire box for each pixel.
16 */
17
18
19 #ifndef BOX_BLUR_FILTER_INCLUDED
20 #define BOX_BLUR_FILTER_INCLUDED
21
22 #include "minorGems/graphics/ChannelFilter.h"
23
24 /**
25 * Blur convolution filter that uses a box for averaging.
26 *
27 * @author Jason Rohrer
28 */
29 class BoxBlurFilter : public ChannelFilter {
30
31 public:
32
33 /**
34 * Constructs a box filter.
35 *
36 * @param inRadius the radius of the box in pixels.
37 */
38 BoxBlurFilter( int inRadius );
39
40
41 /**
42 * Sets the box radius.
43 *
44 * @param inRadius the radius of the box in pixels.
45 */
46 void setRadius( int inRadius );
47
48
49 /**
50 * Gets the box radius.
51 *
52 * @return the radius of the box in pixels.
53 */
54 int getRadius();
55
56
57 // implements the ChannelFilter interface
58 void apply( double *inChannel, int inWidth, int inHeight );
59
60 private:
61 int mRadius;
62 };
63
64
65
BoxBlurFilter(int inRadius)66 inline BoxBlurFilter::BoxBlurFilter( int inRadius )
67 : mRadius( inRadius ) {
68
69 }
70
71
72
setRadius(int inRadius)73 inline void BoxBlurFilter::setRadius( int inRadius ) {
74 mRadius = inRadius;
75 }
76
77
78
getRadius()79 inline int BoxBlurFilter::getRadius() {
80 return mRadius;
81 }
82
83
84
apply(double * inChannel,int inWidth,int inHeight)85 inline void BoxBlurFilter::apply( double *inChannel,
86 int inWidth, int inHeight ) {
87
88 double *blurredChannel = new double[ inWidth * inHeight ];
89
90
91 // optimization:
92 // The sum for a given pixel's box in row N is the sum for the same pixel
93 // in row (N-1) minus one box-row of pixels and plus a box-row of pixels
94 // We don't have to loop over the entire box for each pixel (instead,
95 // we just add the new pixels and subtract the old as the box moves along)
96 char lastRowSumsSet = false;
97 double *lastRowSums = new double[ inWidth ];
98
99 // track the sums from single rows of boxes that we have already seen
100 // Thus, when we need to "subtract a row" from our box sum, we can use
101 // the precomputed version
102 double *singleRowBoxSums = new double[ inWidth * inHeight ];
103
104
105 for( int y=0; y<inHeight; y++ ) {
106 int yIndexContrib = y * inWidth;
107
108 int startBoxY = y - mRadius;
109 int endBoxY = y + mRadius;
110
111 // deal with boundary cases (near top and bottom of image)
112 // Near top, we only add rows to our sum
113 // Near bottom, we only subtract rows to our sum
114 char addARow = true;
115 char subtractARow = true;
116
117 if( startBoxY <= 0 ) {
118 startBoxY = 0;
119
120 // our box is at or hanging over top edge
121 // no rows to subtract from sum
122 subtractARow = false;
123 }
124 if( endBoxY >= inHeight ) {
125 endBoxY = inHeight - 1;
126
127 // box hanging over bottom edge
128 // no rows to add to sum
129 addARow = false;
130 }
131
132 int boxSizeY = endBoxY - startBoxY + 1;
133
134 for( int x=0; x<inWidth; x++ ) {
135
136 int pixelIndex = yIndexContrib + x;
137
138 // sum into this pixel in the blurred channel
139 blurredChannel[ pixelIndex ] = 0;
140
141
142 int startBoxX = x - mRadius;
143 int endBoxX = x + mRadius;
144
145
146
147 if( startBoxX < 0 ) {
148 startBoxX = 0;
149 }
150 if( endBoxX >= inWidth ) {
151 endBoxX = inWidth - 1;
152 }
153
154 int boxSizeX = endBoxX - startBoxX + 1;
155
156 // sum all pixels in the box around this pixel
157 double sum = 0;
158
159
160 if( ! lastRowSumsSet ) {
161 // do the "slow way" for the first row
162
163 for( int boxY = startBoxY; boxY<=endBoxY; boxY++ ) {
164 int yBoxIndexContrib = boxY * inWidth;
165
166 double rowSum = 0;
167 for( int boxX = startBoxX; boxX<=endBoxX; boxX++ ) {
168
169 rowSum += inChannel[ yBoxIndexContrib + boxX ];
170 }
171
172 sum += rowSum;
173
174 // store row sum for future use
175 singleRowBoxSums[ yBoxIndexContrib + x ] = rowSum;
176 }
177
178 }
179 else {
180 // we have sum for this pixel from the previous row
181 // use it to avoid looping over entire box again
182
183 sum = lastRowSums[ x ];
184
185 if( addARow ) {
186 // add pixels from row at endBoxY
187
188
189 int yBoxIndexContrib = endBoxY * inWidth;
190
191 double rowSum = 0;
192 for( int boxX = startBoxX; boxX<=endBoxX; boxX++ ) {
193 rowSum += inChannel[ yBoxIndexContrib + boxX ];
194 }
195
196 sum += rowSum;
197
198 // store it for later when we will need to subtract it
199 singleRowBoxSums[ yBoxIndexContrib + x ] = rowSum;
200 }
201 if( subtractARow ) {
202 // subtract pixels from startBoxY of previous row's box
203
204 int yBoxIndexContrib = (startBoxY - 1) * inWidth;
205
206 // use pre-computed sum for the row we're subtracting
207 sum -= singleRowBoxSums[ yBoxIndexContrib + x ];
208 }
209
210 }
211
212
213 // divide by number of pixels to complete the average
214 blurredChannel[ pixelIndex ] = sum / (boxSizeX * boxSizeY);
215
216 // save to use when computing box sum for next row
217 lastRowSums[ x ] = sum;
218 }
219
220 // we now have valid last row sums that we can use for
221 // all the rest of the rows
222 lastRowSumsSet = true;
223
224 }
225
226 // copy blurred image back into passed-in image
227 memcpy( inChannel, blurredChannel, sizeof(double) * inWidth * inHeight );
228
229 delete [] blurredChannel;
230 delete [] lastRowSums;
231 delete [] singleRowBoxSums;
232 }
233
234 #endif
235