001    /*
002     * MeanDifference
003     * 
004     * Copyright (c) 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.analysis;
009    
010    import net.sourceforge.jiu.data.GrayIntegerImage;
011    import net.sourceforge.jiu.data.Palette;
012    import net.sourceforge.jiu.data.Paletted8Image;
013    import net.sourceforge.jiu.data.PixelImage;
014    import net.sourceforge.jiu.data.RGB24Image;
015    import net.sourceforge.jiu.data.RGBIndex;
016    import net.sourceforge.jiu.data.RGBIntegerImage;
017    import net.sourceforge.jiu.ops.MissingParameterException;
018    import net.sourceforge.jiu.ops.Operation;
019    import net.sourceforge.jiu.ops.WrongParameterException;
020    
021    /**
022     * This operation determines the mean difference between two images.
023     * It requires two images of the same resolution and adds the absolute difference
024     * of all samples.
025     * Then it divides by the number of samples in the image (width times height times
026     * number of channels).
027     * <h3>Supported combinations of image types</h3>
028     * <ul>
029     * <li>One of the two images is of type {@link net.sourceforge.jiu.data.RGB24Image},
030     * the other of type {@link net.sourceforge.jiu.data.Paletted8Image}.</li>
031     * <li>Both images are of the same type and that type implements {@link net.sourceforge.jiu.data.RGBIntegerImage}.</li>
032     * <li>Both images are of the same type and that type implements {@link net.sourceforge.jiu.data.GrayIntegerImage}.</li>
033     * </ul>
034     * <h3>Usage example</h3>
035     * <pre>
036     * MeanDifference diff = new MeanDifference();
037     * diff.setImages(img1, img2);
038     * diff.process();
039     * double meanDifference = diff.getDifference();
040     * </pre>
041     * @author Marco Schmidt
042     * @since 0.11.0
043     */
044    public class MeanDifference extends Operation
045    {
046            private double diff;
047            private PixelImage image1;
048            private PixelImage image2;
049    
050            /**
051             * Returns abs(a - b).
052             * @param a first number
053             * @param b second number
054             * @return abs(a - b)
055             */
056            private static int computeDiff(int a, int b)
057            {
058                    int diff = a - b;
059                    if (diff < 0)
060                    {
061                            return -diff;
062                    }
063                    else
064                    {
065                            return diff;
066                    }
067            }
068    
069            /**
070             * After a call to process, returns the determined mean difference value. 
071             * @return difference value, 0.0 or larger
072             */
073            public double getDifference()
074            {
075                    return diff;
076            }
077    
078            public void process() throws MissingParameterException, WrongParameterException
079            {
080                    if (image1 == null)
081                    {
082                            throw new MissingParameterException("You must specify images using setImages.");
083                    }
084                    boolean sameType = image1.getImageType() == image2.getImageType();
085                    if (image1 instanceof RGB24Image && image2 instanceof Paletted8Image)
086                    {
087                            process((RGB24Image)image1, (Paletted8Image)image2);
088                    }
089                    else
090                    if (image2 instanceof RGB24Image && image1 instanceof Paletted8Image)
091                    {
092                            process((RGB24Image)image2, (Paletted8Image)image1);
093                    }
094                    else
095                    if (sameType && image1 instanceof RGBIntegerImage)
096                    {
097                            process((RGBIntegerImage)image1, (RGBIntegerImage)image2);
098                    }
099                    else
100                    if (sameType && image1 instanceof GrayIntegerImage)
101                    {
102                            process((GrayIntegerImage)image1, (GrayIntegerImage)image2);
103                    }
104                    else
105                    {
106                            throw new WrongParameterException("Not a supported image type combination.");
107                    }
108            }
109    
110            private void process(GrayIntegerImage image1, GrayIntegerImage image2)
111            {
112                    final int HEIGHT = image1.getHeight();
113                    final int WIDTH = image1.getWidth();
114                    long sum = 0;
115                    for (int y = 0; y < HEIGHT; y++)
116                    {
117                            for (int x = 0; x < WIDTH; x++)
118                            {
119                                    sum += computeDiff(image1.getSample(x, y), image2.getSample(x, y));
120                            }
121                            setProgress(y, HEIGHT);
122                    }
123                    setDifference((double)sum / (WIDTH * HEIGHT));
124            }
125    
126            private void process(RGB24Image image1, Paletted8Image image2)
127            {
128                    final int HEIGHT = image1.getHeight();
129                    final int WIDTH = image1.getWidth();
130                    long sum = 0;
131                    Palette pal = image2.getPalette();
132                    int[] red = pal.getSamples(RGBIndex.INDEX_RED);
133                    int[] green = pal.getSamples(RGBIndex.INDEX_GREEN);
134                    int[] blue = pal.getSamples(RGBIndex.INDEX_BLUE);
135                    for (int y = 0; y < HEIGHT; y++)
136                    {
137                            for (int x = 0; x < WIDTH; x++)
138                            {
139                                    int palSample = image2.getSample(x, y);
140                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_RED, x, y), red[palSample]);
141                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_GREEN, x, y), green[palSample]);
142                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_BLUE, x, y), blue[palSample]);
143                            }
144                            setProgress(y, HEIGHT);
145                    }
146                    setDifference((double)sum / (WIDTH * HEIGHT * 3));
147            }
148    
149            private void process(RGBIntegerImage image1, RGBIntegerImage image2)
150            {
151                    final int HEIGHT = image1.getHeight();
152                    final int WIDTH = image1.getWidth();
153                    long sum = 0;
154                    for (int y = 0; y < HEIGHT; y++)
155                    {
156                            for (int x = 0; x < WIDTH; x++)
157                            {
158                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_RED, x, y), image2.getSample(RGBIndex.INDEX_RED, x, y));
159                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_GREEN, x, y), image2.getSample(RGBIndex.INDEX_GREEN, x, y));
160                                    sum += computeDiff(image1.getSample(RGBIndex.INDEX_BLUE, x, y), image2.getSample(RGBIndex.INDEX_BLUE, x, y));
161                            }
162                            setProgress(y, HEIGHT);
163                    }
164                    setDifference((double)sum / (WIDTH * HEIGHT * 3));
165            }
166    
167            private void setDifference(double newValue)
168            {
169                    diff = newValue;
170            }
171    
172            /**
173             * Sets the two images for which the mean difference is to be 
174             * determined.
175             * @param firstImage first image
176             * @param secondImage second image
177             * @throws IllegalArgumentException if either of the images is null,
178             *  if their resolution is different or if their types are not supported
179             *  by this operation
180             */
181            public void setImages(PixelImage firstImage, PixelImage secondImage)
182            {
183                    if (firstImage == null || secondImage == null)
184                    {
185                            throw new IllegalArgumentException("Both image arguments must be non-null.");
186                    }
187                    if (firstImage.getWidth() != secondImage.getWidth())
188                    {
189                            throw new IllegalArgumentException("The images must have the same width.");
190                    }
191                    if (firstImage.getHeight() != secondImage.getHeight())
192                    {
193                            throw new IllegalArgumentException("The images must have the same height.");
194                    }
195                    image1 = firstImage;
196                    image2 = secondImage;
197            }
198    }