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

Last change on this file since 12978 was 12978, checked in by bastiK, 7 years ago

promote old MapCSS reference file to MapCSSRendererTest checks

  • Property svn:eol-style set to native
File size: 11.7 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.GraphicsEnvironment;
8import java.awt.Point;
9import java.awt.image.BufferedImage;
10import java.io.File;
11import java.io.FileInputStream;
12import java.io.FileNotFoundException;
13import java.io.IOException;
14import java.text.MessageFormat;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.List;
20import java.util.stream.Collectors;
21import java.util.stream.Stream;
22
23import javax.imageio.ImageIO;
24
25import org.junit.Assume;
26import org.junit.Before;
27import org.junit.Rule;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30import org.junit.runners.Parameterized;
31import org.junit.runners.Parameterized.Parameters;
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.TestUtils;
34import org.openstreetmap.josm.data.Bounds;
35import org.openstreetmap.josm.data.ProjectionBounds;
36import org.openstreetmap.josm.data.osm.DataSet;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
39import org.openstreetmap.josm.io.IllegalDataException;
40import org.openstreetmap.josm.io.OsmReader;
41import org.openstreetmap.josm.testutils.JOSMTestRules;
42
43import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
45/**
46 * Test cases for {@link StyledMapRenderer} and the MapCSS classes.
47 * <p>
48 * This test uses the data and reference files stored in the test data directory {@value #TEST_DATA_BASE}
49 * @author Michael Zangl
50 */
51@RunWith(Parameterized.class)
52public class MapCSSRendererTest {
53 private static final String TEST_DATA_BASE = "/renderer/";
54 /**
55 * lat = 0..1, lon = 0..1
56 */
57 private static final Bounds AREA_DEFAULT = new Bounds(0, 0, 1, 1);
58 private static final int IMAGE_SIZE = 256;
59
60 /**
61 * Minimal test rules required
62 */
63 @Rule
64 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
65 public JOSMTestRules test = new JOSMTestRules().preferences().projection();
66
67 private TestConfig testConfig;
68
69 // development flag - set to true in order to update all reference images
70 private static final boolean UPDATE_ALL = false;
71
72 /**
73 * The different configurations of this test.
74 *
75 * @return The parameters.
76 */
77 @Parameters(name = "{1}")
78 public static Collection<Object[]> runs() {
79 return Stream.of(
80 /** Tests for StyledMapRenderer#drawNodeSymbol */
81 new TestConfig("node-shapes", AREA_DEFAULT),
82
83 /** Text for nodes */
84 new TestConfig("node-text", AREA_DEFAULT).usesFont("DejaVu Sans"),
85
86 /** Tests that StyledMapRenderer#drawWay respects width */
87 new TestConfig("way-width", AREA_DEFAULT),
88
89 /** Tests the way color property, including alpha */
90 new TestConfig("way-color", AREA_DEFAULT),
91
92 /** Tests dashed ways. */
93 new TestConfig("way-dashes", AREA_DEFAULT),
94
95 /** Tests dashed way clamping algorithm */
96 new TestConfig("way-dashes-clamp", AREA_DEFAULT),
97
98 /** Tests fill-color property */
99 new TestConfig("area-fill-color", AREA_DEFAULT),
100
101 /** Tests the fill-image property. */
102 new TestConfig("area-fill-image", AREA_DEFAULT),
103
104 /** Tests area label drawing/placement */
105 new TestConfig("area-text", AREA_DEFAULT),
106
107 /** Tests area icon drawing/placement */
108 new TestConfig("area-icon", AREA_DEFAULT),
109
110 /** Tests if all styles are sorted correctly. Tests {@link StyleRecord#compareTo(StyleRecord)} */
111 new TestConfig("order", AREA_DEFAULT),
112
113 /** Tests repeat-image feature for ways */
114 new TestConfig("way-repeat-image", AREA_DEFAULT),
115 /** Tests the clamping for repeat-images and repeat-image-phase */
116 new TestConfig("way-repeat-image-clamp", AREA_DEFAULT),
117
118 /** Tests text along a way */
119 new TestConfig("way-text", AREA_DEFAULT),
120
121 /** Another test for node shapes */
122 new TestConfig("node-shapes2").setImageWidth(600),
123 /** Tests default values for node shapes */
124 new TestConfig("node-shapes-default"),
125 /** Tests node shapes with both fill and stroke combined */
126 new TestConfig("node-shapes-combined"),
127 /** Another test for dashed ways */
128 new TestConfig("way-dashes2"),
129 /** Tests node text placement */
130 new TestConfig("node-text2"),
131 /** Tests relation link selector */
132 new TestConfig("relation-linkselector"),
133 /** Tests parent selector on relation */
134 new TestConfig("relation-parentselector")
135
136 ).map(e -> new Object[] {e, e.testDirectory})
137 .collect(Collectors.toList());
138 }
139
140 /**
141 * @param testConfig The config to use for this test.
142 * @param ignored The name to print it nicely
143 */
144 public MapCSSRendererTest(TestConfig testConfig, String ignored) {
145 this.testConfig = testConfig;
146 }
147
148 /**
149 * This test only runs on OpenJDK.
150 * It is ignored for other Java versions since they differ slightly in their rendering engine.
151 * @since 11691
152 */
153 @Before
154 public void forOpenJDK() {
155 String javaHome = System.getProperty("java.home");
156 Assume.assumeTrue("Test requires openJDK", javaHome != null && javaHome.contains("openjdk"));
157
158 List<String> fonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
159 for (String font : testConfig.fonts) {
160 Assume.assumeTrue("Test requires font: " + font, fonts.contains(font));
161 }
162 }
163
164 /**
165 * Run the test using {@link #testConfig}
166 * @throws Exception if an error occurs
167 */
168 @Test
169 public void testRender() throws Exception {
170 // Force reset of preferences
171 StyledMapRenderer.PREFERENCE_ANTIALIASING_USE.put(true);
172 StyledMapRenderer.PREFERENCE_TEXT_ANTIALIASING.put("gasp");
173
174 // load the data
175 DataSet dataSet = testConfig.getOsmDataSet();
176 dataSet.allPrimitives().stream().forEach(this::loadPrimitiveStyle);
177 dataSet.setSelected(dataSet.allPrimitives().stream().filter(n -> n.isKeyTrue("selected")).collect(Collectors.toList()));
178
179 ProjectionBounds pb = new ProjectionBounds();
180 pb.extend(Main.getProjection().latlon2eastNorth(testConfig.getTestArea().getMin()));
181 pb.extend(Main.getProjection().latlon2eastNorth(testConfig.getTestArea().getMax()));
182 double scale = (pb.maxEast - pb.minEast) / testConfig.imageWidth;
183
184 RenderingHelper.StyleData sd = new RenderingHelper.StyleData();
185 sd.styleUrl = testConfig.getStyleSourceUrl();
186 RenderingHelper rh = new RenderingHelper(dataSet, testConfig.getTestArea(), scale, Collections.singleton(sd));
187 rh.setFillBackground(false);
188 BufferedImage image = rh.render();
189
190 if (UPDATE_ALL) {
191 ImageIO.write(image, "png", new File(testConfig.getTestDirectory() + "/reference.png"));
192 return;
193 }
194
195 BufferedImage reference = testConfig.getReference();
196
197 // now compute differences:
198 assertEquals(image.getWidth(), reference.getWidth());
199 assertEquals(image.getHeight(), reference.getHeight());
200
201 StringBuilder differences = new StringBuilder();
202 ArrayList<Point> differencePoints = new ArrayList<>();
203
204 for (int y = 0; y < reference.getHeight(); y++) {
205 for (int x = 0; x < reference.getWidth(); x++) {
206 int expected = reference.getRGB(x, y);
207 int result = image.getRGB(x, y);
208 if (!colorsAreSame(expected, result)) {
209 differencePoints.add(new Point(x, y));
210 if (differences.length() < 500) {
211 differences.append("\nDifference at ")
212 .append(x)
213 .append(",")
214 .append(y)
215 .append(": Expected ")
216 .append(Integer.toHexString(expected))
217 .append(" but got ")
218 .append(Integer.toHexString(result));
219 }
220 }
221 }
222 }
223
224 if (differencePoints.size() > 0) {
225 // You can use this to debug:
226 ImageIO.write(image, "png", new File(testConfig.getTestDirectory() + "/test-output.png"));
227
228 // Add a nice image that highlights the differences:
229 BufferedImage diffImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
230 for (Point p : differencePoints) {
231 diffImage.setRGB(p.x, p.y, 0xffff0000);
232 }
233 ImageIO.write(diffImage, "png", new File(testConfig.getTestDirectory() + "/test-differences.png"));
234
235 fail(MessageFormat.format("Images for test {0} differ at {1} points: {2}",
236 testConfig.testDirectory, differencePoints.size(), differences.toString()));
237 }
238 }
239
240 private void loadPrimitiveStyle(OsmPrimitive n) {
241 n.setHighlighted(n.isKeyTrue("highlight"));
242 if (n.isKeyTrue("disabled")) {
243 n.setDisabledState(false);
244 }
245 }
246
247 /**
248 * Check if two colors differ
249 * @param expected The expected color
250 * @param actual The actual color
251 * @return <code>true</code> if they differ.
252 */
253 private boolean colorsAreSame(int expected, int actual) {
254 int expectedAlpha = expected >> 24;
255 if (expectedAlpha == 0) {
256 return actual >> 24 == 0;
257 } else {
258 return expected == actual;
259 }
260 }
261
262 private static class TestConfig {
263 private final String testDirectory;
264 private Bounds testArea;
265 private final ArrayList<String> fonts = new ArrayList<>();
266 private DataSet ds;
267 private int imageWidth = IMAGE_SIZE;
268
269 TestConfig(String testDirectory, Bounds testArea) {
270 this.testDirectory = testDirectory;
271 this.testArea = testArea;
272 }
273
274 TestConfig(String testDirectory) {
275 this.testDirectory = testDirectory;
276 }
277
278 public TestConfig setImageWidth(int imageWidth) {
279 this.imageWidth = imageWidth;
280 return this;
281 }
282
283 public TestConfig usesFont(String string) {
284 this.fonts.add(string);
285 return this;
286 }
287
288 public BufferedImage getReference() throws IOException {
289 return ImageIO.read(new File(getTestDirectory() + "/reference.png"));
290 }
291
292 private String getTestDirectory() {
293 return TestUtils.getTestDataRoot() + TEST_DATA_BASE + testDirectory;
294 }
295
296 public String getStyleSourceUrl() {
297 return getTestDirectory() + "/style.mapcss";
298 }
299
300 public DataSet getOsmDataSet() throws FileNotFoundException, IllegalDataException {
301 if (ds == null) {
302 ds = OsmReader.parseDataSet(new FileInputStream(getTestDirectory() + "/data.osm"), null);
303 }
304 return ds;
305 }
306
307 public Bounds getTestArea() throws FileNotFoundException, IllegalDataException {
308 if (testArea == null) {
309 testArea = getOsmDataSet().getDataSourceBounds().get(0);
310 }
311 return testArea;
312 }
313
314 @Override
315 public String toString() {
316 return "TestConfig [testDirectory=" + testDirectory + ", testArea=" + testArea + ']';
317 }
318 }
319}
Note: See TracBrowser for help on using the repository browser.