001    /*
002     * PopularityQuantizer
003     *
004     * Copyright (c) 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.color.quantization;
009    
010    import net.sourceforge.jiu.color.analysis.Histogram3DCreator;
011    import net.sourceforge.jiu.color.data.Histogram3D;
012    import net.sourceforge.jiu.data.IntegerImage;
013    import net.sourceforge.jiu.data.Palette;
014    import net.sourceforge.jiu.data.PixelImage;
015    import net.sourceforge.jiu.data.RGB24Image;
016    import net.sourceforge.jiu.data.RGBIndex;
017    import net.sourceforge.jiu.ops.ImageToImageOperation;
018    import net.sourceforge.jiu.ops.MissingParameterException;
019    import net.sourceforge.jiu.ops.OperationFailedException;
020    import net.sourceforge.jiu.ops.WrongParameterException;
021    
022    /**
023     * Performs the popularity color quantization algorithm that maps an image to
024     * the colors occurring most frequently in the input image.
025     * The number of colors in the palette can be defined by the user of this
026     * operation with {@link #setPaletteSize(int)}.
027     * <h3>Supported image types</h3>
028     * The input image must implement {@link net.sourceforge.jiu.data.RGB24Image},
029     * the output image must be of type {@link net.sourceforge.jiu.data.Paletted8Image}.
030     * <h3>Usage example</h3>
031     * The following code snippet uses the default settings with a palette of 256 entries.
032     * <pre>
033     * PopularityQuantizer quantizer = new PopularityQuantizer();
034     * quantizer.setInputImage(image);
035     * quantizer.setPaletteSize(256);
036     * quantizer.process();
037     * PixelImage quantizedImage = quantizer.getOutputImage();
038     * </pre>
039     * @author Marco Schmidt
040     * @since 0.11.0
041     * @see ArbitraryPaletteQuantizer
042     */
043    public class PopularityQuantizer extends ImageToImageOperation implements RGBIndex, RGBQuantizer
044    {
045            private ArbitraryPaletteQuantizer arbQuantizer;
046            private int paletteSize;
047            private Palette palette;
048            private boolean doNotMap;
049    
050            public Palette createPalette()
051            {
052                    if (palette == null)
053                    {
054                            try
055                            {
056                                    palette = determinePalette();
057                                    return (Palette)palette.clone();
058                            }
059                            catch (OperationFailedException ofe)
060                            {
061                                    return null;
062                            }
063                    }
064                    else
065                    {
066                            return (Palette)palette.clone();
067                    }
068            }
069    
070            private Palette determinePalette() throws OperationFailedException
071            {
072                    Histogram3DCreator hc = new Histogram3DCreator();
073                    hc.setImage((IntegerImage)getInputImage(), RGBIndex.INDEX_RED, RGBIndex.INDEX_GREEN, RGBIndex.INDEX_BLUE);
074                    hc.process();
075                    Histogram3D hist = hc.getHistogram();
076                    if (hist == null)
077                    {
078                            throw new OperationFailedException("Could not create histogram from input image.");
079                    }
080                    int numUniqueColors = hist.getNumUsedEntries();
081                    if (numUniqueColors <= paletteSize)
082                    {
083                            throw new WrongParameterException("Input image has only " + numUniqueColors + 
084                                    " unique color(s), so it cannot be reduced to " + paletteSize +
085                                    " color(s).");
086                    }
087                    RGBColorList list = new RGBColorList(hist);
088                    list.sortByCounter(0, list.getNumEntries() - 1);
089                    Palette result = new Palette(paletteSize);
090                    int paletteIndex = paletteSize - 1;
091                    int listIndex = list.getNumEntries() - 1;
092                    while (paletteIndex >= 0)
093                    {
094                            RGBColor color = list.getColor(listIndex--);
095                            result.put(paletteIndex, 
096                                    color.getSample(RGBIndex.INDEX_RED),
097                                    color.getSample(RGBIndex.INDEX_GREEN),
098                                    color.getSample(RGBIndex.INDEX_BLUE)
099                            );
100                    }
101                    return result;
102            }
103    
104            /**
105             * Returns the number of colors in the destination image.
106             * If output is paletted, this is also the number of entries
107             * in the palette.
108             * @return number of colors in the destination
109             * @see #setPaletteSize(int)
110             */
111            public int getPaletteSize()
112            {
113                    return paletteSize;
114            }
115    
116            public int map(int[] origRgb, int[] quantizedRgb)
117            {
118                    return arbQuantizer.map(origRgb, quantizedRgb);
119            }
120    
121            public void process() throws
122                    MissingParameterException,
123                    OperationFailedException,
124                    WrongParameterException
125            {
126                    ensureInputImageIsAvailable();
127                    ensureImagesHaveSameResolution();
128                    PixelImage in = getInputImage();
129                    if (!(in instanceof RGB24Image))
130                    {
131                            throw new WrongParameterException("Input image must implement RGB24Image.");
132                    }
133                    Histogram3DCreator hc = new Histogram3DCreator();
134                    hc.setImage((IntegerImage)in, RGBIndex.INDEX_RED, RGBIndex.INDEX_GREEN, RGBIndex.INDEX_BLUE);
135                    hc.process();
136                    Histogram3D hist = hc.getHistogram();
137                    if (hist == null)
138                    {
139                            throw new OperationFailedException("Could not create histogram from input image.");
140                    }
141                    int numUniqueColors = hist.getNumUsedEntries();
142                    if (numUniqueColors <= paletteSize)
143                    {
144                            throw new WrongParameterException("Input image has only " + numUniqueColors + 
145                                    " unique color(s), so it cannot be reduced to " + paletteSize +
146                                    " color(s).");
147                    }
148                    arbQuantizer = new ArbitraryPaletteQuantizer(createPalette());
149                    if (!doNotMap)
150                    {
151                            arbQuantizer.setInputImage(in);
152                            arbQuantizer.setOutputImage(getOutputImage());
153                            arbQuantizer.process();
154                            // TODO: copy ProgressListeners to arbQuantizer 
155                            setOutputImage(arbQuantizer.getOutputImage());
156                    }
157            }
158    
159            /**
160             * Specifies whether this operation will map the image to the 
161             * new palette (true) or not (false).
162             * The latter may be interesting if only the palette is required.
163             * By default, this operation does map.
164             * @param newValue map to new image (true) or just search palette (false)
165             */
166            public void setMapping(boolean newValue)
167            {
168                    doNotMap = !newValue;
169            }
170    
171            /**
172             * Sets the number of colors that this operations is supposed to reduce
173             * the original image to.
174             * @param newPaletteSize the number of colors
175             * @throws IllegalArgumentException if the argument is smaller than 1 or larger than 256
176             * @see #getPaletteSize
177             */
178            public void setPaletteSize(int newPaletteSize)
179            {
180                    if (newPaletteSize < 1)
181                    {
182                            throw new IllegalArgumentException("Palette size must be 1 or larger.");
183                    }
184                    if (newPaletteSize > 256)
185                    {
186                            throw new IllegalArgumentException("Palette size must be at most 256.");
187                    }
188                    paletteSize = newPaletteSize;
189            }
190    }