001    /*
002     * ReduceShadesOfGray
003     *
004     * Copyright (c) 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.BilevelImage;
011    import net.sourceforge.jiu.data.Gray16Image;
012    import net.sourceforge.jiu.data.Gray8Image;
013    import net.sourceforge.jiu.data.GrayIntegerImage;
014    import net.sourceforge.jiu.data.MemoryBilevelImage;
015    import net.sourceforge.jiu.data.MemoryGray16Image;
016    import net.sourceforge.jiu.data.MemoryGray8Image;
017    import net.sourceforge.jiu.data.PixelImage;
018    import net.sourceforge.jiu.ops.ImageToImageOperation;
019    import net.sourceforge.jiu.ops.MissingParameterException;
020    import net.sourceforge.jiu.ops.WrongParameterException;
021    
022    /**
023     * Reduces the number of shades of gray of a grayscale image.
024     * This class uses the most simple possible algorithm.
025     * Only the most significant N bits are kept (where N is the
026     * number specified with {@link #setBits}), the others are dropped
027     * and the result is scaled back to either 8 or 16 bits to fit
028     * into the two grayscale image types.
029     * <h3>Supported image classes</h3>
030     * This class works with {@link net.sourceforge.jiu.data.Gray8Image}
031     * and {@link net.sourceforge.jiu.data.Gray16Image}.
032     * <h3>Usage example</h3>
033     * Reduce a grayscale image to 3 bit (2<sup>3</sup> = 8 shades of gray):
034     * <pre>
035     * ReduceShadesOfGray reduce = new ReduceShadesOfGray();
036     * reduce.setBits(3);
037     * reduce.setInputImage(image); // some Gray8Image or Gray16Image
038     * reduce.process();
039     * PixelImage reducedImage = reduce.getOutputImage();
040     * </pre>
041     * @author Marco Schmidt
042     * @since 0.3.0
043     */
044    public class ReduceShadesOfGray extends ImageToImageOperation
045    {
046            /**
047             * Number of significant bits in the destination grayscale image.
048             */
049            private Integer destBits;
050    
051            /**
052             * Lookup table, for each possible input sample stores the
053             * corresponding output sample.
054             */
055            private int[] lut;
056    
057            private void createLut(int inDepth)
058            {
059                    int outDepth = destBits.intValue();
060                    lut = new int[1 << inDepth];
061                    final int SHIFT = inDepth - outDepth;
062                    final int MAX_IN_VALUE = (1 << inDepth) - 1;
063                    final int MAX_OUT_VALUE = (1 << outDepth) - 1;
064                    for (int i = 0; i < lut.length; i++)
065                    {
066                            int value = i >> SHIFT;
067                            lut[i] = (value * MAX_IN_VALUE) / MAX_OUT_VALUE;
068                    }
069            }
070    
071            private void process(GrayIntegerImage in, final int MASK, BilevelImage out)
072            {
073                    if (out == null)
074                    {
075                            out = new MemoryBilevelImage(in.getWidth(), in.getHeight());
076                    }
077                    out.clear(BilevelImage.BLACK);
078                    for (int y = 0; y < in.getHeight(); y++)
079                    {
080                            for (int x = 0; x < in.getWidth(); x++)
081                            {
082                                    if ((in.getSample(x, y) & MASK) != 0)
083                                    {
084                                            out.putWhite(x, y);
085                                    }
086                            }
087                            setProgress(y, in.getHeight());
088                    }
089                    setOutputImage(out);
090            }
091    
092            private void process(GrayIntegerImage in, GrayIntegerImage out)
093            {
094                    int bits = destBits.intValue();
095                    for (int y = 0; y < in.getHeight(); y++)
096                    {
097                            for (int x = 0; x < in.getWidth(); x++)
098                            {
099                                    out.putSample(x, y, lut[in.getSample(0, x, y)]);
100                            }
101                            setProgress(y, in.getHeight());
102                    }
103                    setOutputImage(out);
104            }
105    
106            public void process() throws MissingParameterException, WrongParameterException
107            {
108                    if (destBits == null)
109                    {
110                            throw new MissingParameterException("The number of destination bits has not been specified.");
111                    }
112                    ensureInputImageIsAvailable();
113                    ensureImagesHaveSameResolution();
114                    PixelImage in = getInputImage();
115                    boolean gray8 = in instanceof Gray8Image;
116                    boolean gray16 = in instanceof Gray16Image;
117                    if (!(gray8 || gray16))
118                    {
119                            throw new WrongParameterException("Input image must be either Gray8Image or Gray16Image.");
120                    }
121                    if (destBits.intValue() == 1)
122                    {
123                            process((GrayIntegerImage)in, gray8 ? 0x80 : 0x8000, (BilevelImage)getOutputImage());
124                    }
125                    else
126                    if (gray8)
127                    {
128                            if (destBits.intValue() > 7)
129                            {
130                                    throw new WrongParameterException("For a Gray8Image destination bits must be 7 or less.");
131                            }
132                            PixelImage out = getOutputImage();
133                            if (out == null)
134                            {
135                                    out = new MemoryGray8Image(in.getWidth(), in.getHeight());
136                            }
137                            else
138                            {
139                                    if (!(out instanceof Gray8Image))
140                                    {
141                                            throw new WrongParameterException("For this input image, output image must be a Gray8Image.");
142                                    }
143                            }
144                            createLut(8);
145                            process((GrayIntegerImage)in, (GrayIntegerImage)out);
146                    }
147                    else
148                    if (gray16)
149                    {
150                            PixelImage out = getOutputImage();
151                            if (out == null)
152                            {
153                                    out = new MemoryGray16Image(in.getWidth(), in.getHeight());
154                            }
155                            else
156                            {
157                                    if (destBits.intValue() <= 8 && !(out instanceof Gray8Image))
158                                    {
159                                            throw new WrongParameterException("For this input image, output image must be a Gray8Image.");
160                                    }
161                                    if (destBits.intValue() <= 15 && !(out instanceof Gray16Image))
162                                    {
163                                            throw new WrongParameterException("For this input image, output image must be a Gray16Image.");
164                                    }
165                            }
166                            createLut(16);
167                            process((GrayIntegerImage)in, (GrayIntegerImage)out);
168                    }
169            }
170    
171            /**
172             * Specifies the number of bits the output image is supposed to have.
173             * @param bits number of bits in output image, from 1 to 15
174             * @throws IllegalArgumentException if bits is smaller than 1 or larger than 15
175             */
176            public void setBits(int bits)
177            {
178                    if (bits < 1)
179                    {
180                            throw new IllegalArgumentException("Number of bits must be 1 or larger.");
181                    }
182                    if (bits > 15)
183                    {
184                            throw new IllegalArgumentException("Number of bits must be 15 or smaller.");
185                    }
186                    destBits = new Integer(bits);
187            }
188    }