package eu.europeana.service.ir.image.features; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import net.semanticmetadata.lire.imageanalysis.LireFeature; import net.semanticmetadata.lire.imageanalysis.ScalableColor; public class DominantColor extends ScalableColor { // <index, centroidValue> public static Map<Integer, Integer> hCentroidMap = new HashMap<Integer, Integer>(); public static List<Integer> svCentroidValues = new ArrayList<Integer>(); public static List<Integer> grayCentroidValues = new ArrayList<Integer>(); public Map<String, ColorBin> binMap = new HashMap<String, ColorBin>(); DominantColorDescriptor descriptor; // public static Set svBins(); public void initBins() { initHCentroidMap(); initSVCentroids(); initGrayCentroids(); } private void initBinMap() { if (!binMap.isEmpty()) { // clean for (Map.Entry<String, ColorBin> binEntry : binMap.entrySet()) { binEntry.getValue().reset(); } } else { // init initBins(); // fill Integer vLevel; ColorBin bin; String binName; for (Iterator<Integer> iterator = grayCentroidValues.iterator(); iterator .hasNext();) { vLevel = iterator.next(); binName = buildGrayScaleBinName(vLevel); bin = new ColorBin(binName, 0, 16, vLevel); setGrayScaleBinSize(bin, vLevel); binMap.put(bin.getName(), bin); } for (Map.Entry<Integer, Integer> hCentroid : hCentroidMap .entrySet()) { for (Integer sCentroid : svCentroidValues) { for (Integer vCentroid : svCentroidValues) { binName = buildBinNameForCentroid(hCentroid.getValue(), sCentroid, vCentroid); bin = new ColorBin(binName, hCentroid.getValue(), sCentroid, vCentroid); setColorBinSize(bin, hCentroid.getValue(), sCentroid, vCentroid); binMap.put(binName, bin); } } } } } private void setColorBinSize(ColorBin bin, Integer value, Integer sCentroid, Integer vCentroid) { bin.sethSize(43);// 6 bins bin.setsSize(getSvBinSize(sCentroid)); bin.setvSize(getSvBinSize(vCentroid)); } private int getSvBinSize(int svCentroid) { if(svCentroid == 144) return 96; else return 64; } protected void setGrayScaleBinSize(ColorBin bin, Integer vLevel) { bin.sethSize(86);//any hue, still not uniformly distributed, we use 2 avg bin size if(vLevel < 32){ bin.setsSize(255); //if v < 32, everything is black bin.setvSize(64); }else if(vLevel == 144){ bin.setsSize(32); bin.setvSize(96); }else { bin.setsSize(32); bin.setvSize(64); } } protected String buildGrayScaleBinName(Integer vLevel) { String binName; binName = buildBinNameForCentroid(0, 16, vLevel); return binName; } private String buildBinNameForCentroid(int hCentroid, int sCentroid, int vCentroid) { return toHexString(hCentroid) + toHexString(sCentroid) + toHexString(vCentroid); } private String toHexString(int b) { String ret = Integer.toHexString(b); if(ret.length() == 1) ret = "0"+ret; return ret; } private static void initGrayCentroids() { if (!grayCentroidValues.isEmpty()) return; // the static set was already initialized grayCentroidValues.add(computeGrayCentroidValue(31)); grayCentroidValues.add(computeGrayCentroidValue(95)); grayCentroidValues.add(computeGrayCentroidValue(191)); grayCentroidValues.add(computeGrayCentroidValue(255)); } private static void initSVCentroids() { if (!svCentroidValues.isEmpty()) return; // the static set was already initialized // grayscale is threated different // svCentroidValues.add(computeSVCentroidValue(31)); svCentroidValues.add(computeSVCentroidValue(95)); svCentroidValues.add(computeSVCentroidValue(191)); svCentroidValues.add(computeSVCentroidValue(255)); } protected static void initHCentroidMap() { if (!hCentroidMap.isEmpty()) return; // the static map was already initialized // the bins have the names of the H value of the Centroids // using highest values in the bins for computing the index and // hardcoded centroid value hCentroidMap.put(computeHCentroidIndex(21), 0); hCentroidMap.put(computeHCentroidIndex(64), 43); hCentroidMap.put(computeHCentroidIndex(107), 85); hCentroidMap.put(computeHCentroidIndex(150), 128); hCentroidMap.put(computeHCentroidIndex(193), 170); hCentroidMap.put(computeHCentroidIndex(236), 213); hCentroidMap.put(computeHCentroidIndex(237), 0);// her the lowest // version } public static int computeHCentroidIndex(int hValue) { // 43 = Math.round(255/6=42,5);//splitting the H value in 6 bins. // Bin0=Bin5 = [0...21, 213...255] return Math.round((float) hValue / 43); } public static int computeHCentroidValue(int hValue) { return hCentroidMap.get(computeHCentroidIndex(hValue)); } public static int computeSVCentroidValue(int svValue) { if (svValue < 32)// first bin 32 values // return 16;//centroid // not used as this is gray scale throw new IllegalArgumentException( "Input value must be in range [32 ...255]:" + svValue); if (svValue < 96) // second bin 64 values (darker if V or lighter if S ) return 64; if (svValue < 192) // third bin 96 (faded colors) return 144; if (svValue < 256) // forth bin 64 values (full colors) return 224; throw new IllegalArgumentException( "Input value must be in range [32 ...255]:" + svValue); } public static int computeGrayCentroidValue(int vValue) { if (vValue < 32)// first bin 32 values = black return 16;// centroid if (vValue < 96) // second bin 64 values = dark return 64; if (vValue < 192) // third bin 96 = gray return 144; if (vValue < 256) // forth bin 64 values = light return 224; throw new IllegalArgumentException( "Input value must be in range [0...255]:" + vValue); } boolean isGrayScale(int h, int s, int v) { return s < 32 || v < 32; } public DominantColor() { super(); // init(); // throw new RuntimeException("To test!"); } @Override protected void init() { super.init(); initBinMap(); //reset descriptor descriptor = null; } @Override public void extract(BufferedImage image) { super.extract(image); // initBinMap(); descriptor = new DominantColorDescriptor(binMap.values(), _xSize, _ySize); //descriptor.setImage(image.) } @Override protected void _Quant(int H, int S, int V, int m, int n) { super._Quant(H, S, V, m, n); // compute ColorBin String binName; if (isGrayScale(H, S, V)) { binName = buildGrayScaleBinName(computeGrayCentroidValue(V)); } else { binName = buildBinNameForCentroid(computeHCentroidValue(H), computeSVCentroidValue(S), computeSVCentroidValue(V)); } binMap.get(binName).addPixel(H, S, V); } protected int[] computeOrderedDistribution(int[] histogram) { int[] distribution = Arrays.copyOf(histogram, histogram.length); Arrays.sort(distribution); float size = _ySize * _xSize; for (int i = 0; i < distribution.length; i++) { distribution[i] = (int) (distribution[i] * 100 / size); } return distribution; } protected int computeDominantValueCount(int[] distribution, int percentage) { int sum = 0; int count = 0; for (int i = 0; i < distribution.length; i++) { sum += distribution[distribution.length - (i + 1)];// distribution // is ordered in // ascending // order count++; if (sum >= percentage) return count; } return count; } protected LireFeature extractFeatures(String extractorClassName, File imageFile) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException { InputStream in = new FileInputStream(imageFile); BufferedImage image = ImageIO.read(in); extract(image); return this; } public Map<String, ColorBin> getBinMap() { return binMap; } public DominantColorDescriptor getDescriptor() { return descriptor; } public byte[] getByteArrayRepresentation() { // out.writeByte(version); // out.writeByte(length); // for (int i = 0; i < length; i++) { // out.writeByte(h[i]); // out.writeByte(s[i]); // out.writeByte(v[i]); // out.writeShort(score[i]); // } // // return SerializationUtils.toByteArray(null); throw new RuntimeException("Not suported yet"); } public void setByteArrayRepresentation(byte[] in) { throw new RuntimeException("Not suported yet"); } public void setByteArrayRepresentation(byte[] in, int offset, int length) { throw new RuntimeException("Not suported yet"); } public double[] getDoubleHistogram() { throw new RuntimeException("Not suported yet"); } @Override public String getStringRepresentation() { StringBuilder builder = new StringBuilder(); Collection<ColorBin> bins = getDescriptor().getBins(); int length = 10; int i = 0; for (ColorBin colorBin : bins) { builder.append(colorBin.hCentroid).append(' '); builder.append(colorBin.sCentroid).append(' '); builder.append(colorBin.vCentroid).append(' '); builder.append(colorBin.getNormalizedScore()); //keep top 10 only and do not append last empty space if(++i >= length) break; builder.append(' '); } return builder.toString(); } }