/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 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.renderer.composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.imageio.ImageIO;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.data.DataUtilities;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.jts.LiteCoordinateSequence;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.test.ImageAssert;
import org.geotools.map.FeatureLayer;
import org.geotools.map.GridCoverageLayer;
import org.geotools.map.MapContent;
import org.geotools.referencing.crs.DefaultEngineeringCRS;
import org.geotools.renderer.lite.RendererBaseTest;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.Symbolizer;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Runs the same compositions as the {@link CompositeTest}, but going through the StreamingRenderer
* and testing feature type and symbolizer based compositions
*
* @author Andrea Aime - GeoSolutions
*/
@RunWith(Parameterized.class)
public class StreamingRendererCompositeTest {
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private static GridCoverage2D BKG;
private static GridCoverage2D BKG2;
private static GridCoverage2D MAP;
private static GridCoverage2D MAP2;
private String composite;
public StreamingRendererCompositeTest(String composite) {
this.composite = composite;
}
@Parameters(name = "{0}")
public static Collection<Object[]> data() {
List<Object[]> result = new ArrayList<>();
// compositing modes
result.add(new Object[] { "copy" });
result.add(new Object[] { "destination" });
result.add(new Object[] { "source-over" });
result.add(new Object[] { "destination-over" });
result.add(new Object[] { "source-in" });
result.add(new Object[] { "destination-in" });
result.add(new Object[] { "source-out" });
result.add(new Object[] { "destination-out" });
result.add(new Object[] { "source-atop" });
result.add(new Object[] { "destination-atop" });
result.add(new Object[] { "xor" });
// blending modes
result.add(new Object[] { "multiply" });
result.add(new Object[] { "screen" });
result.add(new Object[] { "overlay" });
result.add(new Object[] { "darken" });
result.add(new Object[] { "lighten" });
result.add(new Object[] { "color-dodge" });
result.add(new Object[] { "color-burn" });
result.add(new Object[] { "hard-light" });
result.add(new Object[] { "soft-light" });
result.add(new Object[] { "difference" });
result.add(new Object[] { "exclusion" });
return result;
}
@BeforeClass
public static void prepareData() throws Exception {
BKG = readCoverage("bkg.png");
BKG2 = readCoverage("bkg2.png");
MAP = readCoverage("map.png");
MAP2 = readCoverage("map2.png");
}
private static GridCoverage2D readCoverage(String imageFileName) throws Exception {
BufferedImage bi = ImageIO.read(CompositeTest.class.getResourceAsStream("test-data/"
+ imageFileName));
GridCoverageFactory factory = new GridCoverageFactory();
Envelope2D envelope = new Envelope2D(DefaultEngineeringCRS.GENERIC_2D, 0, 0, bi.getWidth(),
bi.getHeight());
return factory.create(imageFileName, bi, envelope);
}
@Test
public void testCompositeFts1() throws Exception {
BufferedImage blended = composeFts(BKG, MAP);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend1-"
+ composite + ".png");
ImageAssert.assertEquals(reference, blended, 0);
}
@Test
public void testCompositeFts2() throws Exception {
BufferedImage blended = composeFts(BKG2, MAP2);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend2-"
+ composite + ".png");
ImageAssert.assertEquals(reference, blended, 0);
}
@Test
public void testCompositeExternalGraphicPoint1() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("pointBlend.sld");
BufferedImage blended = composePoint(BKG, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend1-"
+ composite + ".png");
ImageIO.write(blended, "PNG", new File(reference.getParent(), "blend1-" + composite
+ "-point.png"));
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeExternalGraphicPoint2() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("pointBlend2.sld");
BufferedImage blended = composePoint(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend2-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeRedMark() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("redMarkBlend.sld");
BufferedImage blended = composePoint(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/bkg2-red-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeRedStrokeLine() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("redLineBlend.sld");
BufferedImage blended = composeLine(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/bkg2-red-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeRedGraphicStrokeLine() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("redLineGraphicStrokeBlend.sld");
BufferedImage blended = composeLine(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/bkg2-red-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeExternalGraphicLine1() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("lineBlend.sld");
BufferedImage blended = composeLine(BKG, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend1-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeExternalGraphicLine2() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("lineBlend2.sld");
BufferedImage blended = composeLine(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend2-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeRedFill() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("redFillBlend.sld");
BufferedImage blended = composePolygon(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/bkg2-red-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeGraphicFill() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("fillBlend.sld");
BufferedImage blended = composePolygon(BKG, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend1-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
@Test
public void testCompositeGraphicFill2() throws Exception {
Style style = applyCompositeOnFirstSymbolizer("fillBlend2.sld");
BufferedImage blended = composePolygon(BKG2, style);
// compare with expected image
File reference = new File(
"./src/test/resources/org/geotools/renderer/composite/test-data/blend2-"
+ composite + ".png");
// allow some tolerance, the JDK does not do exactly the same thing as blending two images
// but only for the copy case... uh
int threshold = 50;
if ("copy".equals(composite)) {
threshold = 200;
}
ImageAssert.assertEquals(reference, blended, threshold);
}
private BufferedImage composeFts(GridCoverage2D first, GridCoverage2D second) {
// build the map content
MapContent mc = new MapContent();
StyleBuilder sb = new StyleBuilder();
Style baseStyle = sb.createStyle(sb.createRasterSymbolizer());
mc.addLayer(new GridCoverageLayer(first, baseStyle));
FeatureTypeStyle compositeFts = sb.createFeatureTypeStyle(sb.createRasterSymbolizer());
compositeFts.getOptions().put(FeatureTypeStyle.COMPOSITE, composite);
Style compositeStyle = sb.createStyle();
compositeStyle.featureTypeStyles().add(compositeFts);
mc.addLayer(new GridCoverageLayer(second, compositeStyle));
// prepare the graphics for the streaming renderer and paint
RenderedImage referenceImage = first.getRenderedImage();
BufferedImage blended = new BufferedImage(referenceImage.getWidth(),
referenceImage.getWidth(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = blended.createGraphics();
StreamingRenderer sr = new StreamingRenderer();
sr.setMapContent(mc);
sr.paint(graphics,
new Rectangle(0, 0, referenceImage.getWidth(), referenceImage.getHeight()),
ReferencedEnvelope.reference(first.getEnvelope()));
graphics.dispose();
mc.dispose();
return blended;
}
private BufferedImage composePoint(GridCoverage2D first, Style pointStyle)
throws SchemaException, IOException {
// build the map content
MapContent mc = new MapContent();
StyleBuilder sb = new StyleBuilder();
// first layer is the usual coverage
Style baseStyle = sb.createStyle(sb.createRasterSymbolizer());
mc.addLayer(new GridCoverageLayer(first, baseStyle));
// second layer is a point in the middle of the map
RenderedImage referenceImage = first.getRenderedImage();
Point midPoint = new Point(new LiteCoordinateSequence(referenceImage.getWidth() / 2d,
referenceImage.getHeight() / 2d), GEOMETRY_FACTORY);
SimpleFeatureType ft = DataUtilities.createType("test", "geom:Point:srid=4326");
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(ft);
fb.add(midPoint);
SimpleFeature pointFeature = fb.buildFeature(null);
SimpleFeatureCollection collection = DataUtilities.collection(pointFeature);
// load the point style
mc.addLayer(new FeatureLayer(collection, pointStyle));
// prepare the graphics for the streaming renderer and paint
BufferedImage blended = new BufferedImage(referenceImage.getWidth(),
referenceImage.getWidth(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = blended.createGraphics();
StreamingRenderer sr = new StreamingRenderer();
sr.setMapContent(mc);
sr.paint(graphics,
new Rectangle(0, 0, referenceImage.getWidth(), referenceImage.getHeight()),
ReferencedEnvelope.reference(first.getEnvelope()));
graphics.dispose();
mc.dispose();
return blended;
}
private BufferedImage composeLine(GridCoverage2D first, Style lineStyle)
throws SchemaException, IOException {
// build the map content
MapContent mc = new MapContent();
StyleBuilder sb = new StyleBuilder();
// first layer is the usual coverage
Style baseStyle = sb.createStyle(sb.createRasterSymbolizer());
mc.addLayer(new GridCoverageLayer(first, baseStyle));
// second layer is a line in the middle of the map
RenderedImage referenceImage = first.getRenderedImage();
LineString midLine = new LineString(new LiteCoordinateSequence(0,
referenceImage.getHeight() / 2d, referenceImage.getWidth(),
referenceImage.getHeight() / 2d), GEOMETRY_FACTORY);
SimpleFeatureType ft = DataUtilities.createType("test", "geom:LineString:srid=4326");
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(ft);
fb.add(midLine);
SimpleFeature pointFeature = fb.buildFeature(null);
SimpleFeatureCollection collection = DataUtilities.collection(pointFeature);
// load the point style
mc.addLayer(new FeatureLayer(collection, lineStyle));
// prepare the graphics for the streaming renderer and paint
BufferedImage blended = new BufferedImage(referenceImage.getWidth(),
referenceImage.getWidth(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = blended.createGraphics();
StreamingRenderer sr = new StreamingRenderer();
sr.setMapContent(mc);
sr.paint(graphics,
new Rectangle(0, 0, referenceImage.getWidth(), referenceImage.getHeight()),
ReferencedEnvelope.reference(first.getEnvelope()));
graphics.dispose();
mc.dispose();
return blended;
}
private BufferedImage composePolygon(GridCoverage2D first, Style polygonStyle)
throws SchemaException, IOException {
// build the map content
MapContent mc = new MapContent();
StyleBuilder sb = new StyleBuilder();
// first layer is the usual coverage
Style baseStyle = sb.createStyle(sb.createRasterSymbolizer());
mc.addLayer(new GridCoverageLayer(first, baseStyle));
// second layer is a polygon covering the whole map
RenderedImage referenceImage = first.getRenderedImage();
int w = referenceImage.getWidth();
int h = referenceImage.getHeight();
LinearRing shell = new LinearRing(new LiteCoordinateSequence(0, 0, 0, h, w, h, w, 0, 0, 0),
GEOMETRY_FACTORY);
Polygon polygon = new Polygon(shell, null, GEOMETRY_FACTORY);
SimpleFeatureType ft = DataUtilities.createType("test", "geom:Polygon:srid=4326");
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(ft);
fb.add(polygon);
SimpleFeature pointFeature = fb.buildFeature(null);
SimpleFeatureCollection collection = DataUtilities.collection(pointFeature);
// load the point style
mc.addLayer(new FeatureLayer(collection, polygonStyle));
// prepare the graphics for the streaming renderer and paint
BufferedImage blended = new BufferedImage(referenceImage.getWidth(),
referenceImage.getWidth(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = blended.createGraphics();
StreamingRenderer sr = new StreamingRenderer();
sr.setMapContent(mc);
sr.paint(graphics,
new Rectangle(0, 0, referenceImage.getWidth(), referenceImage.getHeight()),
ReferencedEnvelope.reference(first.getEnvelope()));
graphics.dispose();
mc.dispose();
return blended;
}
private Style applyCompositeOnFirstSymbolizer(String styleName) throws IOException {
Style style = RendererBaseTest.loadStyle(StreamingRendererCompositeTest.class, styleName);
Symbolizer symbolizer = style.featureTypeStyles().get(0).rules().get(0).symbolizers()
.get(0);
symbolizer.getOptions().put(FeatureTypeStyle.COMPOSITE, composite);
return style;
}
}