001    /*
002     * RGBToGrayConversion
003     * 
004     * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.reduction;
009    
010    import net.sourceforge.jiu.data.Gray16Image;
011    import net.sourceforge.jiu.data.Gray8Image;
012    import net.sourceforge.jiu.data.GrayIntegerImage;
013    import net.sourceforge.jiu.data.MemoryGray16Image;
014    import net.sourceforge.jiu.data.MemoryGray8Image;
015    import net.sourceforge.jiu.data.Palette;
016    import net.sourceforge.jiu.data.Paletted8Image;
017    import net.sourceforge.jiu.data.PixelImage;
018    import net.sourceforge.jiu.data.RGB24Image;
019    import net.sourceforge.jiu.data.RGB48Image;
020    import net.sourceforge.jiu.data.RGBIndex;
021    import net.sourceforge.jiu.data.RGBIntegerImage;
022    import net.sourceforge.jiu.ops.ImageToImageOperation;
023    import net.sourceforge.jiu.ops.MissingParameterException;
024    import net.sourceforge.jiu.ops.WrongParameterException;
025    
026    /**
027     * Converts RGB color images (both truecolor and paletted) to grayscale images.
028     * The weights to be used with the three base colors red, green and blue can be 
029     * modified with a call to
030     * {@link #setColorWeights(float, float, float)}.
031     * <h3>Supported image types</h3>
032     * {@link RGB24Image} and {@link Paletted8Image} can be used as input image types.
033     * A {@link Gray8Image} be will be created from them.
034     * <p>
035     * Could be optimized to use int multiplication instead of float multiplication.
036     * <p>
037     * NOTE: Should be adjusted to support RGB48Image objects once they're available.
038     * <h3>Usage example</h3>
039     * <pre>
040     * RGBToGrayConversion rgbtogray = new RGBToGrayConversion();
041     * rgbtogray.setInputImage(image);
042     * rgbtogray.process();
043     * PixelImage grayImage = rgbtogray.getOutputImage();
044     * </pre>
045     * @author Marco Schmidt
046     */
047    public class RGBToGrayConversion extends ImageToImageOperation
048    {
049            /**
050             * The default weight for red samples in the conversion, 0.3f.
051             */
052            public static final float DEFAULT_RED_WEIGHT = 0.3f;
053    
054            /**
055             * The default weight for green samples in the conversion, 0.59f.
056             */
057            public static final float DEFAULT_GREEN_WEIGHT = 0.59f;
058    
059            /**
060             * The default weight for blue samples in the conversion, 0.11f.
061             */
062            public static final float DEFAULT_BLUE_WEIGHT = 0.11f;
063    
064            private float redWeight = DEFAULT_RED_WEIGHT;
065            private float greenWeight = DEFAULT_GREEN_WEIGHT;
066            private float blueWeight = DEFAULT_BLUE_WEIGHT;
067    
068            private void convert(RGBIntegerImage in, GrayIntegerImage out)
069            {
070                    final int WIDTH = in.getWidth();
071                    final int HEIGHT = in.getHeight();
072                    for (int y = 0; y < HEIGHT; y++)
073                    {
074                            for (int x = 0; x < WIDTH; x++)
075                            {
076                                    int red = in.getSample(RGBIndex.INDEX_RED, x, y);
077                                    int green = in.getSample(RGBIndex.INDEX_GREEN, x, y);
078                                    int blue = in.getSample(RGBIndex.INDEX_BLUE, x, y);
079                                    out.putSample(x, y, (int)(red * redWeight + green * greenWeight + blue * blueWeight));
080                            }
081                            setProgress(y, HEIGHT);
082                    }
083                    setOutputImage(out);
084            }
085    
086            public void process() throws
087                    MissingParameterException,
088                    WrongParameterException
089            {
090                    ensureInputImageIsAvailable();
091                    PixelImage in = getInputImage();
092                    if (in instanceof RGB24Image)
093                    {
094                            process((RGB24Image)in);
095                    }
096                    else
097                    if (in instanceof RGB48Image)
098                    {
099                            process((RGB48Image)in);
100                    }
101                    else
102                    if (in instanceof Paletted8Image)
103                    {
104                            process((Paletted8Image)in);
105                    }
106                    else
107                    {
108                            throw new WrongParameterException("Type of input image unsupported: " +  in.getImageType().getName());
109                    }
110            }
111    
112            private void process(Paletted8Image in) throws
113                    MissingParameterException,
114                    WrongParameterException
115            {
116                    PixelImage image = getOutputImage();
117                    Gray8Image out = null;
118                    if (image == null)
119                    {
120                            out = new MemoryGray8Image(in.getWidth(), in.getHeight());
121                    }
122                    else
123                    {
124                            if (!(image instanceof Gray8Image))
125                            {
126                                    throw new WrongParameterException("Specified output image must be of type Gray8Image for input image of type Paletted8Image.");
127                            }
128                            out = (Gray8Image)image;
129                            ensureImagesHaveSameResolution();
130                    }
131                    Palette palette = in.getPalette();
132                    int[] lut = new int[palette.getNumEntries()];
133                    for (int i = 0; i < lut.length; i++)
134                    {
135                            int red = palette.getSample(RGBIndex.INDEX_RED, i);
136                            int green = palette.getSample(RGBIndex.INDEX_GREEN, i);
137                            int blue = palette.getSample(RGBIndex.INDEX_BLUE, i);
138                            lut[i] = (int)(red * redWeight + green * greenWeight + blue * blueWeight);
139                    }
140                    final int WIDTH = in.getWidth();
141                    final int HEIGHT = in.getHeight();
142                    for (int y = 0; y < HEIGHT; y++)
143                    {
144                            for (int x = 0; x < WIDTH; x++)
145                            {
146                                    try
147                                    {
148                                            out.putSample(0, x, y, lut[in.getSample(0, x, y)]);
149                                    }
150                                    catch (ArrayIndexOutOfBoundsException aioobe)
151                                    {
152                                    }
153                            }
154                            setProgress(y, HEIGHT);
155                    }
156                    setOutputImage(out);
157            }
158    
159            private void process(RGB24Image in) throws WrongParameterException
160            {
161                    PixelImage out = getOutputImage();
162                    if (out == null)
163                    {
164                            out = new MemoryGray8Image(in.getWidth(), in.getHeight());
165                    }
166                    else
167                    {
168                            if (!(out instanceof Gray8Image))
169                            {
170                                    throw new WrongParameterException("Specified output image must be of type Gray8Image for input image of type RGB24Image.");
171                            }
172                            ensureImagesHaveSameResolution();
173                    }
174                    convert(in, (GrayIntegerImage)out);
175            }
176    
177            private void process(RGB48Image in) throws WrongParameterException
178            {
179                    PixelImage out = getOutputImage();
180                    if (out == null)
181                    {
182                            out = new MemoryGray16Image(in.getWidth(), in.getHeight());
183                    }
184                    else
185                    {
186                            if (!(out instanceof Gray16Image))
187                            {
188                                    throw new WrongParameterException("Specified output image must be of type Gray16Image for input image of type RGB48Image.");
189                            }
190                            ensureImagesHaveSameResolution();
191                    }
192                    convert(in, (GrayIntegerImage)out);
193            }
194    
195            /**
196             * Sets the weights for the three colors red, green and blue used in the conversion procedure.
197             * For each RGB value <code>(r, g, b)</code> to be converted (whether in a truecolor 
198             * image or in the palette), the formula is <code>gray = r * red + g * green + b * blue</code>.
199             * The default values for these weights are {@link #DEFAULT_RED_WEIGHT}, 
200             * {@link #DEFAULT_GREEN_WEIGHT} and {@link #DEFAULT_BLUE_WEIGHT}.
201             * This method lets the user change these values.
202             * Each of these arguments must be >= 0.0f and <= 1.0f.
203             * The sum of the three must be <= 1.0f.
204             * For any resulting gray value to be spread over the complete scale from 0.0f to 1.0f it is
205             * preferable for the sum to be equal to or at least close to 1.0f.
206             * However, this is not checked.
207             * The smaller the sum of the weights is, the darker the resulting gray image will become.
208             * @param red weight of the red sample in the conversion, between <code>0.0f</code> and <code>1.0f</code>
209             * @param green weight of the green sample in the conversion, between <code>0.0f</code> and <code>1.0f</code>
210             * @param blue weight of the blue sample in the conversion, between <code>0.0f</code> and <code>1.0f</code>
211             * @throws IllegalArgumentException if any one of the above mentioned constraints for the arguments is not met
212             */
213            public void setColorWeights(float red, float green, float blue)
214            {
215                    if (red < 0.0f)
216                    {
217                            throw new IllegalArgumentException("The red weight must be larger than or equal to 0; got " + red);
218                    }
219                    if (green < 0.0f)
220                    {
221                            throw new IllegalArgumentException("The green weight must be larger than or equal to 0; got " + green);
222                    }
223                    if (blue < 0.0f)
224                    {
225                            throw new IllegalArgumentException("The blue weight must be larger than or equal to 0; got " + blue);
226                    }
227                    if (red > 1.0f)
228                    {
229                            throw new IllegalArgumentException("The red weight must be smaller than or equal to 1.0f; got " + red);
230                    }
231                    if (green > 1.0f)
232                    {
233                            throw new IllegalArgumentException("The green weight must be smaller than or equal to 1.0f; got " + green);
234                    }
235                    if (blue > 1.0f)
236                    {
237                            throw new IllegalArgumentException("The blue weight must be smaller than or equal to 1.0f; got " + blue);
238                    }
239                    float sum = red + green + blue;
240                    if (sum > 1.0f)
241                    {
242                            throw new IllegalArgumentException("The sum of the three weights must be smaller than or equal to 1.0f; got " + sum);
243                    }
244                    redWeight = red;
245                    greenWeight = green;
246                    blueWeight = blue;
247            }
248    }