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 }