001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.convolution;
031
032import org.openimaj.image.FImage;
033import org.openimaj.image.processor.ImageProcessor;
034
035/**
036 * Image convolution with a triangular filter. Implementation is based on
037 * repeated convolution with rectangular kernels, which is done efficiently
038 * using a integral image style approach. Overall complexity is independent of
039 * filter size and linear in the number of pixels.
040 * 
041 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
042 * 
043 */
044public class FTriangleFilter implements ImageProcessor<FImage> {
045        private boolean zeropad;
046        private int filterHeight;
047        private int filterWidth;
048
049        /**
050         * Construct with the given dimensions.
051         * 
052         * @param filterWidth
053         *            width of filter
054         * @param filterHeight
055         *            height of filter
056         * @param zeropad
057         *            zero-pad off the edge of the image if true; duplicate edge
058         *            value otherwise.
059         */
060        public FTriangleFilter(int filterWidth, int filterHeight, boolean zeropad) {
061                super();
062                this.filterWidth = filterWidth;
063                this.filterHeight = filterHeight;
064                this.zeropad = zeropad;
065        }
066
067        /**
068         * Construct with the given dimensions. Edge effects are handled by
069         * duplicating the edge pixels.
070         * 
071         * @param filterWidth
072         *            half-width of filter
073         * @param filterHeight
074         *            half-height of filter
075         */
076        public FTriangleFilter(int filterWidth, int filterHeight) {
077                this(filterWidth, filterHeight, false);
078        }
079
080        @Override
081        public void processImage(FImage image) {
082                convolve(image, filterWidth, filterHeight, zeropad);
083        }
084
085        /**
086         * Construct a triangular kernel of the given size. The kernel will have
087         * 2*width - 1 elements.
088         * 
089         * @param width
090         *            the kernel half-width
091         * @return the triangular kernel
092         */
093        public static float[] createKernel1D(int width) {
094                final float[] kernel = new float[width * 2 - 1];
095                final float invNorm = 1f / (width * width);
096
097                kernel[width - 1] = width * invNorm;
098                for (int i = 0; i < width - 1; i++) {
099                        kernel[i] = (i + 1) * invNorm;
100                        kernel[kernel.length - i - 1] = kernel[i];
101                }
102                return kernel;
103        }
104
105        static void convolve(FImage image, int filterWidth, int filterHeight, boolean zeropad) {
106                convolveVertical(image, image, filterHeight, zeropad);
107                convolveHorizontal(image, image, filterWidth, zeropad);
108        }
109
110        static void convolveVertical(FImage dest, FImage image, int filterSize, boolean zeropad) {
111                if (image.height == 0) {
112                        return;
113                }
114
115                final float scale = (float) (1.0 / ((double) filterSize * (double) filterSize));
116                final float[] buffer = new float[image.height + filterSize];
117                final int bufferOffset = filterSize;
118
119                for (int x = 0; x < image.width; x++) {
120                        // integrate backward
121                        buffer[bufferOffset + image.height - 1] = image.pixels[image.height - 1][x];
122                        int y;
123                        for (y = image.height - 2; y >= 0; --y) {
124                                buffer[bufferOffset + y] = buffer[bufferOffset + y + 1] + image.pixels[y][x];
125                        }
126                        if (zeropad) {
127                                for (; y >= -filterSize; y--) {
128                                        buffer[bufferOffset + y] = buffer[bufferOffset + y + 1];
129                                }
130                        } else {
131                                for (; y >= -filterSize; y--) {
132                                        buffer[bufferOffset + y] = buffer[bufferOffset + y + 1] + image.pixels[0][x];
133                                }
134                        }
135
136                        // filter forward
137                        for (y = -filterSize; y < image.height - filterSize; y++) {
138                                buffer[bufferOffset + y] = buffer[bufferOffset + y] - buffer[bufferOffset + y + filterSize];
139                        }
140                        if (!zeropad) {
141                                for (y = image.height - filterSize; y < image.height; ++y) {
142                                        buffer[bufferOffset + y] =
143                                                        buffer[bufferOffset + y] - buffer[bufferOffset + image.height - 1]
144                                                                        * (image.height - filterSize - y);
145                                }
146                        }
147
148                        // integrate forward
149                        for (y = -filterSize + 1; y < image.height; y++) {
150                                buffer[bufferOffset + y] += buffer[bufferOffset + y - 1];
151                        }
152
153                        // filter backward
154                        for (y = dest.height - 1; y >= 0; y--) {
155                                dest.pixels[y][x] = scale * (buffer[bufferOffset + y] - buffer[bufferOffset + y - filterSize]);
156                        }
157                }
158        }
159
160        static void convolveHorizontal(FImage dest, FImage image, int filterSize, boolean zeropad) {
161                if (image.width == 0) {
162                        return;
163                }
164
165                final float scale = (float) (1.0 / ((double) filterSize * (double) filterSize));
166                final float[] buffer = new float[image.width + filterSize];
167                final int bufferOffset = filterSize;
168
169                for (int y = 0; y < image.height; y++) {
170                        // integrate backward
171                        buffer[bufferOffset + image.width - 1] = image.pixels[y][image.width - 1];
172                        int x;
173                        for (x = image.width - 2; x >= 0; --x) {
174                                buffer[bufferOffset + x] = buffer[bufferOffset + x + 1] + image.pixels[y][x];
175                        }
176                        if (zeropad) {
177                                for (; x >= -filterSize; x--) {
178                                        buffer[bufferOffset + x] = buffer[bufferOffset + x + 1];
179                                }
180                        } else {
181                                for (; x >= -filterSize; x--) {
182                                        buffer[bufferOffset + x] = buffer[bufferOffset + x + 1] + image.pixels[y][0];
183                                }
184                        }
185
186                        // filter forward
187                        for (x = -filterSize; x < image.width - filterSize; x++) {
188                                buffer[bufferOffset + x] = buffer[bufferOffset + x] - buffer[bufferOffset + x + filterSize];
189                        }
190                        if (!zeropad) {
191                                for (x = image.width - filterSize; x < image.width; ++x) {
192                                        buffer[bufferOffset + x] =
193                                                        buffer[bufferOffset + x] - buffer[bufferOffset + image.width - 1]
194                                                                        * (image.width - filterSize - x);
195                                }
196                        }
197
198                        // integrate forward
199                        for (x = -filterSize + 1; x < image.width; x++) {
200                                buffer[bufferOffset + x] += buffer[bufferOffset + x - 1];
201                        }
202
203                        // filter backward
204                        for (x = dest.width - 1; x >= 0; x--) {
205                                dest.pixels[y][x] = scale * (buffer[bufferOffset + x] - buffer[bufferOffset + x - filterSize]);
206                        }
207                }
208        }
209}