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