/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010, Geomatys * * 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.geotoolkit.display2d; import java.awt.image.Raster; import org.geotoolkit.style.StyleConstants; import org.geotoolkit.map.CoverageMapLayer; import javax.imageio.ImageIO; import org.geotoolkit.display2d.service.OutputDef; import java.io.File; import java.io.IOException; import java.util.Set; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.opengis.coverage.Coverage; import org.opengis.referencing.operation.TransformException; import org.opengis.style.PointSymbolizer; import org.opengis.geometry.Envelope; import org.opengis.util.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.style.GraphicalSymbol; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.internal.referencing.GeodeticObjectBuilder; import org.apache.sis.referencing.CommonCRS; import org.geotoolkit.coverage.CoverageStack; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.grid.GridCoverageBuilder; import org.geotoolkit.data.FeatureStoreUtilities; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureWriter; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display2d.canvas.J2DCanvas; import org.geotoolkit.display2d.canvas.painter.GradiantColorPainter; import org.geotoolkit.display2d.service.CanvasDef; import org.geotoolkit.display2d.service.DefaultPortrayalService; import org.geotoolkit.display2d.service.PortrayalExtension; import org.geotoolkit.display2d.service.SceneDef; import org.geotoolkit.display2d.service.ViewDef; import org.geotoolkit.factory.Hints; import org.geotoolkit.map.MapBuilder; import org.geotoolkit.map.MapContext; import org.geotoolkit.map.MapLayer; import org.apache.sis.referencing.CRS; import org.geotoolkit.style.DefaultStyleFactory; import org.geotoolkit.style.MutableFeatureTypeStyle; import org.geotoolkit.style.MutableStyle; import org.geotoolkit.style.MutableStyleFactory; import org.geotoolkit.test.Assert; import org.apache.sis.geometry.Envelopes; import org.geotoolkit.data.query.QueryBuilder; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import static org.geotoolkit.style.StyleConstants.*; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.filter.Filter; /** * Testing color model optimisations. * * @author Johann Sorel (Geomatys) */ public class ColorModelTest extends org.geotoolkit.test.TestBase { private static final GeometryFactory GF = new GeometryFactory(); private static final MutableStyleFactory SF = new DefaultStyleFactory(); private final List<FeatureCollection> featureColls = new ArrayList<>(); private final List<GridCoverage2D> coverages = new ArrayList<>(); private final List<Envelope> envelopes = new ArrayList<>(); private final List<Date[]> dates = new ArrayList<>(); private final List<Double[]> elevations = new ArrayList<>(); private final Coverage coverage4D; public ColorModelTest() throws Exception { final GridCoverageBuilder gcb = new GridCoverageBuilder(); // create the feature collection for tests ----------------------------- final FeatureTypeBuilder sftb = new FeatureTypeBuilder(); sftb.setName("test"); sftb.addAttribute(Point.class).setName("geom").setCRS(CommonCRS.WGS84.normalizedGeographic()); sftb.addAttribute(String.class).setName("att1"); sftb.addAttribute(Double.class).setName("att2"); final FeatureType sft = sftb.build(); FeatureCollection col = FeatureStoreUtilities.collection("id", sft); final FeatureWriter writer = col.getSession().getFeatureStore().getFeatureWriter(QueryBuilder.filtered(sft.getName().toString(),Filter.EXCLUDE)); Feature sf = writer.next(); sf.setPropertyValue("geom", GF.createPoint(new Coordinate(0, 0))); sf.setPropertyValue("att1", "value1"); writer.write(); sf = writer.next(); sf.setPropertyValue("geom", GF.createPoint(new Coordinate(-180, -90))); sf.setPropertyValue("att1", "value1"); writer.write(); sf = writer.next(); sf.setPropertyValue("geom", GF.createPoint(new Coordinate(-180, 90))); sf.setPropertyValue("att1", "value1"); writer.write(); sf = writer.next(); sf.setPropertyValue("geom", GF.createPoint(new Coordinate(180, -90))); sf.setPropertyValue("att1", "value1"); writer.write(); sf = writer.next(); sf.setPropertyValue("geom", GF.createPoint(new Coordinate(180, -90))); sf.setPropertyValue("att1", "value1"); writer.write(); writer.close(); featureColls.add(col); //create a serie of envelopes for tests -------------------------------- GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -90, 90); env.setRange(1, -180, 180); envelopes.add(env); env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -12, 31); env.setRange(1, -5, 46); envelopes.add(env); env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); envelopes.add(env); env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic()); env.setRange(0, -5, 46); env.setRange(1, -12, 31); envelopes.add(env); env = new GeneralEnvelope(CRS.forCode("EPSG:3395")); env.setRange(0, -1200000, 3100000); env.setRange(1, -500000, 4600000); envelopes.add(env); //create a serie of date ranges ---------------------------------------- dates.add(new Date[]{new Date(1000),new Date(15000)}); dates.add(new Date[]{null, new Date(15000)}); dates.add(new Date[]{new Date(1000),null}); dates.add(new Date[]{null, null}); //create a serie of elevation ranges ----------------------------------- elevations.add(new Double[]{-15d, 50d}); elevations.add(new Double[]{null, 50d}); elevations.add(new Double[]{-15d, null}); elevations.add(new Double[]{null, null}); //create some coverages ------------------------------------------------ env = new GeneralEnvelope(CRS.forCode("EPSG:32738")); env.setRange(0, 695035, 795035); env.setRange(1, 7545535, 7645535); BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); Graphics2D g = img.createGraphics(); g.setColor(Color.RED); g.fill(new Rectangle(0, 0, 100, 100)); gcb.reset(); gcb.setEnvelope(env); gcb.setRenderedImage(img); GridCoverage2D coverage = gcb.getGridCoverage2D(); coverages.add(coverage); env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -10, 25); env.setRange(1, -56, -21); img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); g = img.createGraphics(); g.setColor(Color.RED); g.fill(new Rectangle(0, 0, 100, 100)); gcb.reset(); gcb.setEnvelope(env); gcb.setRenderedImage(img); coverage = gcb.getGridCoverage2D(); coverages.add(coverage); //create some ND coverages --------------------------------------------- CoordinateReferenceSystem crs = new GeodeticObjectBuilder().addName("4D crs").createCompoundCRS(CommonCRS.WGS84.geographic(), CommonCRS.Vertical.ELLIPSOIDAL.crs(), CommonCRS.Temporal.JAVA.crs()); List<Coverage> temps = new ArrayList<Coverage>(); for(int i=0; i<10; i++){ final List<Coverage> eles = new ArrayList<Coverage>(); for(int k=0;k<10;k++){ env = new GeneralEnvelope(crs); env.setRange(0, 0, 10); env.setRange(1, 0, 10); env.setRange(2, k, k+1); env.setRange(3, i, i+1); img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); gcb.reset(); gcb.setEnvelope(env); gcb.setRenderedImage(img); coverage = gcb.getGridCoverage2D(); eles.add(coverage); } temps.add(new CoverageStack("3D", eles)); } coverage4D = new CoverageStack("4D", coverages); } @BeforeClass public static void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() throws Exception { } @Test public void testNoDataCM() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), null), new SceneDef(context), new ViewDef(env)); assertTrue( img.getColorModel() instanceof IndexColorModel); final IndexColorModel icm = (IndexColorModel) img.getColorModel(); //we should have only two value assertEquals(2, icm.getMapSize()); //with one being transparent assertTrue(icm.getTransparentPixel() >= 0); } @Test public void testSolidColorBackground() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.GREEN), new SceneDef(context), new ViewDef(env)); assertTrue( img.getColorModel() instanceof IndexColorModel); final IndexColorModel icm = (IndexColorModel) img.getColorModel(); //we should have only two value assertEquals(2, icm.getMapSize()); assertTrue(Color.GREEN.getRGB() == icm.getRGB(0) || Color.GREEN.getRGB() == icm.getRGB(1)); } @Test public void testSolidColorBackgroundWithAA() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.GREEN), new SceneDef(context, new Hints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)), new ViewDef(env)); //background is single color opaque we should obtain an RGB color model because of active //anti-aliasing assertTrue(!(img.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, img.getColorModel().getColorSpace().getType()); assertEquals(3, img.getColorModel().getNumComponents()); assertEquals(3, img.getColorModel().getNumColorComponents()); } @Test public void testAlphaColorBackground() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), new Color(0.5f, 0.1f, 0.7f, 0.6f)), new SceneDef(context), new ViewDef(env)); //background is not opaque we should obtain an RGBA color model assertTrue(!(img.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, img.getColorModel().getColorSpace().getType()); assertEquals(4, img.getColorModel().getNumComponents()); assertEquals(3, img.getColorModel().getNumColorComponents()); } @Test public void testOpaqueUnpredictableBackground() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600),null), new SceneDef(context,null, new PortrayalExtension() { @Override public void completeCanvas(J2DCanvas canvas) throws PortrayalException { canvas.setBackgroundPainter(new GradiantColorPainter()); } }), new ViewDef(env)); //background is opaque we should obtain an RGB color model assertTrue(!(img.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, img.getColorModel().getColorSpace().getType()); assertEquals(3, img.getColorModel().getNumComponents()); assertEquals(3, img.getColorModel().getNumColorComponents()); } @Test public void testOpaqueStyleDatas() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); context.layers().add(createLayer(Color.BLUE,Color.RED,Color.YELLOW)); context.layers().add(createLayer(Color.BLUE,Color.GREEN,Color.GRAY)); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.WHITE), new SceneDef(context), new ViewDef(env)); assertTrue( img.getColorModel() instanceof IndexColorModel); final IndexColorModel icm = (IndexColorModel) img.getColorModel(); assertEquals(Transparency.OPAQUE, icm.getTransparency()); assertEquals(-1, icm.getTransparentPixel()); assertFalse(icm.hasAlpha()); //we should have only six value assertEquals(6, icm.getMapSize()); final Set<Integer> colors = new HashSet<Integer>(); colors.add(Color.WHITE.getRGB()); colors.add(Color.BLUE.getRGB()); colors.add(Color.RED.getRGB()); colors.add(Color.YELLOW.getRGB()); colors.add(Color.GREEN.getRGB()); colors.add(Color.GRAY.getRGB()); for(int i=0;i<icm.getMapSize();i++){ assertTrue(colors.contains(icm.getRGB(i))); } } @Test public void testRasterData() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException { final MapContext context = MapBuilder.createContext(); context.layers().add(MapBuilder.createCoverageLayer(coverages.get(0), SF.style(SF.rasterSymbolizer()), "test")); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); final BufferedImage img = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.WHITE), new SceneDef(context), new ViewDef(env)); //background is opaque we should obtain an RGB color model since raster styles //are unpredictable assertTrue(!(img.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, img.getColorModel().getColorSpace().getType()); assertEquals(3, img.getColorModel().getNumComponents()); assertEquals(3, img.getColorModel().getNumColorComponents()); } /** * Test that when asking a jpeg output, the resulting writen image has been * configured with a white background. */ @Test public void testJPEGOutput() throws NoSuchAuthorityCodeException, FactoryException, IOException, PortrayalException{ final MapContext context = MapBuilder.createContext(); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic()); env.setRange(0, -180, 180); env.setRange(1, -90, 90); File tempFile = File.createTempFile("testjpeg", ".jpg"); tempFile.deleteOnExit(); DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), null), new SceneDef(context), new ViewDef(env), new OutputDef("image/jpeg", tempFile)); //we should obtain a white background image final BufferedImage img = ImageIO.read(tempFile); for(int x=0; x<img.getWidth(); x++){ for(int y=0; y<img.getHeight(); y++){ //jpeg can't encode a perfect white image, CMY to RGB conversion lost I guess. Color c = new Color(img.getRGB(x, y)); Assert.assertBetween("color is not white", 250, 255, c.getRed()); Assert.assertBetween("color is not white", 250, 255, c.getGreen()); Assert.assertBetween("color is not white", 250, 255, c.getBlue()); Assert.assertBetween("color is not white", 250, 255, c.getAlpha()); } } } @Test public void testReprojectionCoverageARGB() throws TransformException, PortrayalException, NoSuchAuthorityCodeException, FactoryException{ //create a test coverage final BufferedImage img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); final Graphics2D g2d = img.createGraphics(); g2d.setColor(Color.GREEN); g2d.fillRect(0, 0, 500, 500); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic()); env.setRange(0, 0, 20); env.setRange(1, 0, 20); final GridCoverageBuilder gcb = new GridCoverageBuilder(); gcb.setEnvelope(env); gcb.setRenderedImage(img); final GridCoverage2D coverage = gcb.getGridCoverage2D(); //display it final MapContext context = MapBuilder.createContext(); final CoverageMapLayer cl = MapBuilder.createCoverageLayer(coverage, SF.style(StyleConstants.DEFAULT_RASTER_SYMBOLIZER), "coverage"); context.layers().add(cl); final Envelope envelope = Envelopes.transform(env, CRS.forCode("EPSG:3031")); final BufferedImage result = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.WHITE), new SceneDef(context), new ViewDef(envelope)); //background is opaque we should obtain an RGB color model since raster styles //are unpredictable assertTrue(!(result.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, result.getColorModel().getColorSpace().getType()); assertEquals(3, result.getColorModel().getNumComponents()); assertEquals(3, result.getColorModel().getNumColorComponents()); //check we don't have any black reprojection pixels int[] buffer = new int[4]; final Raster raster = result.getData(); for(int x=0;x<raster.getWidth();x++){ for(int y=0;y<raster.getHeight();y++){ raster.getPixel(x, y, buffer); if(buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0){ //black pixel fail("reprojection should not have generated black pixels."); } } } } @Test @org.junit.Ignore("Test failure to investigate: reprojection should not have generated black pixels.") public void testReprojectionCoverageRGB() throws TransformException, PortrayalException, NoSuchAuthorityCodeException, FactoryException{ //create a test coverage final BufferedImage img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB); final Graphics2D g2d = img.createGraphics(); g2d.setColor(Color.GREEN); g2d.fillRect(0, 0, 500, 500); final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic()); env.setRange(0, 0, 20); env.setRange(1, 0, 20); final GridCoverageBuilder gcb = new GridCoverageBuilder(); gcb.setEnvelope(env); gcb.setRenderedImage(img); final GridCoverage2D coverage = gcb.getGridCoverage2D(); //display it final MapContext context = MapBuilder.createContext(); final CoverageMapLayer cl = MapBuilder.createCoverageLayer(coverage, SF.style(StyleConstants.DEFAULT_RASTER_SYMBOLIZER), "coverage"); context.layers().add(cl); final Envelope envelope = Envelopes.transform(env, CRS.forCode("EPSG:3031")); final BufferedImage result = DefaultPortrayalService.portray( new CanvasDef(new Dimension(800, 600), Color.WHITE), new SceneDef(context), new ViewDef(envelope)); //background is opaque we should obtain an RGB color model since raster styles //are unpredictable assertTrue(!(result.getColorModel() instanceof IndexColorModel)); assertEquals(ColorSpace.TYPE_RGB, result.getColorModel().getColorSpace().getType()); assertEquals(3, result.getColorModel().getNumComponents()); assertEquals(3, result.getColorModel().getNumColorComponents()); //check we don't have any black reprojection pixels int[] buffer = new int[4]; final Raster raster = result.getData(); for(int x=0;x<raster.getWidth();x++){ for(int y=0;y<raster.getHeight();y++){ raster.getPixel(x, y, buffer); if(buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0){ //black pixel fail("reprojection should not have generated black pixels."); } } } } private MapLayer createLayer(final Color ... colors){ return MapBuilder.createFeatureLayer(featureColls.get(0), createStyle(colors)); } private static MutableStyle createStyle(final Color ... colors){ final MutableStyle style = SF.style(); for(Color c : colors){ final MutableFeatureTypeStyle fts = SF.featureTypeStyle(); final GraphicalSymbol gs = SF.mark(MARK_CIRCLE, SF.fill(c), SF.stroke(c, 1)); final PointSymbolizer symbol = SF.pointSymbolizer(SF.graphic( Collections.singletonList(gs), LITERAL_ONE_FLOAT, LITERAL_ONE_FLOAT, LITERAL_ZERO_FLOAT, DEFAULT_ANCHOR_POINT, DEFAULT_DISPLACEMENT), null); fts.rules().add(SF.rule(symbol)); style.featureTypeStyles().add(fts); } return style; } }