source: josm/trunk/test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java@ 11697

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

MapCSSRendererTest: Require fixed font for text drawing test.

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