source: josm/trunk/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java@ 11726

Last change on this file since 11726 was 11726, checked in by michael2402, 7 years ago

Don't use null to indicate a rotation of 0 degrees, simply set the rotation to 0.

  • Property svn:eol-style set to native
File size: 11.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.fail;
6
7import java.awt.Graphics2D;
8import java.awt.GraphicsEnvironment;
9import java.awt.Point;
10import java.awt.RenderingHints;
11import java.awt.image.BufferedImage;
12import java.io.File;
13import java.io.FileInputStream;
14import java.io.FileNotFoundException;
15import java.io.IOException;
16import java.text.MessageFormat;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.List;
21import java.util.stream.Collectors;
22import java.util.stream.Stream;
23
24import javax.imageio.ImageIO;
25
26import org.junit.Assume;
27import org.junit.Before;
28import org.junit.Rule;
29import org.junit.Test;
30import org.junit.runner.RunWith;
31import org.junit.runners.Parameterized;
32import org.junit.runners.Parameterized.Parameters;
33import org.openstreetmap.josm.TestUtils;
34import org.openstreetmap.josm.data.Bounds;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
38import org.openstreetmap.josm.gui.NavigatableComponent;
39import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
40import org.openstreetmap.josm.gui.preferences.SourceEntry;
41import org.openstreetmap.josm.io.IllegalDataException;
42import org.openstreetmap.josm.io.OsmReader;
43import org.openstreetmap.josm.testutils.JOSMTestRules;
44
45import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
46
47/**
48 * Test cases for {@link StyledMapRenderer} and the MapCSS classes.
49 * <p>
50 * This test uses the data and reference files stored in the test data directory {@value #TEST_DATA_BASE}
51 * @author Michael Zangl
52 */
53@RunWith(Parameterized.class)
54public class MapCSSRendererTest {
55 private static final String TEST_DATA_BASE = "/renderer/";
56 /**
57 * lat = 0..1, lon = 0..1
58 */
59 private static final Bounds AREA_DEFAULT = new Bounds(0, 0, 1, 1);
60 private static final int IMAGE_SIZE = 256;
61
62 /**
63 * Minimal test rules required
64 */
65 @Rule
66 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
67 public JOSMTestRules test = new JOSMTestRules().preferences().projection();
68
69 private TestConfig testConfig;
70
71 /**
72 * The different configurations of this test.
73 *
74 * @return The parameters.
75 */
76 @Parameters(name = "{1}")
77 public static Collection<Object[]> runs() {
78 return Stream.of(
79 /** Tests for StyledMapRenderer#drawNodeSymbol */
80 new TestConfig("node-shapes", AREA_DEFAULT),
81
82 /** Text for nodes */
83 new TestConfig("node-text", AREA_DEFAULT).usesFont("DejaVu Sans"),
84
85 /** Tests that StyledMapRenderer#drawWay respects width */
86 new TestConfig("way-width", AREA_DEFAULT),
87
88 /** Tests the way color property, including alpha */
89 new TestConfig("way-color", AREA_DEFAULT),
90
91 /** Tests dashed ways. */
92 new TestConfig("way-dashes", AREA_DEFAULT),
93
94 /** Tests fill-color property */
95 new TestConfig("area-fill-color", AREA_DEFAULT),
96
97 /** Tests the fill-image property. */
98 new TestConfig("area-fill-image", AREA_DEFAULT),
99
100 /** Tests area label drawing/placement */
101 new TestConfig("area-text", AREA_DEFAULT),
102
103 /** Tests if all styles are sorted correctly. Tests {@link StyleRecord#compareTo(StyleRecord)} */
104 new TestConfig("order", AREA_DEFAULT)
105
106 ).map(e -> new Object[] {e, e.testDirectory})
107 .collect(Collectors.toList());
108 }
109
110 /**
111 * @param testConfig The config to use for this test.
112 * @param ignored The name to print it nicely
113 */
114 public MapCSSRendererTest(TestConfig testConfig, String ignored) {
115 this.testConfig = testConfig;
116 }
117
118 /**
119 * This test only runs on OpenJDK.
120 * It is ignored for other Java versions since they differ slightly in their rendering engine.
121 * @since 11691
122 */
123 @Before
124 public void forOpenJDK() {
125 String javaHome = System.getProperty("java.home");
126 Assume.assumeTrue("Test requires openJDK", javaHome != null && javaHome.contains("openjdk"));
127
128 List<String> fonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
129 for (String font : testConfig.fonts) {
130 Assume.assumeTrue("Test requires font: " + font, fonts.contains(font));
131 }
132 }
133
134 /**
135 * Run the test using {@link #testConfig}
136 * @throws Exception if an error occurs
137 */
138 @Test
139 public void testRender() throws Exception {
140 // load the data
141 DataSet dataSet = testConfig.getOsmDataSet();
142
143 // load the style
144 MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().lock();
145 try {
146 MapPaintStyles.getStyles().clear();
147
148 MapCSSStyleSource source = new MapCSSStyleSource(testConfig.getStyleSourceEntry());
149 source.loadStyleSource();
150 if (!source.getErrors().isEmpty()) {
151 fail("Failed to load style file. Errors: " + source.getErrors());
152 }
153 MapPaintStyles.getStyles().setStyleSources(Arrays.asList(source));
154 MapPaintStyles.fireMapPaintSylesUpdated();
155 MapPaintStyles.getStyles().clearCached();
156
157 } finally {
158 MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().unlock();
159 }
160
161 // create the renderer
162 BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
163 NavigatableComponent nc = new NavigatableComponent() {
164 {
165 setBounds(0, 0, IMAGE_SIZE, IMAGE_SIZE);
166 updateLocationState();
167 }
168
169 @Override
170 protected boolean isVisibleOnScreen() {
171 return true;
172 }
173
174 @Override
175 public Point getLocationOnScreen() {
176 return new Point(0, 0);
177 }
178 };
179 nc.zoomTo(testConfig.testArea);
180 dataSet.allPrimitives().stream().forEach(this::loadPrimitiveStyle);
181 dataSet.setSelected(dataSet.allPrimitives().stream().filter(n -> n.isKeyTrue("selected")).collect(Collectors.toList()));
182
183 Graphics2D g = image.createGraphics();
184 // Force all render hints to be defaults - do not use platform values
185 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
186 g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
187 g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
188 g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
189 g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
190 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
191 g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
192 g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
193 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
194 new StyledMapRenderer(g, nc, false).render(dataSet, false, testConfig.testArea);
195
196 BufferedImage reference = testConfig.getReference();
197
198 // now compute differences:
199 assertEquals(IMAGE_SIZE, reference.getWidth());
200 assertEquals(IMAGE_SIZE, reference.getHeight());
201
202 StringBuilder differences = new StringBuilder();
203 ArrayList<Point> differencePoints = new ArrayList<>();
204
205 for (int y = 0; y < reference.getHeight(); y++) {
206 for (int x = 0; x < reference.getWidth(); x++) {
207 int expected = reference.getRGB(x, y);
208 int result = image.getRGB(x, y);
209 if (!colorsAreSame(expected, result)) {
210 differencePoints.add(new Point(x, y));
211 if (differences.length() < 500) {
212 differences.append("\nDifference at ")
213 .append(x)
214 .append(",")
215 .append(y)
216 .append(": Expected ")
217 .append(Integer.toHexString(expected))
218 .append(" but got ")
219 .append(Integer.toHexString(result));
220 }
221 }
222 }
223 }
224
225 if (differencePoints.size() > 0) {
226 // You can use this to debug:
227 ImageIO.write(image, "png", new File(testConfig.getTestDirectory() + "/test-output.png"));
228
229 // Add a nice image that highlights the differences:
230 BufferedImage diffImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
231 for (Point p : differencePoints) {
232 diffImage.setRGB(p.x, p.y, 0xffff0000);
233 }
234 ImageIO.write(diffImage, "png", new File(testConfig.getTestDirectory() + "/test-differences.png"));
235
236 fail(MessageFormat.format("Images for test {0} differ at {1} points: {2}",
237 testConfig.testDirectory, differencePoints.size(), differences.toString()));
238 }
239 }
240
241 private void loadPrimitiveStyle(OsmPrimitive n) {
242 n.setHighlighted(n.isKeyTrue("highlight"));
243 if (n.isKeyTrue("disabled")) {
244 n.setDisabledState(false);
245 }
246 }
247
248 /**
249 * Check if two colors differ
250 * @param expected The expected color
251 * @param actual The actual color
252 * @return <code>true</code> if they differ.
253 */
254 private boolean colorsAreSame(int expected, int actual) {
255 int expectedAlpha = expected >> 24;
256 if (expectedAlpha == 0) {
257 return actual >> 24 == 0;
258 } else {
259 return expected == actual;
260 }
261 }
262
263 private static class TestConfig {
264 private final String testDirectory;
265 private final Bounds testArea;
266 private final ArrayList<String> fonts = new ArrayList<>();
267
268 TestConfig(String testDirectory, Bounds testArea) {
269 this.testDirectory = testDirectory;
270 this.testArea = testArea;
271 }
272
273 public TestConfig usesFont(String string) {
274 this.fonts.add(string);
275 return this;
276 }
277
278 public BufferedImage getReference() throws IOException {
279 return ImageIO.read(new File(getTestDirectory() + "/reference.png"));
280 }
281
282 private String getTestDirectory() {
283 return TestUtils.getTestDataRoot() + TEST_DATA_BASE + testDirectory;
284 }
285
286 public SourceEntry getStyleSourceEntry() {
287 return new SourceEntry(getTestDirectory() + "/style.mapcss",
288 "test style", "a test style", true // active
289 );
290 }
291
292 public DataSet getOsmDataSet() throws FileNotFoundException, IllegalDataException {
293 return OsmReader.parseDataSet(new FileInputStream(getTestDirectory() + "/data.osm"), null);
294 }
295
296 @Override
297 public String toString() {
298 return "TestConfig [testDirectory=" + testDirectory + ", testArea=" + testArea + ']';
299 }
300 }
301}
Note: See TracBrowser for help on using the repository browser.