Quantizer { public ColorIndexer buildColorIndexer(RenderedImage image) { long totalPixelCount = (long) image.getWidth() * (long) image.getHeight(); int subsx, subsy; if(subsample) { subsx = 1 + (int) (Math.log(image.getWidth()) / Math.log(8)); subsy = 1 + (int) (Math.log(image.getHeight()) / Math.log(8)); } else { subsx = 1; subsy = 1; } PackedHistogram histogram = new PackedHistogram(image, subsx, subsy); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Found " + histogram.size() + " unique colors with shift " + histogram.getShift()); LOGGER.fine("Histogram count " + histogram.pixelCount() + " and pixels " + totalPixelCount); } int colors = Math.min(histogram.size(), maxColors); List<Box> boxes = new ArrayList<Box>(); boxes.add(new Box(0, histogram.size(), totalPixelCount, histogram, null)); int sortSwitch = Math.round(colors * THRESHOLD); Comparator<Box> comparator = new SumComparator(); Comparator<Box> volumeComparator = new VolumeComparator(); while (boxes.size() < colors) { int boxIndex = 0; for (; boxIndex < boxes.size(); boxIndex++) { if (boxes.get(boxIndex).colors > 1) { break; } } if (boxIndex == boxes.size()) { break; } Box box = boxes.get(boxIndex); int spana = box.getAlphaSpan(); int spanr = box.getRedSpan(); int spang = box.getGreenSpan(); int spanb = box.getBlueSpan(); SortComponent sort; if (spana > spanr && spana > spanb && spana > spang) { sort = SortComponent.Alpha; } else if (spanr > spang && spanr > spanb) { sort = SortComponent.Red; } else if (spang > spanb) { sort = SortComponent.Green; } else { sort = SortComponent.Blue; } box.sort(sort); Box newBox = box.split(); boxes.add(newBox); if (comparator instanceof SumComparator && boxes.size() > sortSwitch) { comparator = volumeComparator; } Collections.sort(boxes, comparator); } if (LOGGER.isLoggable(Level.FINER)) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < boxes.size(); i++) { Box b = boxes.get(i); sb.append("Box " + i + ", pixels: " + b.sum + " colors: " + b.colors + " volume: " + b.getVolume() + " p*v: " + b.getVolume() * b.sum); } sb.append("\n"); LOGGER.finer("Median cut resulted in the following boxes:\n" + sb); } if(boxes.size() == 1) { boxes.add(boxes.get(0)); } PaletteEntry[] palette = new PaletteEntry[boxes.size()]; int shift = histogram.getShift(); for (int i = 0; i < boxes.size(); i++) { Box box = boxes.get(i); byte r, g, b, a; if (box.colors == 1) { int color = histogram.getColor(box.idx); r = (byte) red(color); g = (byte) green(color); b = (byte) blue(color); a = (byte) alpha(color); } else { if (MEDIAN_BOX) { int color = histogram.getColor(box.idx + box.colors / 2); r = (byte) red(color); g = (byte) green(color); b = (byte) blue(color); a = (byte) alpha(color); } else { final int start = box.idx; final int end = box.idx + box.colors; long rs, gs, bs, as, sum; rs = gs = bs = as = sum = 0; for (int idx = start; idx < end; idx++) { int color = histogram.getColor(idx); long count = histogram.getCount(idx); rs += red(color) * count; gs += green(color) * count; bs += blue(color) * count; as += alpha(color) * count; sum += count; } r = (byte) (rs / sum); g = (byte) (gs / sum); b = (byte) (bs / sum); a = (byte) (as / sum); } } palette[i] = new PaletteEntry(r, g, b, a, i); } Arrays.sort(palette); byte[][] rgba = new byte[4][palette.length]; for (int i = 0; i < palette.length; i++) { PaletteEntry pe = palette[i]; rgba[0][i] = pe.r; rgba[1][i] = pe.g; rgba[2][i] = pe.b; rgba[3][i] = pe.a; } ColorIndexer simpleMapper = new SimpleColorIndexer(rgba); ColorMap colorMap = histogram.colorMap; for (ColorEntry ce : colorMap) { int color = ce.color; int r = red(color); int g = green(color); int b = blue(color); int a = alpha(color); if (shift > 0) { r = unshift(r, shift); g = unshift(g, shift); b = unshift(b, shift); a = unshift(a, shift); } int idx = simpleMapper.getClosestIndex(r, g, b, a) & 0xFF; ce.value = idx; } ColorIndexer delegate = new MappedColorIndexer(rgba, colorMap, shift); return new CachingColorIndexer(delegate); } Quantizer(int maxColors); Quantizer subsample(); ColorIndexer buildColorIndexer(RenderedImage image);  }
@Test public void testThreeColors() { BufferedImage bi = new BufferedImage(4, 4, BufferedImage.TYPE_3BYTE_BGR); Graphics2D gr = bi.createGraphics(); gr.setColor(Color.RED); gr.fillRect(0, 0, 2, 2); gr.setColor(Color.GREEN); gr.fillRect(2, 0, 2, 2); gr.setColor(Color.BLUE); gr.fillRect(0, 2, 4, 2); gr.dispose(); ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi); IndexColorModel icm = indexer.toIndexColorModel(); assertEquals(Transparency.OPAQUE, icm.getTransparency()); assertEquals(3, icm.getNumComponents()); RenderedImage indexed = ColorIndexerDescriptor.create(bi, indexer, null); IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel(); assertEquals(icm, icm2); assertImagesSimilar(bi, indexed, 0); }
@Test public void testRedGradient() { BufferedImage bi = new BufferedImage(10, 256, BufferedImage.TYPE_3BYTE_BGR); Graphics2D gr = bi.createGraphics(); for (int i = 0; i < 256; i++) { gr.setColor(new Color(i, 0, 0)); gr.drawLine(0, i, 10, i); } gr.dispose(); ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi); IndexColorModel icm = indexer.toIndexColorModel(); assertEquals(Transparency.OPAQUE, icm.getTransparency()); assertEquals(3, icm.getNumComponents()); RenderedImage indexed = ColorIndexerDescriptor.create(bi, indexer, null); IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel(); assertEquals(icm, icm2); assertImagesSimilar(bi, indexed, 0); }
@Test public void testColorWheelBitmask() { final int SIZE = 100; BufferedImage bi = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_4BYTE_ABGR); Graphics2D gr = bi.createGraphics(); for (int s = 0; s < SIZE; s++) { int arcw = SIZE * s / SIZE; int arch = SIZE * s / SIZE; for (int h = 0; h < 360; h++) { float hue = h / 360f; float sat = s / (float) SIZE; Color c = Color.getHSBColor(hue, sat, 1F); gr.setColor(c); gr.fillArc(SIZE / 2 - arcw / 2, SIZE / 2 - arch / 2, arcw, arch, h, 1); } } gr.dispose(); ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi); IndexColorModel icm = indexer.toIndexColorModel(); assertEquals(Transparency.BITMASK, icm.getTransparency()); assertEquals(4, icm.getNumComponents()); RenderedImage indexed = ColorIndexerDescriptor.create(bi, indexer, null); IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel(); assertEquals(icm, icm2); assertImagesSimilar(bi, indexed, 250); }
@Test public void testColorWheelTranslucent() throws Exception { final int SIZE = 100; BufferedImage bi = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_4BYTE_ABGR); Graphics2D gr = bi.createGraphics(); gr.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); for (int s = 0; s < SIZE; s++) { int arcw = SIZE * s / SIZE; int arch = SIZE * s / SIZE; for (int h = 0; h < 360; h++) { float hue = h / 360f; float sat = s / (float) SIZE; Color c = Color.getHSBColor(hue, sat, 1F); c = new Color(c.getRed(), c.getGreen(), c.getBlue(), s * 255 / SIZE); gr.setColor(c); gr.fillArc(SIZE / 2 - arcw / 2, SIZE / 2 - arch / 2, arcw, arch, h, 1); } } gr.dispose(); ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi); IndexColorModel icm = indexer.toIndexColorModel(); assertEquals(Transparency.TRANSLUCENT, icm.getTransparency()); assertEquals(4, icm.getNumComponents()); RenderedImage indexed = ColorIndexerDescriptor.create(bi, indexer, null); IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel(); assertEquals(icm, icm2); assertImagesSimilar(bi, indexed, 250); }