/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2015, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.image.palette;
import static org.junit.Assert.*;
import it.geosolutions.jaiext.colorindexer.ColorIndexer;
import it.geosolutions.jaiext.colorindexer.Quantizer;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import javax.media.jai.PlanarImage;
import org.geotools.image.ImageWorker;
import org.geotools.image.test.ImageAssert;
import org.junit.Test;
public class QuantizerTest {
@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();
// simple palette checks
ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi);
IndexColorModel icm = indexer.toIndexColorModel();
assertEquals(Transparency.OPAQUE, icm.getTransparency());
assertEquals(3, icm.getNumComponents());
// quantize and check
RenderedImage indexed = new ImageWorker(bi).colorIndex(indexer).getRenderedImage();
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();
// simple palette checks
ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi);
IndexColorModel icm = indexer.toIndexColorModel();
assertEquals(Transparency.OPAQUE, icm.getTransparency());
assertEquals(3, icm.getNumComponents());
// quantize and check
RenderedImage indexed = new ImageWorker(bi).colorIndex(indexer).getRenderedImage();
IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel();
assertEquals(icm, icm2);
assertImagesSimilar(bi, indexed, 0);
}
@Test
public void testRedGradientSubsample() {
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();
// simple palette checks
ColorIndexer indexer = new Quantizer(256).subsample().buildColorIndexer(bi);
IndexColorModel icm = indexer.toIndexColorModel();
assertEquals(Transparency.OPAQUE, icm.getTransparency());
assertEquals(3, icm.getNumComponents());
// quantize and check
RenderedImage indexed = new ImageWorker(bi).colorIndex(indexer).getRenderedImage();
IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel();
assertEquals(icm, icm2);
assertImagesSimilar(bi, indexed, 2); // allow a very small color difference
}
@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();
// simple palette checks
ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi);
IndexColorModel icm = indexer.toIndexColorModel();
assertEquals(Transparency.BITMASK, icm.getTransparency());
assertEquals(4, icm.getNumComponents());
// quantize and check
RenderedImage indexed = new ImageWorker(bi).colorIndex(indexer).getRenderedImage();
IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel();
assertEquals(icm, icm2);
// the source image has up to 36000 colors (the distance has been checked visually, and
// yes, in this particular case pngquant and pngnq do much better, the Quantizer algorithm
// has been setup to preserve translucent borders better than the inside)
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();
// simple palette checks
ColorIndexer indexer = new Quantizer(256).buildColorIndexer(bi);
IndexColorModel icm = indexer.toIndexColorModel();
assertEquals(Transparency.TRANSLUCENT, icm.getTransparency());
assertEquals(4, icm.getNumComponents());
// quantize and check
RenderedImage indexed = new ImageWorker(bi).colorIndex(indexer).getRenderedImage();
IndexColorModel icm2 = (IndexColorModel) indexed.getColorModel();
assertEquals(icm, icm2);
// the source image has up to 36000 colors + alpha (the distance has been checked visually)
assertImagesSimilar(bi, indexed, 250);
}
/**
* Checks two images are visually equal given a certain maximum color distance. For a
* better tool you might want to check out {@link ImageAssert}, but that works only with RGB
* images, this one is color model independent
*
* @param image1
* @param image2
* @param maxColorDistance
*/
private void assertImagesSimilar(RenderedImage image1, RenderedImage image2,
int maxColorDistance) {
assertEquals(image1.getWidth(), image2.getWidth());
assertEquals(image1.getHeight(), image2.getHeight());
BufferedImage bi1 = toBufferedImage(image1);
BufferedImage bi2 = toBufferedImage(image2);
for (int i = 0; i < bi1.getWidth(); i++) {
for (int j = 0; j < bi1.getHeight(); j++) {
int c1 = bi1.getRGB(i, j);
int c2 = bi2.getRGB(i, j);
int a1 = ColorUtils.alpha(c1);
int a2 = ColorUtils.alpha(c2);
int r1 = ColorUtils.red(c1);
int r2 = ColorUtils.red(c2);
int g1 = ColorUtils.green(c1);
int g2 = ColorUtils.green(c2);
int b2 = ColorUtils.blue(c2);
int b1 = ColorUtils.blue(c1);
int dr = r1 - r2;
int dg = g1 - g2;
int db = b1 - b2;
int da = a1 - a2;
double d = Math.sqrt((1.5 * dr * dr + 2 * dg * dg + db * db + 2 * da * da) / (1.5 + 2 + 1 + 2));
assertTrue("Color distance " + d + " excessive for pixels " + i + "," + j,
d <= maxColorDistance);
}
}
}
private BufferedImage toBufferedImage(RenderedImage ri) {
if (ri instanceof BufferedImage) {
return (BufferedImage) ri;
} else {
return PlanarImage.wrapRenderedImage(ri).getAsBufferedImage();
}
}
}