| | 1 | // License: GPL. For details, see LICENSE file. |
| | 2 | package org.openstreetmap.josm.testutils; |
| | 3 | |
| | 4 | import static org.junit.Assert.fail; |
| | 5 | |
| | 6 | import java.awt.image.BufferedImage; |
| | 7 | |
| | 8 | import java.util.Arrays; |
| | 9 | import java.util.HashMap; |
| | 10 | import java.util.Map; |
| | 11 | import java.util.Optional; |
| | 12 | import java.util.function.IntFunction; |
| | 13 | import java.util.regex.Matcher; |
| | 14 | import java.util.regex.Pattern; |
| | 15 | import java.util.stream.Collectors; |
| | 16 | |
| | 17 | /** |
| | 18 | * Utilities to aid in making assertions about images using regular expressions. |
| | 19 | */ |
| | 20 | public final class ImagePatternMatching { |
| | 21 | private ImagePatternMatching() {} |
| | 22 | |
| | 23 | private static final Map<String, Pattern> patternCache = new HashMap<String, Pattern>(); |
| | 24 | |
| | 25 | private static Matcher imageStripPatternMatchInner( |
| | 26 | final BufferedImage image, |
| | 27 | final int columnOrRowIndex, |
| | 28 | IntFunction<String> paletteMapFn, |
| | 29 | final Map<Integer, String> paletteMap, |
| | 30 | Pattern pattern, |
| | 31 | final String patternString, |
| | 32 | final boolean isColumn, |
| | 33 | final boolean assertMatch |
| | 34 | ) { |
| | 35 | paletteMapFn = Optional.ofNullable(paletteMapFn) |
| | 36 | // using "#" as the default "unmapped" character as it can be used in regexes without escaping |
| | 37 | .orElse(i -> paletteMap.getOrDefault(i, "#")); |
| | 38 | pattern = Optional.ofNullable(pattern) |
| | 39 | .orElseGet(() -> patternCache.computeIfAbsent(patternString, k -> Pattern.compile(k))); |
| | 40 | |
| | 41 | int[] columnOrRow = isColumn |
| | 42 | ? image.getRGB(columnOrRowIndex, 0, 1, image.getHeight(), null, 0, 1) |
| | 43 | : image.getRGB(0, columnOrRowIndex, image.getWidth(), 1, null, 0, image.getWidth()); |
| | 44 | |
| | 45 | String stringRepr = Arrays.stream(columnOrRow).mapToObj(paletteMapFn).collect(Collectors.joining()); |
| | 46 | Matcher result = pattern.matcher(stringRepr); |
| | 47 | |
| | 48 | if (assertMatch && !result.matches()) { |
| | 49 | System.err.println(String.format("Full strip failing to match pattern %s: %s", pattern, stringRepr)); |
| | 50 | fail(String.format( |
| | 51 | "%s %d failed to match pattern %s", |
| | 52 | isColumn ? "Column" : "Row", |
| | 53 | columnOrRowIndex, |
| | 54 | pattern |
| | 55 | )); |
| | 56 | } |
| | 57 | |
| | 58 | return result; |
| | 59 | } |
| | 60 | |
| | 61 | /** |
| | 62 | * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMap} |
| | 63 | * against the regular expression described by {@code patternString}. |
| | 64 | * |
| | 65 | * @param image image to take column from |
| | 66 | * @param colNumber image column number for comparison |
| | 67 | * @param paletteMap {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 68 | * is advised to only map colors to single characters. Colors with no corresponding entry in |
| | 69 | * the map are mapped to {@code #}. |
| | 70 | * @param patternString string representation of regular expression to match against. These are simply used to |
| | 71 | * construct a {@link Pattern} which is cached in case of re-use. |
| | 72 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 73 | * @return {@link Matcher} produced by matching attempt |
| | 74 | */ |
| | 75 | public static Matcher columnMatch( |
| | 76 | final BufferedImage image, |
| | 77 | final int colNumber, |
| | 78 | final Map<Integer, String> paletteMap, |
| | 79 | final String patternString, |
| | 80 | final boolean assertMatch |
| | 81 | ) { |
| | 82 | return imageStripPatternMatchInner( |
| | 83 | image, |
| | 84 | colNumber, |
| | 85 | null, |
| | 86 | paletteMap, |
| | 87 | null, |
| | 88 | patternString, |
| | 89 | true, |
| | 90 | true |
| | 91 | ); |
| | 92 | } |
| | 93 | |
| | 94 | /** |
| | 95 | * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMapFn} |
| | 96 | * against the regular expression described by {@code patternString}. |
| | 97 | * |
| | 98 | * @param image image to take column from |
| | 99 | * @param colNumber image column number for comparison |
| | 100 | * @param paletteMapFn function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 101 | * is advised to only map colors to single characters. |
| | 102 | * @param patternString string representation of regular expression to match against. These are simply used to |
| | 103 | * construct a {@link Pattern} which is cached in case of re-use. |
| | 104 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 105 | * @return {@link Matcher} produced by matching attempt |
| | 106 | */ |
| | 107 | public static Matcher columnMatch( |
| | 108 | final BufferedImage image, |
| | 109 | final int colNumber, |
| | 110 | final IntFunction<String> paletteMapFn, |
| | 111 | final String patternString, |
| | 112 | final boolean assertMatch |
| | 113 | ) { |
| | 114 | return imageStripPatternMatchInner( |
| | 115 | image, |
| | 116 | colNumber, |
| | 117 | paletteMapFn, |
| | 118 | null, |
| | 119 | null, |
| | 120 | patternString, |
| | 121 | true, |
| | 122 | true |
| | 123 | ); |
| | 124 | } |
| | 125 | |
| | 126 | /** |
| | 127 | * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMap} |
| | 128 | * against the regular expression {@code pattern}. |
| | 129 | * |
| | 130 | * @param image image to take column from |
| | 131 | * @param colNumber image column number for comparison |
| | 132 | * @param paletteMap {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 133 | * is advised to only map colors to single characters. Colors with no corresponding entry in |
| | 134 | * the map are mapped to {@code #}. |
| | 135 | * @param pattern regular expression to match against |
| | 136 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 137 | * @return {@link Matcher} produced by matching attempt |
| | 138 | */ |
| | 139 | public static Matcher columnMatch( |
| | 140 | final BufferedImage image, |
| | 141 | final int colNumber, |
| | 142 | final Map<Integer, String> paletteMap, |
| | 143 | final Pattern pattern, |
| | 144 | final boolean assertMatch |
| | 145 | ) { |
| | 146 | return imageStripPatternMatchInner( |
| | 147 | image, |
| | 148 | colNumber, |
| | 149 | null, |
| | 150 | paletteMap, |
| | 151 | pattern, |
| | 152 | null, |
| | 153 | true, |
| | 154 | true |
| | 155 | ); |
| | 156 | } |
| | 157 | |
| | 158 | /** |
| | 159 | * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMapFn} |
| | 160 | * against the regular expression {@code pattern}. |
| | 161 | * |
| | 162 | * @param image image to take column from |
| | 163 | * @param colNumber image column number for comparison |
| | 164 | * @param paletteMapFn function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 165 | * is advised to only map colors to single characters. |
| | 166 | * @param pattern regular expression to match against |
| | 167 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 168 | * @return {@link Matcher} produced by matching attempt |
| | 169 | */ |
| | 170 | public static Matcher columnMatch( |
| | 171 | final BufferedImage image, |
| | 172 | final int colNumber, |
| | 173 | final IntFunction<String> paletteMapFn, |
| | 174 | final Pattern pattern, |
| | 175 | final boolean assertMatch |
| | 176 | ) { |
| | 177 | return imageStripPatternMatchInner( |
| | 178 | image, |
| | 179 | colNumber, |
| | 180 | paletteMapFn, |
| | 181 | null, |
| | 182 | pattern, |
| | 183 | null, |
| | 184 | true, |
| | 185 | true |
| | 186 | ); |
| | 187 | } |
| | 188 | |
| | 189 | /** |
| | 190 | * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMap} |
| | 191 | * against the regular expression described by {@code patternString}. |
| | 192 | * |
| | 193 | * @param image image to take row from |
| | 194 | * @param rowNumber image row number for comparison |
| | 195 | * @param paletteMap {@link Map} of {@code Integer}s (denoting the or in ARGB format) to {@link String}s. It |
| | 196 | * is advised to only map colors to single characters. Colors with no corresponding entry in |
| | 197 | * the map are mapped to {@code #}. |
| | 198 | * @param patternString string representation of regular expression to match against. These are simply used to |
| | 199 | * construct a {@link Pattern} which is cached in case of re-use. |
| | 200 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 201 | * @return {@link Matcher} produced by matching attempt |
| | 202 | */ |
| | 203 | public static Matcher rowMatch( |
| | 204 | final BufferedImage image, |
| | 205 | final int rowNumber, |
| | 206 | final Map<Integer, String> paletteMap, |
| | 207 | final String patternString, |
| | 208 | final boolean assertMatch |
| | 209 | ) { |
| | 210 | return imageStripPatternMatchInner( |
| | 211 | image, |
| | 212 | rowNumber, |
| | 213 | null, |
| | 214 | paletteMap, |
| | 215 | null, |
| | 216 | patternString, |
| | 217 | false, |
| | 218 | true |
| | 219 | ); |
| | 220 | } |
| | 221 | |
| | 222 | /** |
| | 223 | * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMapFn} |
| | 224 | * against the regular expression described by {@code patternString}. |
| | 225 | * |
| | 226 | * @param image image to take row from |
| | 227 | * @param rowNumber image row number for comparison |
| | 228 | * @param paletteMapFn function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 229 | * is advised to only map colors to single characters. |
| | 230 | * @param patternString string representation of regular expression to match against. These are simply used to |
| | 231 | * construct a {@link Pattern} which is cached in case of re-use. |
| | 232 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 233 | * @return {@link Matcher} produced by matching attempt |
| | 234 | */ |
| | 235 | public static Matcher rowMatch( |
| | 236 | final BufferedImage image, |
| | 237 | final int rowNumber, |
| | 238 | final IntFunction<String> paletteMapFn, |
| | 239 | final String patternString, |
| | 240 | final boolean assertMatch |
| | 241 | ) { |
| | 242 | return imageStripPatternMatchInner( |
| | 243 | image, |
| | 244 | rowNumber, |
| | 245 | paletteMapFn, |
| | 246 | null, |
| | 247 | null, |
| | 248 | patternString, |
| | 249 | false, |
| | 250 | true |
| | 251 | ); |
| | 252 | } |
| | 253 | |
| | 254 | /** |
| | 255 | * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMap} |
| | 256 | * against the regular expression {@code pattern}. |
| | 257 | * |
| | 258 | * @param image image to take row from |
| | 259 | * @param rowNumber image row number for comparison |
| | 260 | * @param paletteMap {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 261 | * is advised to only map colors to single characters. Colors with no corresponding entry in |
| | 262 | * the map are mapped to {@code #}. |
| | 263 | * @param pattern regular expression to match against |
| | 264 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 265 | * @return {@link Matcher} produced by matching attempt |
| | 266 | */ |
| | 267 | public static Matcher rowMatch( |
| | 268 | final BufferedImage image, |
| | 269 | final int rowNumber, |
| | 270 | final Map<Integer, String> paletteMap, |
| | 271 | final Pattern pattern, |
| | 272 | final boolean assertMatch |
| | 273 | ) { |
| | 274 | return imageStripPatternMatchInner( |
| | 275 | image, |
| | 276 | rowNumber, |
| | 277 | null, |
| | 278 | paletteMap, |
| | 279 | pattern, |
| | 280 | null, |
| | 281 | false, |
| | 282 | true |
| | 283 | ); |
| | 284 | } |
| | 285 | |
| | 286 | /** |
| | 287 | * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMapFn} |
| | 288 | * against the regular expression {@code pattern}. |
| | 289 | * |
| | 290 | * @param image image to take row from |
| | 291 | * @param rowNumber image row number for comparison |
| | 292 | * @param paletteMapFn function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It |
| | 293 | * is advised to only map colors to single characters. |
| | 294 | * @param pattern regular expression to match against |
| | 295 | * @param assertMatch whether to raise an (informative) {@link AssertionFailedError} if no match is found. |
| | 296 | * @return {@link Matcher} produced by matching attempt |
| | 297 | */ |
| | 298 | public static Matcher rowMatch( |
| | 299 | final BufferedImage image, |
| | 300 | final int rowNumber, |
| | 301 | final IntFunction<String> paletteMapFn, |
| | 302 | final Pattern pattern, |
| | 303 | final boolean assertMatch |
| | 304 | ) { |
| | 305 | return imageStripPatternMatchInner( |
| | 306 | image, |
| | 307 | rowNumber, |
| | 308 | paletteMapFn, |
| | 309 | null, |
| | 310 | pattern, |
| | 311 | null, |
| | 312 | false, |
| | 313 | true |
| | 314 | ); |
| | 315 | } |
| | 316 | } |