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

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

MapCSSRendererTest: use proper bounds and avoid 20 px border added by NavigatableComponent

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