001    /*
002     * AreaFilterOperation
003     * 
004     * Copyright (c) 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.filters;
009    
010    import net.sourceforge.jiu.data.GrayIntegerImage;
011    import net.sourceforge.jiu.data.IntegerImage;
012    import net.sourceforge.jiu.data.PixelImage;
013    import net.sourceforge.jiu.data.RGBIntegerImage;
014    import net.sourceforge.jiu.ops.ImageToImageOperation;
015    import net.sourceforge.jiu.ops.MissingParameterException;
016    import net.sourceforge.jiu.ops.WrongParameterException;
017    
018    /**
019     * Base class for operations that convert images to images and determine
020     * an output sample by doing calculations on the input sample at the same
021     * position plus some neighboring samples.
022     * <p>
023     * Override {@link #computeSample} and the operation will work.
024     * @since 0.9.0
025     * @author Marco Schmidt
026     */
027    public abstract class AreaFilterOperation extends ImageToImageOperation
028    {
029            private int areaWidth;
030            private int areaHeight;
031    
032            /**
033             * Checks if the argument is a valid area height value.
034             * The default implementation requires the argument to be odd and larger than zero.
035             * Override this method if your extension of AreaFilterOperation requires different heights.
036             * @throws IllegalArgumentException if the argument is not valid
037             */
038            public void checkAreaHeight(int height)
039            {
040                    if (height < 1)
041                    {
042                            throw new IllegalArgumentException("Height must be larger than 0.");
043                    }
044                    if ((height & 1) == 0)
045                    {
046                            throw new IllegalArgumentException("Height must be odd.");
047                    }
048            }
049    
050            /**
051             * Checks if the argument is a valid area width value.
052             * The default implementation requires the argument to be odd and larger than zero.
053             * Override this method if your extension of AreaFilterOperation requires different widths.
054             * @throws IllegalArgumentException if the argument is not valid
055             */
056            public void checkAreaWidth(int width)
057            {
058                    if (width < 1)
059                    {
060                            throw new IllegalArgumentException("Width must be larger than 0.");
061                    }
062                    if ((width & 1) == 0)
063                    {
064                            throw new IllegalArgumentException("Width must be odd.");
065                    }
066            }
067    
068            /**
069             * Determine the resulting sample for an array with the source sample
070             * and zero or more of its neighbors.
071             * This abstract method must be implemented by classes extending this operation.
072             * The array will hold <code>numSamples</code> samples, which will be stored
073             * starting at offset <code>0</code>.
074             * <p>
075             * Normally, <code>numSamples</code> is equal to {@link #getAreaWidth} times {@link #getAreaHeight}.
076             * Near the border of the image you may get less samples.
077             * Example: the top left sample of an image has only three neighbors (east, south-east and south), 
078             * so you will only get four samples (three neighbors and the sample itself).
079             * @param samples the array holding the sample(s)
080             * @param numSamples number of samples in the array
081             * @return sample to be written to the output image
082             */
083            public abstract int computeSample(int[] samples, int numSamples);
084    
085            /**
086             * Returns the current area height.
087             * @return height of area window in pixels
088             * @see #setAreaHeight(int)
089             */
090            public int getAreaHeight()
091            {
092                    return areaHeight;
093            }
094    
095            /**
096             * Returns the current area width.
097             * @return width of area window in pixels
098             * @see #setAreaWidth(int)
099             */
100            public int getAreaWidth()
101            {
102                    return areaWidth;
103            }
104    
105            /**
106             * Applies the filter to one of the channels of an image.
107             */
108            private void process(int channelIndex, IntegerImage in, IntegerImage out)
109            {
110                    processBorders(channelIndex, in, out);
111                    processCenter(channelIndex, in, out);
112    /*
113                    final int HEIGHT = in.getHeight();
114                    final int WIDTH = in.getWidth();
115                    final int H_2 = areaWidth / 2;
116                    final int V_2 = areaHeight / 2;
117                    int processedItems = channelIndex * HEIGHT;
118                    final int TOTAL_ITEMS = in.getNumChannels() * HEIGHT;
119                    int[] samples = new int[areaWidth * areaHeight];
120                    for (int y = 0; y < HEIGHT; y++)
121                    {
122                            for (int x = 0; x < WIDTH; x++)
123                            {
124                                    // collect samples from area
125                                    int numSamples = 0;
126                                    for (int v = y - V_2; v <= y + V_2; v++)
127                                    {
128                                            if (v >= 0 && v < HEIGHT)
129                                            {
130                                                    for (int u = x - H_2; u <= x + H_2; u++)
131                                                    {
132                                                            if (u >= 0 && u < WIDTH)
133                                                            {
134                                                                    samples[numSamples++] = in.getSample(channelIndex, u, v);
135                                                            }
136                                                    }
137                                            }
138                                    }
139                                    // determine and set output sample
140                                    out.putSample(channelIndex, x, y, computeSample(samples, numSamples));
141                            }
142                            setProgress(processedItems++, TOTAL_ITEMS);
143                    }
144                    */
145            }
146    
147            private void process(IntegerImage in, IntegerImage out)
148            {
149                    if (out == null)
150                    {
151                            out = (IntegerImage)in.createCompatibleImage(in.getWidth(), in.getHeight());
152                            setOutputImage(out);
153                    }
154                    for (int channelIndex = 0; channelIndex < in.getNumChannels(); channelIndex++)
155                    {
156                            process(channelIndex, in, out);
157                    }
158            }
159    
160            public void process() throws 
161                    MissingParameterException,
162                    WrongParameterException 
163            {
164                    if (areaWidth == 0)
165                    {
166                            throw new MissingParameterException("Area width has not been initialized.");
167                    }
168                    if (areaHeight == 0)
169                    {
170                            throw new MissingParameterException("Area height has not been initialized.");
171                    }
172                    ensureInputImageIsAvailable();
173                    ensureImagesHaveSameResolution();
174                    PixelImage in = getInputImage();
175                    PixelImage out = getOutputImage();
176                    if (in instanceof GrayIntegerImage || in instanceof RGBIntegerImage)
177                    {
178                            process((IntegerImage)in, (IntegerImage)out);
179                    }
180                    else
181                    {
182                            throw new WrongParameterException("Input image must implement GrayIntegerImage or RGBIntegerImage.");
183                    }
184            }
185    
186            private void processBorders(int channelIndex, IntegerImage in, IntegerImage out)
187            {
188                    /*processBorderNorthWest(channelIndex, in, out);
189                    processBorderNorth(channelIndex, in, out);
190                    processBorderNorthEast(channelIndex, in, out);
191    
192                    processBorderEast(channelIndex, in, out);
193    
194                    processBorderSouthEast(channelIndex, in, out);
195                    processBorderSouth(channelIndex, in, out);
196                    processBorderSouthWest(channelIndex, in, out);
197    
198                    processBorderWest(channelIndex, in, out);*/
199            }
200    
201            private void processCenter(int channelIndex, IntegerImage in, IntegerImage out)
202            {
203                    final int HEIGHT = in.getHeight();
204                    final int WIDTH = in.getWidth();
205                    final int AREA_WIDTH = getAreaWidth();
206                    final int H_2 = AREA_WIDTH / 2;
207                    final int AREA_HEIGHT = getAreaHeight();
208                    final int V_2 = AREA_HEIGHT / 2;
209                    if (WIDTH < AREA_WIDTH || HEIGHT < AREA_HEIGHT)
210                    {
211                            return;
212                    }
213                    final int NUM_SAMPLES = AREA_WIDTH * AREA_HEIGHT;
214                    final int TOTAL_ITEMS = in.getNumChannels() * HEIGHT;
215                    int processedItems = channelIndex * HEIGHT + AREA_HEIGHT / 2;
216                    int[] samples = new int[AREA_WIDTH * AREA_HEIGHT];
217                    for (int y1 = 0, y2 = V_2; y2 < HEIGHT - V_2; y1++, y2++)
218                    {
219                            for (int x1 = 0, x2 = H_2; x2 < WIDTH - H_2; x1++, x2++)
220                            {
221                                    in.getSamples(channelIndex, x1, y1, areaWidth, areaHeight, samples, 0);
222                                    out.putSample(channelIndex, x2, y2, computeSample(samples, NUM_SAMPLES));
223                            }
224                            setProgress(processedItems++, TOTAL_ITEMS);
225                    }
226            }
227    
228            /**
229             * Sets the area of the window to be used to determine each pixel's mean to
230             * the argument width and height.
231             * @param width width of window, must be 1 or larger
232             * @param height height of window, must be 1 or larger
233             * @see #setAreaHeight
234             * @see #setAreaWidth
235             */
236            public void setArea(int width, int height)
237            {
238                    setAreaWidth(width);
239                    setAreaHeight(height);
240            }
241    
242            /**
243             * Sets the height of the area of the window to be used to determine each pixel's mean to
244             * the argument value.
245             * @param height height of window, must be odd and 1 or larger
246             * @see #getAreaHeight
247             * @see #setArea
248             * @see #setAreaWidth
249             */
250            public void setAreaHeight(int height)
251            {
252                    checkAreaHeight(height);
253                    areaHeight = height;
254            }
255    
256            /**
257             * Sets the width of the area of the window to be used to determine each pixel's mean to
258             * the argument value.
259             * @param width width of window, must be odd and 1 or larger
260             * @see #getAreaWidth
261             * @see #setArea
262             * @see #setAreaHeight
263             */
264            public void setAreaWidth(int width)
265            {
266                    checkAreaWidth(width);
267                    areaWidth = width;
268            }
269    }