Ticket #21432: 21432.patch
File 21432.patch, 27.2 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
diff --git a/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java b/src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java index 2e637a12d1..41fb48b2a9 100644
a b 2 2 package org.openstreetmap.josm.data.cache; 3 3 4 4 import java.awt.image.BufferedImage; 5 import java.awt.image.RenderedImage; 5 6 import java.io.ByteArrayInputStream; 6 7 import java.io.ByteArrayOutputStream; 7 8 import java.io.IOException; … … public class BufferedImageCacheEntry extends CacheEntry { 37 38 * @return a cache entry for the PNG encoded image 38 39 * @throws UncheckedIOException if an I/O error occurs 39 40 */ 40 public static BufferedImageCacheEntry pngEncoded( BufferedImage img) {41 public static BufferedImageCacheEntry pngEncoded(RenderedImage img) { 41 42 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 42 43 ImageIO.write(img, "png", output); 43 44 return new BufferedImageCacheEntry(output.toByteArray()); -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java index 418fcfd520..73d7e42daa 100644
a b import java.awt.image.BufferedImage; 21 21 import java.io.IOException; 22 22 import java.util.Objects; 23 23 import java.util.concurrent.Future; 24 import java.util.concurrent.atomic.AtomicInteger; 24 25 25 26 import javax.swing.JComponent; 26 27 import javax.swing.SwingUtilities; … … import org.openstreetmap.josm.gui.MainApplication; 34 35 import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable; 35 36 import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.IImageViewer; 36 37 import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.ImageProjectionRegistry; 38 import org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling.IImageTiling; 37 39 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 38 40 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener; 39 41 import org.openstreetmap.josm.gui.util.GuiHelper; … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 87 89 88 90 private final ImgDisplayMouseListener imgMouseListener = new ImgDisplayMouseListener(); 89 91 92 private final AtomicInteger zoom = new AtomicInteger(12); 93 90 94 private String emptyText; 91 95 private String osdText; 92 96 … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 364 368 if (rotation > 0) { 365 369 currentVisibleRect.width = (int) (currentVisibleRect.width * ZOOM_STEP.get()); 366 370 currentVisibleRect.height = (int) (currentVisibleRect.height * ZOOM_STEP.get()); 371 ImageDisplay.this.zoom.decrementAndGet(); 367 372 } else { 368 373 currentVisibleRect.width = (int) (currentVisibleRect.width / ZOOM_STEP.get()); 369 374 currentVisibleRect.height = (int) (currentVisibleRect.height / ZOOM_STEP.get()); 375 ImageDisplay.this.zoom.incrementAndGet(); 370 376 } 377 // FIXME Remove logging 378 Logging.error("Current zoom: {0}", ImageDisplay.this.zoom.get()); 371 379 372 380 // Check that the zoom doesn't exceed MAX_ZOOM:1 373 381 ensureMaxZoom(currentVisibleRect); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 719 727 Rectangle r = new Rectangle(currentVisibleRect); 720 728 Rectangle target = calculateDrawImageRectangle(currentVisibleRect, size); 721 729 722 currentImageViewer.paintImage(g, currentImage, target, r); 730 if (currentEntry instanceof IImageTiling && ((IImageTiling) currentEntry).isTilingEnabled()) { 731 currentImageViewer.paintTiledImage(g, (IImageTiling) currentEntry, target, r, zoom.get()); 732 } else { 733 currentImageViewer.paintImage(g, currentImage, target, r); 734 } 723 735 paintSelectedRect(g, target, currentVisibleRect, size); 724 736 if (currentErrorLoading && currentEntry != null) { 725 737 String loadingStr = tr("Error on file {0}", currentEntry.getDisplayName()); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 1017 1029 } else { 1018 1030 rectangle.height = wFact / getSize().width; 1019 1031 } 1032 1033 final IImageEntry<?> currentEntry; 1034 synchronized (this) { 1035 currentEntry = this.entry; 1036 } 1037 if (currentEntry instanceof IImageTiling) { 1038 IImageTiling imageTiling = (IImageTiling) currentEntry; 1039 if (this.zoom.get() > imageTiling.getMaxZoom()) { 1040 this.zoom.set(imageTiling.getMaxZoom()); 1041 } else if (this.zoom.get() < imageTiling.getMinZoom()) { 1042 this.zoom.set(imageTiling.getMinZoom()); 1043 } 1044 } 1020 1045 } 1021 1046 1022 1047 /** -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java index d5a94886b1..888611fbd2 100644
a b import static org.openstreetmap.josm.tools.I18n.tr; 6 6 import java.awt.Dimension; 7 7 import java.awt.Graphics2D; 8 8 import java.awt.Image; 9 import java.awt.Rectangle; 9 10 import java.awt.geom.AffineTransform; 10 11 import java.awt.image.BufferedImage; 11 12 import java.io.File; … … import java.net.MalformedURLException; 15 16 import java.net.URL; 16 17 import java.util.Collections; 17 18 import java.util.Objects; 19 18 20 import javax.imageio.IIOParam; 19 21 import javax.imageio.ImageReadParam; 20 22 import javax.imageio.ImageReader; … … import javax.imageio.ImageReader; 22 24 import org.openstreetmap.josm.data.ImageData; 23 25 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 24 26 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 27 import org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling.IImageTiling; 25 28 import org.openstreetmap.josm.tools.ExifReader; 26 29 import org.openstreetmap.josm.tools.ImageProvider; 27 30 import org.openstreetmap.josm.tools.Logging; … … import org.openstreetmap.josm.tools.Logging; 30 33 * Stores info about each image, with an optional thumbnail 31 34 * @since 2662 32 35 */ 33 public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry> {36 public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry>, IImageTiling { 34 37 35 38 private Image thumbnail; 36 39 private ImageData dataSet; … … public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry> 212 215 return applyExifRotation(image); 213 216 } 214 217 218 @Override 219 public Image getTileImage(int zoom, int tileSize, int column, int row) { 220 if (column < 0 || row < 0 || zoom > this.getMaxZoom() || this.getWidth(zoom) < column * tileSize || this.getHeight(zoom) < row * tileSize) { 221 return null; 222 } 223 final URL imageUrl; 224 final BufferedImage image; 225 try { 226 imageUrl = getImageUrl(); 227 Logging.info(tr("Loading {0} at {1}/{2}/{3} with size {4}", imageUrl, zoom, column, row, tileSize)); 228 image = ImageProvider.read(imageUrl, true, false, 229 r -> this.withSubsampling(r, tileSize, zoom, column, row)); 230 } catch (IOException e) { 231 Logging.error(e); 232 return null; 233 } 234 235 if (image == null) { 236 Logging.warn("Unable to load {0}", imageUrl); 237 } 238 // applyExifRotation not used here since it will not work with tiled images 239 // Instead, we will have to rotate the column/row, and then apply rotation here. 240 return image; 241 } 242 215 243 protected URL getImageUrl() throws MalformedURLException { 216 244 return getFile().toURI().toURL(); 217 245 } 218 246 247 private ImageReadParam withSubsampling(ImageReader reader, int tileSize, int zoom, int column, int row) { 248 Rectangle tile = IImageTiling.super.getTileDimension(zoom, column, row, tileSize); 249 ImageReadParam param = reader.getDefaultReadParam(); 250 param.setSourceRegion(tile); 251 int subsampling = (int) Math.floor(Math.max(Math.pow(IImageTiling.super.getScale(zoom), -1), 1)); 252 param.setSourceSubsampling(subsampling, subsampling, 0, 0); 253 return param; 254 } 255 219 256 private ImageReadParam withSubsampling(ImageReader reader, Dimension target) { 220 257 try { 221 258 ImageReadParam param = reader.getDefaultReadParam(); -
src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/IImageViewer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/IImageViewer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/IImageViewer.java index 3c1d41e534..583bd53f97 100644
a b import java.util.Set; 12 12 13 13 import org.openstreetmap.josm.data.imagery.street_level.Projections; 14 14 import org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay; 15 import org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling.IImageTiling; 15 16 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 16 17 17 18 /** … … public interface IImageViewer extends ComponentListener { 34 35 */ 35 36 void paintImage(Graphics g, BufferedImage image, Rectangle target, Rectangle visibleRect); 36 37 38 39 /** 40 * Paint the image tile 41 * @param g The graphics to paint on 42 * @param entry The image entry (specifically, with the tile size) 43 * @param tile The tile to paint (x, y, z) 44 * @param image The image to paint 45 */ 46 default void paintImageTile(Graphics g, Rectangle target, Rectangle visibleRect, IImageTiling entry, IImageTiling.ImageTile tile, Image image) { 47 final Rectangle toUse = visibleRect; 48 g.drawImage(image, -toUse.x + entry.getTileSize() * tile.getXIndex(), -toUse.y + entry.getTileSize() * tile.getYIndex(), null); 49 } 50 51 /** 52 * Paint the image 53 * @param g The graphics to paint on 54 * @param imageEntry The image to paint 55 * @param target The target area 56 * @param visibleRect The visible rectangle 57 * @param zoom The zoom level 58 */ 59 default void paintTiledImage(Graphics g, IImageTiling imageEntry, Rectangle target, Rectangle visibleRect, int zoom) { 60 imageEntry.getTiles(zoom, visibleRect).forEach(pair -> this.paintImageTile(g, target, visibleRect, imageEntry, pair.a, pair.b)); 61 } 62 37 63 /** 38 64 * Get the default visible rectangle for the projection 39 65 * @param component The component the image will be displayed in -
new file src/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTiling.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTiling.java b/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTiling.java new file mode 100644 index 0000000000..7413517fbc
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling; 3 4 import java.awt.Image; 5 import java.awt.Rectangle; 6 import java.awt.image.RenderedImage; 7 import java.io.IOException; 8 import java.text.MessageFormat; 9 import java.util.function.IntUnaryOperator; 10 import java.util.stream.IntStream; 11 import java.util.stream.Stream; 12 13 import org.apache.commons.jcs3.access.CacheAccess; 14 import org.openstreetmap.gui.jmapviewer.TileXY; 15 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 16 import org.openstreetmap.josm.data.cache.JCSCacheManager; 17 import org.openstreetmap.josm.spi.preferences.Config; 18 import org.openstreetmap.josm.tools.Logging; 19 import org.openstreetmap.josm.tools.Pair; 20 21 /** 22 * An interface for tiled images. Primarily used to reduce memory usage in large images. 23 * @author Taylor Smock 24 * @since xxx 25 */ 26 public interface IImageTiling { 27 /** 28 * Just a class to hold tile information 29 */ 30 class ImageTile extends TileXY { 31 final int zoom; 32 /** 33 * Returns an instance of an image tile. 34 * @param x number of the tile 35 * @param y number of the tile 36 * @param z The zoom level 37 */ 38 public ImageTile(int x, int y, int z) { 39 super(x, y); 40 this.zoom = z; 41 } 42 } 43 /** 44 * The default tile size for the image tiles -- each tile takes 1024 px * 1024 px * 4 bytes = 4 MiB max 45 * A 4k image (4160x3120) has (Math.ceil(4160/1024) * Math.ceil(3120/1024) = 20 tiles). Some tiles are almost empty. 46 * This gives a reasonable number of tiles for most image sizes. 47 */ 48 int DEFAULT_TILE_SIZE = 1024; 49 50 /** A good default minimum zoom (the image size is {@link #DEFAULT_TILE_SIZE} max, at 1024 it is 5) */ 51 int DEFAULT_MIN_ZOOM = (int) Math.round(Math.log(Math.sqrt(DEFAULT_TILE_SIZE))/Math.log(2)); 52 53 /** A cache for images */ 54 CacheAccess<String, BufferedImageCacheEntry> IMAGE_CACHE = JCSCacheManager.getCache("iimagetiling", 100, 1_000, Config.getDirs().getCacheDirectory(true).getAbsolutePath()); 55 56 /** 57 * Get the size of the image at a specified zoom level 58 * @param zoom The zoom level. Zoom 0 == 1 px for the image. Zoom 1 == 4 px for the image. 59 * @return The number of pixels (max, for a square image) 60 */ 61 static long getSizeAtZoom(final int zoom) { 62 final long dimension = 1L << zoom; 63 return dimension * dimension; 64 } 65 66 /** 67 * Get the default tile size. 68 * @return The tile size to use 69 */ 70 default int getDefaultTileSize() { 71 return DEFAULT_TILE_SIZE; 72 } 73 74 /** 75 * Get the tile size. 76 * @return The tile size to use 77 */ 78 default int getTileSize() { 79 return this.getDefaultTileSize(); 80 } 81 82 /** 83 * Get the maximum zoom that the image supports 84 * Feel free to override and cache the result for performance reasons. 85 * 86 * @return The maximum zoom of the image 87 */ 88 default int getMaxZoom() { 89 final int maxSize = Math.max(this.getWidth(), this.getHeight()); 90 return (int) Math.round(Math.ceil(Math.log(maxSize) / Math.log(2))); 91 } 92 93 /** 94 * Get the minimum zoom that the image supports or makes sense 95 * @return The minimum zoom that makes sense 96 */ 97 default int getMinZoom() { 98 final IntUnaryOperator minZoom = input -> Math.toIntExact(Math.round(Math.floor(this.getMaxZoom() + Math.log((double) this.getTileSize() / input) / Math.log(2)))); 99 return Math.min(minZoom.applyAsInt(this.getWidth()), minZoom.applyAsInt(this.getHeight())); 100 } 101 102 /** 103 * Get the current scale of the image 104 * @param zoom The zoom level 105 * @return The scaling of the image at the specified level 106 */ 107 default double getScale(final int zoom) { 108 return Math.pow(2, (double) zoom - this.getMaxZoom()); 109 } 110 111 /** 112 * Get the width of the image 113 * @return The width of the image 114 */ 115 int getWidth(); 116 117 /** 118 * Get the width of the image at a specified scale 119 * @param zoom The zoom to use 120 * @return The width at the specified scale 121 */ 122 default int getWidth(final int zoom) { 123 return Math.toIntExact(Math.round(this.getScale(zoom) * this.getWidth())); 124 } 125 126 /** 127 * Get the height of the image 128 * @return The height of the image 129 */ 130 int getHeight(); 131 132 /** 133 * Get the height of the image at a specified scale 134 * @param zoom The zoom to use 135 * @return The height at the specified scale 136 */ 137 default int getHeight(final int zoom) { 138 return Math.toIntExact(Math.round(this.getScale(zoom) * this.getHeight())); 139 } 140 141 /** 142 * Get the number of rows at a specified zoom level 143 * @param zoom The zoom level 144 * @return The number of rows 145 */ 146 default int getRows(final int zoom) { 147 return this.getRows(zoom, this.getTileSize()); 148 } 149 150 /** 151 * Get the number of rows at a specified zoom level 152 * @param zoom The zoom level 153 * @param tileSize The tile size 154 * @return The number of rows 155 */ 156 default int getRows(final int zoom, final int tileSize) { 157 final int height = this.getHeight(zoom); 158 return Math.toIntExact(Math.round(Math.ceil(height / (double) tileSize))); 159 } 160 161 /** 162 * Get the number of columns at a specified zoom level 163 * @param zoom The zoom level 164 * @return The number of columns 165 */ 166 default int getColumns(final int zoom) { 167 return this.getColumns(zoom, this.getTileSize()); 168 } 169 170 /** 171 * Get the number of columns at a specified zoom level 172 * @param zoom The zoom level 173 * @param tileSize The tile size 174 * @return The number of columns 175 */ 176 default int getColumns(final int zoom, final int tileSize) { 177 final int width = this.getWidth(zoom); 178 return Math.toIntExact(Math.round(Math.ceil(width / (double) tileSize))); 179 } 180 181 /** 182 * Get the image to show for a specific tile location. This should be cached by the implementation in most cases. 183 * Top-left corner is 0,0 184 * @param zoom The zoom to use 185 * @param tileSize The tile size to use 186 * @param column The column to get (x) 187 * @param row The row to get (y) 188 * @return The image to display (not padded). May be {@code null}. 189 */ 190 Image getTileImage(int zoom, int tileSize, int column, int row); 191 192 /** 193 * Get the image to show for a specific tile location with the default tile size 194 * Top-left corner is 0,0 195 * @param zoom The zoom to use 196 * @param column The column to get (x) 197 * @param row The row to get (y) 198 * @return The image to display (not padded). May be {@code null}. 199 */ 200 default Image getTileImage(final int zoom, final int column, final int row) { 201 final String storage = MessageFormat.format("{0}: {1}/{2}/{3}", this, zoom, column, row); 202 BufferedImageCacheEntry image = IMAGE_CACHE.get(storage); 203 if (image == null) { 204 Image newImage = this.getTileImage(zoom, this.getTileSize(), column, row); 205 if (newImage instanceof RenderedImage) { 206 IMAGE_CACHE.put(storage, BufferedImageCacheEntry.pngEncoded((RenderedImage) newImage)); 207 } 208 return newImage; 209 } 210 try { 211 return image.getImage(); 212 } catch (IOException e) { 213 Logging.error(e); 214 } 215 return null; 216 } 217 218 /** 219 * Get the subsection of the image to show 220 * Top-left corner is 0,0 221 * @param zoom The zoom to use 222 * @param column The column to get (x) 223 * @param row The row to get (y) 224 * @return The subsection of the image to get 225 */ 226 default Rectangle getTileDimension(final int zoom, final int column, final int row) { 227 return this.getTileDimension(zoom, column, row, this.getTileSize()); 228 } 229 230 /** 231 * Get the subsection of the image to show 232 * Top-left corner is 0,0 233 * @param zoom The zoom to use 234 * @param column The column to get (x) 235 * @param row The row to get (y) 236 * @param tileSize the tile size to use 237 * @return The subsection of the image to get 238 */ 239 default Rectangle getTileDimension(final int zoom, final int column, final int row, final int tileSize) { 240 final double scale = this.getScale(zoom); // e.g., 1, 1/2, 1/4, etc. 241 final int x = Math.toIntExact(Math.round(Math.floor(column * tileSize / scale))); 242 final int y = Math.toIntExact(Math.round(Math.floor(row * tileSize / scale))); 243 return new Rectangle(x, y, (int) (tileSize / scale), (int) (tileSize / scale)); 244 } 245 246 /** 247 * Get the tiles for a zoom level given a visible rectangle 248 * @param zoom The zoom to get 249 * @param visibleRect The rectangle to get 250 * @return A stream of tiles to images 251 */ 252 default Stream<Pair<ImageTile, Image>> getTiles(int zoom, Rectangle visibleRect) { 253 final double scale = this.getScale(zoom); 254 final int startX = Math.toIntExact(Math.round(Math.floor(visibleRect.getMinX() * scale / this.getTileSize()))); 255 final int startY = Math.toIntExact(Math.round(Math.floor(visibleRect.getMinY() * scale / this.getTileSize()))); 256 final int endX = Math.toIntExact(Math.round(Math.ceil(visibleRect.getMaxX() * scale / this.getTileSize()))); 257 final int endY = Math.toIntExact(Math.round(Math.ceil(visibleRect.getMaxY() * scale / this.getTileSize()))); 258 Logging.error("x [{0} - {1}], y[{2} - {3}]", startX, endX, startY, endY); 259 return IntStream.rangeClosed(startX, endX).mapToObj(x -> IntStream.rangeClosed(startY, endY).mapToObj(y -> new ImageTile(x, y, zoom))) 260 .flatMap(stream -> stream).map(tile -> new Pair<>(tile, this.getTileImage(tile.zoom, tile.getXIndex(), tile.getYIndex()))); 261 } 262 263 /** 264 * Check if tiling is enabled for this object. 265 * 266 * @return {@code true} if tiling should be u sed 267 */ 268 default boolean isTilingEnabled() { 269 return true; 270 } 271 } -
new file test/unit/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTilingTest.java
diff --git a/test/unit/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTilingTest.java b/test/unit/org/openstreetmap/josm/gui/layer/geoimage/viewers/tiling/IImageTilingTest.java new file mode 100644 index 0000000000..0ad1096efc
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling; 3 4 import static org.junit.jupiter.api.Assertions.assertAll; 5 import static org.junit.jupiter.api.Assertions.assertEquals; 6 import static org.junit.jupiter.api.Assertions.assertNotEquals; 7 import static org.junit.jupiter.api.Assertions.assertNotNull; 8 import static org.junit.jupiter.api.Assertions.assertTrue; 9 10 import java.awt.Image; 11 import java.awt.image.BufferedImage; 12 import java.util.concurrent.atomic.AtomicInteger; 13 import java.util.stream.Stream; 14 15 import org.junit.jupiter.params.ParameterizedTest; 16 import org.junit.jupiter.params.provider.Arguments; 17 import org.junit.jupiter.params.provider.MethodSource; 18 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 19 20 /** 21 * Test class for {@link IImageTiling} 22 * @author Taylor Smock 23 */ 24 @BasicPreferences 25 class IImageTilingTest { 26 static Stream<Arguments> testSizeAtZoom() { 27 return Stream.of(Arguments.of(0, 1L), Arguments.of(1, 4L)); 28 } 29 30 @ParameterizedTest 31 @MethodSource 32 void testSizeAtZoom(int zoom, long expected) { 33 assertEquals(expected, IImageTiling.getSizeAtZoom(zoom)); 34 } 35 36 static Stream<Arguments> getImageTilingSamples() { 37 return Stream.of( 38 Arguments.of(new ImageTiling(new BufferedImage(5000, 2500, BufferedImage.TYPE_INT_ARGB)), 13) 39 ); 40 } 41 42 @ParameterizedTest 43 @MethodSource("getImageTilingSamples") 44 void testGetTileSizes(final ImageTiling imageTiling) { 45 // The fake class uses default methods 46 assertEquals(imageTiling.getTileSize(), imageTiling.getDefaultTileSize()); 47 } 48 49 @ParameterizedTest 50 @MethodSource("getImageTilingSamples") 51 void testGetMaxZoom(final ImageTiling imageTiling, final int maxZoom) { 52 assertEquals(maxZoom, imageTiling.getMaxZoom()); 53 } 54 55 @ParameterizedTest 56 @MethodSource("getImageTilingSamples") 57 void testGetScale(final ImageTiling imageTiling, final int maxZoom) { 58 assertEquals(1, imageTiling.getScale(maxZoom)); 59 assertEquals(0.5, imageTiling.getScale(maxZoom - 1)); 60 assertEquals(0.25, imageTiling.getScale(maxZoom - 2)); 61 } 62 63 @ParameterizedTest 64 @MethodSource("getImageTilingSamples") 65 void testGetWidth(final IImageTiling imageTiling, final int maxZoom) { 66 assertEquals(imageTiling.getWidth(), imageTiling.getWidth(maxZoom)); 67 assertEquals(imageTiling.getWidth() / 2, imageTiling.getWidth(maxZoom - 1)); 68 assertEquals(imageTiling.getWidth() / 4, imageTiling.getWidth(maxZoom - 2)); 69 } 70 71 @ParameterizedTest 72 @MethodSource("getImageTilingSamples") 73 void testGetHeight(final IImageTiling imageTiling, final int maxZoom) { 74 assertEquals(imageTiling.getHeight(), imageTiling.getHeight(maxZoom)); 75 assertEquals(imageTiling.getHeight() / 2, imageTiling.getHeight(maxZoom - 1)); 76 assertEquals(imageTiling.getHeight() / 4, imageTiling.getHeight(maxZoom - 2)); 77 } 78 79 @ParameterizedTest 80 @MethodSource("getImageTilingSamples") 81 void testGetRows(final IImageTiling imageTiling, final int maxZoom) { 82 assertEquals(3, imageTiling.getRows(maxZoom)); 83 assertEquals(2, imageTiling.getRows(maxZoom - 1)); 84 } 85 86 @ParameterizedTest 87 @MethodSource("getImageTilingSamples") 88 void testGetColumns(final IImageTiling imageTiling, final int maxZoom) { 89 assertEquals(5, imageTiling.getColumns(maxZoom)); 90 assertEquals(3, imageTiling.getColumns(maxZoom - 1)); 91 } 92 93 @ParameterizedTest 94 @MethodSource("getImageTilingSamples") 95 void testGetTileImage(final IImageTiling imageTiling, final int maxZoom) { 96 assertNotNull(imageTiling.getTileImage(maxZoom, 0, 0)); 97 final Image cornerImage = imageTiling.getTileImage(maxZoom, imageTiling.getColumns(maxZoom) - 1, imageTiling.getRows(maxZoom) - 1); 98 assertAll(() -> assertNotEquals(-1, cornerImage.getWidth(null)), 99 () -> assertNotEquals(-1, cornerImage.getHeight(null)), 100 () -> assertTrue(imageTiling.getTileSize() > cornerImage.getWidth(null)), 101 () -> assertTrue(imageTiling.getTileSize() > cornerImage.getHeight(null))); 102 } 103 104 @ParameterizedTest 105 @MethodSource("getImageTilingSamples") 106 void testGetTileDimension(final IImageTiling imageTiling) { 107 imageTiling.getTileDimension(0, 0, 0); 108 } 109 110 private static class ImageTiling implements IImageTiling { 111 private final int width; 112 private final int height; 113 private final Image image; 114 final AtomicInteger counter = new AtomicInteger(0); 115 ImageTiling(final Image image) { 116 this.image = image; 117 this.width = image.getWidth(null); 118 this.height = image.getHeight(null); 119 } 120 121 @Override 122 public int getWidth() { 123 return this.width; 124 } 125 126 @Override 127 public int getHeight() { 128 return this.height; 129 } 130 131 @Override 132 public Image getTileImage(int zoom, int tileSize, int column, int row) { 133 this.counter.incrementAndGet(); 134 if (image instanceof BufferedImage) { 135 final BufferedImage bufferedImage = (BufferedImage) image; 136 return bufferedImage.getSubimage(column * tileSize, row * tileSize, 137 Math.min(tileSize, bufferedImage.getWidth() - column * tileSize - 1), 138 Math.min(tileSize, bufferedImage.getHeight() - row * tileSize - 1)); 139 } 140 throw new UnsupportedOperationException("The test ImageTiling class only supports BufferedImages"); 141 } 142 } 143 }