Ticket #21432: 21432.2.patch
File 21432.2.patch, 49.4 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..54e8248a52 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 213 217 } 214 218 215 219 public void checkRectPos() { 220 this.checkRectPos(null, 0); 221 } 222 223 /** 224 * Ensure that the rectangle is within bounds 225 * @param imageEntry The current image entry -- if it is a tiling entry, different constraints are needed 226 * @param zoom The current zoom level (only used if tiling) 227 */ 228 public void checkRectPos(final IImageEntry<?> imageEntry, final int zoom) { 229 final int useWidth; 230 final int useHeight; 231 if (imageEntry instanceof IImageTiling) { 232 useHeight = ((IImageTiling) imageEntry).getHeight(zoom); 233 useWidth = ((IImageTiling) imageEntry).getWidth(zoom); 234 } else { 235 useWidth = init.width; 236 useHeight = init.height; 237 } 216 238 if (x < 0) { 217 239 x = 0; 218 240 } 219 241 if (y < 0) { 220 242 y = 0; 221 243 } 222 if (x + width > init.width) { 223 x = init.width - width; 244 if (width > useWidth) { 245 width = useWidth; 246 } 247 if (height > useHeight) { 248 height = useHeight; 224 249 } 225 if (y + height > init.height) { 226 y = init.height - height; 250 if (x + width > useWidth) { 251 x = useWidth - width; 252 } 253 if (y + height > useHeight) { 254 y = useHeight - height; 227 255 } 228 256 } 229 257 230 258 public void checkRectSize() { 231 if (width > init.width) { 232 width = init.width; 259 this.checkRectSize(null, 0); 260 } 261 262 /** 263 * Ensure that the rectangle is the appropriate size 264 * @param imageEntry The current image entry -- if it is a tiling entry, different constraints are needed 265 * @param zoom The current zoom level (only used if tiling) 266 */ 267 public void checkRectSize(final IImageEntry<?> imageEntry, final int zoom) { 268 final int useWidth; 269 final int useHeight; 270 if (imageEntry instanceof IImageTiling) { 271 useWidth = ((IImageTiling) imageEntry).getWidth(zoom); 272 useHeight = ((IImageTiling) imageEntry).getHeight(zoom); 273 } else { 274 useWidth = init.width; 275 useHeight = init.height; 276 } 277 if (width > useWidth) { 278 width = useWidth; 233 279 } 234 if (height > init.height) {235 height = init.height;280 if (height > useHeight) { 281 height = useHeight; 236 282 } 237 283 } 238 284 … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 289 335 } 290 336 } 291 337 292 int width = img.getWidth(); 293 int height = img.getHeight(); 294 entry.setWidth(width); 295 entry.setHeight(height); 338 // Only set width/height if the entry is not something that can be tiled 339 // Tiling *requires* knowledge of the actual width/height of the image. 340 if (!(entry instanceof IImageTiling)) { 341 int width = img.getWidth(); 342 int height = img.getHeight(); 343 entry.setWidth(width); 344 entry.setHeight(height); 345 } 296 346 297 347 synchronized (ImageDisplay.this) { 298 348 if (this.entry != ImageDisplay.this.entry) { … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 304 354 updateProcessedImage(); 305 355 // This will clear the loading info box 306 356 ImageDisplay.this.oldEntry = ImageDisplay.this.entry; 307 visibleRect = getIImageViewer(entry).getDefaultVisibleRectangle(ImageDisplay.this, image );357 visibleRect = getIImageViewer(entry).getDefaultVisibleRectangle(ImageDisplay.this, image, this.entry); 308 358 309 359 selectedRect = null; 310 360 errorLoading = false; … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 358 408 359 409 // Calculate the mouse cursor position in image coordinates to center the zoom. 360 410 if (refreshMousePointInImg) 361 mousePointInImg = comp2imgCoord(current VisibleRect, x, y, getSize());411 mousePointInImg = comp2imgCoord(currentEntry, currentVisibleRect, x, y, getSize(), zoom.get()); 362 412 363 413 // Apply the zoom to the visible rectangle in image coordinates 414 final int zoom; 364 415 if (rotation > 0) { 365 416 currentVisibleRect.width = (int) (currentVisibleRect.width * ZOOM_STEP.get()); 366 417 currentVisibleRect.height = (int) (currentVisibleRect.height * ZOOM_STEP.get()); 418 zoom = ImageDisplay.this.zoom.decrementAndGet(); 367 419 } else { 368 420 currentVisibleRect.width = (int) (currentVisibleRect.width / ZOOM_STEP.get()); 369 421 currentVisibleRect.height = (int) (currentVisibleRect.height / ZOOM_STEP.get()); 422 zoom = ImageDisplay.this.zoom.incrementAndGet(); 370 423 } 371 424 372 425 // Check that the zoom doesn't exceed MAX_ZOOM:1 373 426 ensureMaxZoom(currentVisibleRect); 374 427 375 428 // The size of the visible rectangle is limited by the image size or the viewer implementation. 429 // It can also be influenced by whether or not the current entry allows tiling. Tiling image implementations 430 // don't care what the size of the image buffer is. In fact, the image buffer can be the size of the window. 431 // So the image buffer really only defines the scale. 376 432 if (imageViewer != null) { 377 imageViewer.checkAndModifyVisibleRectSize(currentImage, current VisibleRect);433 imageViewer.checkAndModifyVisibleRectSize(currentImage, currentEntry, currentVisibleRect); 378 434 } else { 379 currentVisibleRect.checkRectSize( );435 currentVisibleRect.checkRectSize(currentEntry, zoom); 380 436 } 381 437 382 438 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image. 383 Rectangle drawRect = calculateDrawImageRectangle(currentVisibleRect, getSize()); 439 final Rectangle drawRect; 440 if (currentEntry instanceof IImageTiling) { 441 final byte multiplyBy = rotation > 0 ? (byte) -2 : (byte) 2; 442 drawRect = new VisRect(currentVisibleRect.x * multiplyBy, currentVisibleRect.y * multiplyBy, 443 currentVisibleRect.width * multiplyBy, currentVisibleRect.height * multiplyBy); 444 } else { 445 drawRect = calculateDrawImageRectangle(currentVisibleRect, getSize()); 446 } 384 447 currentVisibleRect.x = mousePointInImg.x + ((drawRect.x - x) * currentVisibleRect.width) / drawRect.width; 385 448 currentVisibleRect.y = mousePointInImg.y + ((drawRect.y - y) * currentVisibleRect.height) / drawRect.height; 386 449 387 450 // The position is also limited by the image size 388 currentVisibleRect.checkRectPos( );451 currentVisibleRect.checkRectPos(currentEntry, zoom); 389 452 390 453 synchronized (ImageDisplay.this) { 391 454 if (ImageDisplay.this.entry == currentEntry) { … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 447 510 } 448 511 449 512 // Calculate the translation to set the clicked point the center of the view. 450 Point click = comp2imgCoord(current VisibleRect, e.getX(), e.getY(), getSize());513 Point click = comp2imgCoord(currentEntry, currentVisibleRect, e.getX(), e.getY(), getSize(), zoom.get()); 451 514 Point center = getCenterImgCoord(currentVisibleRect); 452 515 453 516 currentVisibleRect.x += click.x - center.x; 454 517 currentVisibleRect.y += click.y - center.y; 455 518 456 currentVisibleRect.checkRectPos( );519 currentVisibleRect.checkRectPos(currentEntry, ImageDisplay.this.zoom.get()); 457 520 458 521 synchronized (ImageDisplay.this) { 459 522 if (ImageDisplay.this.entry == currentEntry) { … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 467 530 * a picture part) */ 468 531 @Override 469 532 public void mousePressed(MouseEvent e) { 470 Image currentImage; 471 VisRect currentVisibleRect; 533 final Image currentImage; 534 final VisRect currentVisibleRect; 535 final IImageEntry<?> imageEntry; 472 536 473 537 synchronized (ImageDisplay.this) { 474 538 currentImage = ImageDisplay.this.image; 475 539 currentVisibleRect = ImageDisplay.this.visibleRect; 540 imageEntry = ImageDisplay.this.entry; 476 541 } 477 542 478 543 if (currentImage == null) … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 481 546 selectedRect = null; 482 547 483 548 if (mouseIsDragging(e) || mouseIsZoomSelecting(e)) 484 mousePointInImg = comp2imgCoord( currentVisibleRect, e.getX(), e.getY(), getSize());549 mousePointInImg = comp2imgCoord(imageEntry, currentVisibleRect, e.getX(), e.getY(), getSize(), zoom.get()); 485 550 } 486 551 487 552 @Override … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 503 568 return; 504 569 505 570 if (mouseIsDragging(e) && mousePointInImg != null) { 506 Point p = comp2imgCoord( currentVisibleRect, e.getX(), e.getY(), getSize());571 Point p = comp2imgCoord(imageEntry, currentVisibleRect, e.getX(), e.getY(), getSize(), zoom.get()); 507 572 getIImageViewer(entry).mouseDragged(this.mousePointInImg, p, currentVisibleRect); 508 currentVisibleRect.checkRectPos( );573 currentVisibleRect.checkRectPos(imageEntry, ImageDisplay.this.zoom.get()); 509 574 synchronized (ImageDisplay.this) { 510 575 if (ImageDisplay.this.entry == imageEntry) { 511 576 ImageDisplay.this.visibleRect = currentVisibleRect; … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 514 579 // We have to update the mousePointInImg for 360 image panning, as otherwise the panning never stops. 515 580 // This does not work well with the perspective viewer at this time (2021-08-26). 516 581 boolean is360panning = entry != null && Projections.EQUIRECTANGULAR == entry.getProjectionType(); 517 if (is360panning ) {582 if (is360panning || entry instanceof IImageTiling) { 518 583 this.mousePointInImg = p; 519 584 } 520 585 ImageDisplay.this.repaint(); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 525 590 } 526 591 527 592 if (mouseIsZoomSelecting(e) && mousePointInImg != null) { 528 Point p = comp2imgCoord( currentVisibleRect, e.getX(), e.getY(), getSize());593 Point p = comp2imgCoord(imageEntry, currentVisibleRect, e.getX(), e.getY(), getSize(), zoom.get()); 529 594 currentVisibleRect.checkPointInside(p); 530 595 VisRect selectedRectTemp = new VisRect( 531 596 Math.min(p.x, mousePointInImg.x), … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 533 598 p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x, 534 599 p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y, 535 600 currentVisibleRect); 536 selectedRectTemp.checkRectSize( );537 selectedRectTemp.checkRectPos( );601 selectedRectTemp.checkRectSize(imageEntry, zoom.get()); 602 selectedRectTemp.checkRectPos(imageEntry, zoom.get()); 538 603 ImageDisplay.this.selectedRect = selectedRectTemp; 539 604 ImageDisplay.this.repaint(); 540 605 } … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 574 639 selectedRect.y -= (selectedRect.height - oldHeight) / 2; 575 640 } 576 641 577 selectedRect.checkRectSize( );578 selectedRect.checkRectPos( );642 selectedRect.checkRectSize(currentEntry, zoom.get()); 643 selectedRect.checkRectPos(currentEntry, zoom.get()); 579 644 } 580 645 581 646 synchronized (ImageDisplay.this) { … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 719 784 Rectangle r = new Rectangle(currentVisibleRect); 720 785 Rectangle target = calculateDrawImageRectangle(currentVisibleRect, size); 721 786 722 currentImageViewer.paintImage(g, currentImage, target, r); 787 if (currentEntry instanceof IImageTiling && ((IImageTiling) currentEntry).isTilingEnabled()) { 788 currentImageViewer.paintTiledImage(g, (IImageTiling) currentEntry, target, r, zoom.get()); 789 } else { 790 currentImageViewer.paintImage(g, currentImage, target, r); 791 } 723 792 paintSelectedRect(g, target, currentVisibleRect, size); 724 793 if (currentErrorLoading && currentEntry != null) { 725 794 String loadingStr = tr("Error on file {0}", currentEntry.getDisplayName()); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 826 895 */ 827 896 private void paintSelectedRect(Graphics g, Rectangle target, VisRect visibleRectTemp, Dimension size) { 828 897 if (selectedRect != null) { 829 Point topLeft = img2compCoord( visibleRectTemp, selectedRect.x, selectedRect.y, size);830 Point bottomRight = img2compCoord( visibleRectTemp,898 Point topLeft = img2compCoord(entry, visibleRectTemp, selectedRect.x, selectedRect.y, size, zoom.get()); 899 Point bottomRight = img2compCoord(entry, visibleRectTemp, 831 900 selectedRect.x + selectedRect.width, 832 selectedRect.y + selectedRect.height, size );901 selectedRect.y + selectedRect.height, size, zoom.get()); 833 902 g.setColor(new Color(128, 128, 128, 180)); 834 903 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y); 835 904 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 840 909 } 841 910 } 842 911 843 static Point img2compCoord(VisRect visibleRect, int xImg, int yImg, Dimension compSize) { 844 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize); 912 /** 913 * Convert an image coordinate to a component coordinate 914 * @param imageEntry The image entry -- only used if tiling 915 * @param visibleRect The visible rectangle 916 * @param xImg The x position in the component 917 * @param yImg The y position in the component 918 * @param compSize The component size 919 * @param zoom The current zoom level 920 * @return The point in the image 921 */ 922 static Point img2compCoord(IImageEntry<?> imageEntry, VisRect visibleRect, int xImg, int yImg, Dimension compSize, int zoom) { 923 final Rectangle drawRect; 924 if (imageEntry instanceof IImageTiling) { 925 drawRect = visibleRect; 926 } else { 927 drawRect = calculateDrawImageRectangle(visibleRect, compSize); 928 } 845 929 return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width, 846 930 drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height); 847 931 } 848 932 849 static Point comp2imgCoord(VisRect visibleRect, int xComp, int yComp, Dimension compSize) { 850 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize); 933 /** 934 * Convert a component coordinate to an image coordinate 935 * @param imageEntry The image entry -- only used if tiling 936 * @param visibleRect The visible rectangle 937 * @param xComp The x position in the component 938 * @param yComp The y position in the component 939 * @param compSize The component size 940 * @param zoom The current zoom level 941 * @return The point in the image 942 */ 943 static Point comp2imgCoord(IImageEntry<?> imageEntry, VisRect visibleRect, int xComp, int yComp, Dimension compSize, int zoom) { 944 final Rectangle drawRect; 945 if (imageEntry instanceof IImageTiling) { 946 drawRect = visibleRect; 947 } else { 948 drawRect = calculateDrawImageRectangle(visibleRect, compSize); 949 } 851 950 Point p = new Point( 852 951 ((xComp - drawRect.x) * visibleRect.width), 853 952 ((yComp - drawRect.y) * visibleRect.height)); … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 944 1043 Point center = getCenterImgCoord(currentVisibleRect); 945 1044 currentVisibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2, 946 1045 getWidth(), getHeight()); 947 currentVisibleRect.checkRectSize( );948 currentVisibleRect.checkRectPos( );1046 currentVisibleRect.checkRectSize(currentEntry, this.zoom.get()); 1047 currentVisibleRect.checkRectPos(currentEntry, this.zoom.get()); 949 1048 } 950 1049 951 1050 synchronized (this) { … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 1017 1116 } else { 1018 1117 rectangle.height = wFact / getSize().width; 1019 1118 } 1119 1120 final IImageEntry<?> currentEntry; 1121 synchronized (this) { 1122 currentEntry = this.entry; 1123 } 1124 if (currentEntry instanceof IImageTiling) { 1125 IImageTiling imageTiling = (IImageTiling) currentEntry; 1126 if (this.zoom.get() > imageTiling.getMaxZoom()) { 1127 this.zoom.set(imageTiling.getMaxZoom()); 1128 } else if (this.zoom.get() < imageTiling.getMinZoom()) { 1129 this.zoom.set(imageTiling.getMinZoom()); 1130 } 1131 } 1020 1132 } 1021 1133 1022 1134 /** … … public class ImageDisplay extends JComponent implements Destroyable, PreferenceC 1027 1139 public void updateVisibleRectangle() { 1028 1140 final VisRect currentVisibleRect; 1029 1141 final Image mouseImage; 1030 final IImageViewer iImageViewer; 1142 final IImageViewer currentImageViewer; 1143 final IImageEntry<?> imageEntry; 1031 1144 synchronized (this) { 1032 1145 currentVisibleRect = this.visibleRect; 1033 1146 mouseImage = this.image; 1034 iImageViewer = this.getIImageViewer(this.entry); 1147 imageEntry = this.entry; 1148 currentImageViewer = this.getIImageViewer(imageEntry); 1035 1149 } 1036 if (mouseImage != null && currentVisibleRect != null && iImageViewer != null) {1037 final Image maxImageSize = iImageViewer.getMaxImageSize(this, mouseImage);1150 if (mouseImage != null && currentVisibleRect != null && currentImageViewer != null) { 1151 final Image maxImageSize = currentImageViewer.getMaxImageSize(this, mouseImage); 1038 1152 final VisRect maxVisibleRect = new VisRect(0, 0, maxImageSize.getWidth(null), maxImageSize.getHeight(null)); 1039 1153 maxVisibleRect.setRect(currentVisibleRect); 1040 1154 ensureMaxZoom(maxVisibleRect); 1041 1155 1042 maxVisibleRect.checkRectSize( );1156 maxVisibleRect.checkRectSize(imageEntry, this.zoom.get()); 1043 1157 synchronized (this) { 1044 1158 this.visibleRect = maxVisibleRect; 1045 1159 } -
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..082496e1d4 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 final Rectangle tile = IImageTiling.super.getTileDimension(zoom, column, row, tileSize); 221 if (column < 0 || row < 0 || zoom > this.getMaxZoom() || tile.getWidth() <= 0 || tile.getHeight() <= 0) { 222 return null; 223 } 224 final URL imageUrl; 225 final BufferedImage image; 226 try { 227 imageUrl = getImageUrl(); 228 Logging.info(tr("Loading {0} at {1}/{2}/{3} with size {4}", imageUrl, zoom, column, row, tileSize)); 229 image = ImageProvider.read(imageUrl, true, false, 230 r -> this.withSubsampling(r, tile, zoom)); 231 } catch (IOException e) { 232 Logging.error(e); 233 return null; 234 } 235 236 if (image == null) { 237 Logging.warn("Unable to load {0}", imageUrl); 238 } 239 // applyExifRotation not used here since it will not work with tiled images 240 // Instead, we will have to rotate the column/row, and then apply rotation here. 241 return image; 242 } 243 215 244 protected URL getImageUrl() throws MalformedURLException { 216 245 return getFile().toURI().toURL(); 217 246 } 218 247 248 private ImageReadParam withSubsampling(ImageReader reader, final Rectangle tile, int zoom) { 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(); … … public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry> 247 284 g.dispose(); 248 285 return rotated; 249 286 } 287 288 @Override 289 public boolean isTilingEnabled() { 290 // Flipped images are going to need more work (the column/rows will need to be translated) 291 return IImageTiling.super.isTilingEnabled() && !ExifReader.orientationNeedsCorrection(getExifOrientation()); 292 } 250 293 } -
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..7f9a05a42d 100644
a b import java.awt.event.ComponentListener; 10 10 import java.awt.image.BufferedImage; 11 11 import java.util.Set; 12 12 13 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 13 14 import org.openstreetmap.josm.data.imagery.street_level.Projections; 14 15 import org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay; 16 import org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling.IImageTiling; 15 17 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 16 18 17 19 /** … … public interface IImageViewer extends ComponentListener { 34 36 */ 35 37 void paintImage(Graphics g, BufferedImage image, Rectangle target, Rectangle visibleRect); 36 38 39 40 /** 41 * Paint the image tile 42 * @param g The graphics to paint on 43 * @param entry The image entry (specifically, with the tile size) 44 * @param tile The tile to paint (x, y, z) 45 * @param image The image to paint 46 */ 47 default void paintImageTile(Graphics g, Rectangle target, Rectangle visibleRect, IImageTiling entry, IImageTiling.ImageTile tile, Image image) { 48 final Rectangle toUse = visibleRect; 49 g.drawImage(image, -toUse.x + entry.getTileSize() * tile.getXIndex(), -toUse.y + entry.getTileSize() * tile.getYIndex(), null); 50 } 51 52 /** 53 * Paint the image 54 * @param g The graphics to paint on 55 * @param imageEntry The image to paint 56 * @param target The target area 57 * @param visibleRect The visible rectangle 58 * @param zoom The zoom level 59 */ 60 default void paintTiledImage(Graphics g, IImageTiling imageEntry, Rectangle target, Rectangle visibleRect, int zoom) { 61 imageEntry.getTiles(zoom, visibleRect).forEachOrdered(pair -> this.paintImageTile(g, target, visibleRect, imageEntry, pair.a, pair.b)); 62 } 63 37 64 /** 38 65 * Get the default visible rectangle for the projection 39 66 * @param component The component the image will be displayed in … … public interface IImageViewer extends ComponentListener { 42 69 */ 43 70 ImageDisplay.VisRect getDefaultVisibleRectangle(Component component, Image image); 44 71 72 /** 73 * Get the default visible rectangle for the projection and entry 74 * @param component The component the image will be displayed in 75 * @param image The image that will be shown 76 * @param entry The entry that will be used 77 * @return The default visible rectangle 78 */ 79 default ImageDisplay.VisRect getDefaultVisibleRectangle(Component component, Image image, IImageEntry<?> entry) { 80 return this.getDefaultVisibleRectangle(component, image); 81 } 82 45 83 /** 46 84 * Get the current rotation in the image viewer 47 85 * @return The rotation … … public interface IImageViewer extends ComponentListener { 77 115 } 78 116 } 79 117 118 /** 119 * Check and modify the visible rect size to appropriate dimensions 120 * @param visibleRect the visible rectangle to update 121 * @param entry the entry to use for checking 122 * @param image The image to use for checking 123 */ 124 default void checkAndModifyVisibleRectSize(Image image, IImageEntry<?> entry, ImageDisplay.VisRect visibleRect) { 125 if (entry instanceof IImageTiling) { 126 final IImageTiling tiling = (IImageTiling) entry; 127 if (visibleRect.width > tiling.getWidth()) { 128 visibleRect.width = tiling.getWidth(); 129 } 130 if (visibleRect.height > tiling.getHeight()) { 131 visibleRect.height = tiling.getHeight(); 132 } 133 if (visibleRect.x + visibleRect.width > tiling.getWidth()) { 134 visibleRect.x = tiling.getWidth() - visibleRect.width; 135 } 136 if (visibleRect.y + visibleRect.height > tiling.getHeight()) { 137 visibleRect.y = tiling.getHeight() - visibleRect.height; 138 } 139 if (visibleRect.x < 0) { 140 visibleRect.x = 0; 141 } 142 if (visibleRect.y < 0) { 143 visibleRect.y = 0; 144 } 145 } else { 146 this.checkAndModifyVisibleRectSize(image, visibleRect); 147 } 148 } 149 80 150 /** 81 151 * Get the maximum image size that can be displayed 82 152 * @param imageDisplay The image display -
src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/Perspective.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/Perspective.java b/src/org/openstreetmap/josm/gui/layer/geoimage/viewers/projections/Perspective.java index d570c53dde..08d4add18b 100644
a b import java.awt.image.BufferedImage; 10 10 import java.util.EnumSet; 11 11 import java.util.Set; 12 12 13 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 13 14 import org.openstreetmap.josm.data.imagery.street_level.Projections; 14 15 import org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay; 16 import org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling.IImageTiling; 15 17 16 18 /** 17 19 * The default perspective image viewer class. … … public class Perspective extends ComponentAdapter implements IImageViewer { 36 38 public ImageDisplay.VisRect getDefaultVisibleRectangle(Component component, Image image) { 37 39 return new ImageDisplay.VisRect(0, 0, image.getWidth(null), image.getHeight(null)); 38 40 } 41 42 @Override 43 public ImageDisplay.VisRect getDefaultVisibleRectangle(Component component, Image image, IImageEntry<?> entry) { 44 if (entry instanceof IImageTiling) { 45 return new ImageDisplay.VisRect(0, 0, ((IImageTiling) entry).getWidth(), ((IImageTiling) entry).getHeight()); 46 } 47 return IImageViewer.super.getDefaultVisibleRectangle(component, image, entry); 48 } 39 49 } -
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..84ab763822
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage.viewers.tiling; 3 4 import java.awt.Dimension; 5 import java.awt.Image; 6 import java.awt.Rectangle; 7 import java.awt.image.RenderedImage; 8 import java.io.IOException; 9 import java.text.MessageFormat; 10 import java.util.function.DoubleToIntFunction; 11 import java.util.function.IntUnaryOperator; 12 import java.util.stream.IntStream; 13 import java.util.stream.Stream; 14 15 import org.apache.commons.jcs3.access.CacheAccess; 16 import org.openstreetmap.gui.jmapviewer.TileXY; 17 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 18 import org.openstreetmap.josm.data.cache.JCSCacheManager; 19 import org.openstreetmap.josm.spi.preferences.Config; 20 import org.openstreetmap.josm.tools.Logging; 21 import org.openstreetmap.josm.tools.Pair; 22 23 /** 24 * An interface for tiled images. Primarily used to reduce memory usage in large images. 25 * @author Taylor Smock 26 * @since xxx 27 */ 28 public interface IImageTiling { 29 /** 30 * Just a class to hold tile information 31 */ 32 class ImageTile extends TileXY { 33 final int zoom; 34 /** 35 * Returns an instance of an image tile. 36 * @param x number of the tile 37 * @param y number of the tile 38 * @param z The zoom level 39 */ 40 public ImageTile(int x, int y, int z) { 41 super(x, y); 42 this.zoom = z; 43 } 44 } 45 46 /** 47 * The default tile size for the image tiles -- each tile takes 1024 px * 1024 px * 4 bytes = 4 MiB max 48 * A 4k image (4160x3120) has (Math.ceil(4160/1024) * Math.ceil(3120/1024) = 20 tiles). Some tiles are almost empty. 49 * This gives a reasonable number of tiles for most image sizes. 50 */ 51 int DEFAULT_TILE_SIZE = 1024; 52 53 /** A good default minimum zoom (the image size is {@link #DEFAULT_TILE_SIZE} max, at 1024 it is 5) */ 54 int DEFAULT_MIN_ZOOM = (int) Math.round(Math.log(Math.sqrt(DEFAULT_TILE_SIZE))/Math.log(2)); 55 56 /** A cache for images */ 57 CacheAccess<String, BufferedImageCacheEntry> IMAGE_CACHE = JCSCacheManager.getCache("iimagetiling", 100, 1_000, Config.getDirs().getCacheDirectory(true).getAbsolutePath()); 58 59 /** 60 * Get the size of the image at a specified zoom level 61 * @param zoom The zoom level. Zoom 0 == 1 px for the image. Zoom 1 == 4 px for the image. 62 * @return The number of pixels (max, for a square image) 63 */ 64 static long getSizeAtZoom(final int zoom) { 65 final long dimension = 1L << zoom; 66 return dimension * dimension; 67 } 68 69 /** 70 * Get the default tile size. 71 * @return The tile size to use 72 */ 73 default int getDefaultTileSize() { 74 return DEFAULT_TILE_SIZE; 75 } 76 77 /** 78 * Get the tile size. 79 * @return The tile size to use 80 */ 81 default int getTileSize() { 82 return this.getDefaultTileSize(); 83 } 84 85 /** 86 * Get the maximum zoom that the image supports 87 * Feel free to override and cache the result for performance reasons. 88 * 89 * @return The maximum zoom of the image 90 */ 91 default int getMaxZoom() { 92 final int maxSize = Math.max(this.getWidth(), this.getHeight()); 93 return (int) Math.round(Math.ceil(Math.log(maxSize) / Math.log(2))); 94 } 95 96 /** 97 * Get the minimum zoom that the image supports or makes sense 98 * @return The minimum zoom that makes sense 99 */ 100 default int getMinZoom() { 101 final IntUnaryOperator minZoom = input -> Math.toIntExact(Math.round(Math.floor(this.getMaxZoom() + Math.log((double) this.getTileSize() / input) / Math.log(2)))); 102 return Math.min(minZoom.applyAsInt(this.getWidth()), minZoom.applyAsInt(this.getHeight())); 103 } 104 105 /** 106 * Get the current scale of the image 107 * @param zoom The zoom level 108 * @return The scaling of the image at the specified level 109 */ 110 default double getScale(final int zoom) { 111 return Math.pow(2, (double) zoom - this.getMaxZoom()); 112 } 113 114 /** 115 * Get the width of the image 116 * @return The width of the image 117 */ 118 int getWidth(); 119 120 /** 121 * Get the width of the image at a specified scale 122 * @param zoom The zoom to use 123 * @return The width at the specified scale 124 */ 125 default int getWidth(final int zoom) { 126 return Math.toIntExact(Math.round(this.getScale(zoom) * this.getWidth())); 127 } 128 129 /** 130 * Get the height of the image 131 * @return The height of the image 132 */ 133 int getHeight(); 134 135 /** 136 * Get the height of the image at a specified scale 137 * @param zoom The zoom to use 138 * @return The height at the specified scale 139 */ 140 default int getHeight(final int zoom) { 141 return Math.toIntExact(Math.round(this.getScale(zoom) * this.getHeight())); 142 } 143 144 /** 145 * Get the size of the image 146 * @return The image size at the zoom level 147 */ 148 default Dimension getSize(int zoom) { 149 return new Dimension(this.getWidth(zoom), this.getHeight(zoom)); 150 } 151 152 /** 153 * Get the number of rows at a specified zoom level 154 * @param zoom The zoom level 155 * @return The number of rows 156 */ 157 default int getRows(final int zoom) { 158 return this.getRows(zoom, this.getTileSize()); 159 } 160 161 /** 162 * Get the number of rows at a specified zoom level 163 * @param zoom The zoom level 164 * @param tileSize The tile size 165 * @return The number of rows 166 */ 167 default int getRows(final int zoom, final int tileSize) { 168 final int height = this.getHeight(zoom); 169 return Math.toIntExact(Math.round(Math.ceil(height / (double) tileSize))); 170 } 171 172 /** 173 * Get the number of columns at a specified zoom level 174 * @param zoom The zoom level 175 * @return The number of columns 176 */ 177 default int getColumns(final int zoom) { 178 return this.getColumns(zoom, this.getTileSize()); 179 } 180 181 /** 182 * Get the number of columns at a specified zoom level 183 * @param zoom The zoom level 184 * @param tileSize The tile size 185 * @return The number of columns 186 */ 187 default int getColumns(final int zoom, final int tileSize) { 188 final int width = this.getWidth(zoom); 189 return Math.toIntExact(Math.round(Math.ceil(width / (double) tileSize))); 190 } 191 192 /** 193 * Get the image to show for a specific tile location. This should be cached by the implementation in most cases. 194 * Top-left corner is 0,0 195 * @param zoom The zoom to use 196 * @param tileSize The tile size to use 197 * @param column The column to get (x) 198 * @param row The row to get (y) 199 * @return The image to display (not padded). May be {@code null}. 200 */ 201 Image getTileImage(int zoom, int tileSize, int column, int row); 202 203 /** 204 * Get the image to show for a specific tile location with the default tile size 205 * Top-left corner is 0,0 206 * @param zoom The zoom to use 207 * @param column The column to get (x) 208 * @param row The row to get (y) 209 * @return The image to display (not padded). May be {@code null}. 210 */ 211 default Image getTileImage(final int zoom, final int column, final int row) { 212 final String storage = MessageFormat.format("{0}: {1}/{2}/{3}", this, zoom, column, row); 213 BufferedImageCacheEntry image = IMAGE_CACHE.get(storage); 214 if (image == null) { 215 Image newImage = this.getTileImage(zoom, this.getTileSize(), column, row); 216 if (newImage instanceof RenderedImage) { 217 IMAGE_CACHE.put(storage, BufferedImageCacheEntry.pngEncoded((RenderedImage) newImage)); 218 } 219 return newImage; 220 } 221 try { 222 return image.getImage(); 223 } catch (IOException e) { 224 Logging.error(e); 225 } 226 return null; 227 } 228 229 /** 230 * Get the subsection of the image to show 231 * Top-left corner is 0,0 232 * @param zoom The zoom to use 233 * @param column The column to get (x) 234 * @param row The row to get (y) 235 * @return The subsection of the image to get 236 */ 237 default Rectangle getTileDimension(final int zoom, final int column, final int row) { 238 return this.getTileDimension(zoom, column, row, this.getTileSize()); 239 } 240 241 /** 242 * Get the subsection of the image to show 243 * Top-left corner is 0,0 244 * @param zoom The zoom to use 245 * @param column The column to get (x) 246 * @param row The row to get (y) 247 * @param tileSize the tile size to use 248 * @return The subsection of the image to get 249 */ 250 default Rectangle getTileDimension(final int zoom, final int column, final int row, final int tileSize) { 251 final double scale = this.getScale(zoom); // e.g., 1, 1/2, 1/4, etc. 252 final DoubleToIntFunction roundToInt = dbl -> Math.toIntExact(Math.round(Math.floor(dbl))); 253 final int x = roundToInt.applyAsInt(column * tileSize / scale); 254 final int y = roundToInt.applyAsInt(row * tileSize / scale); 255 final int defaultDimension = roundToInt.applyAsInt(tileSize / scale); 256 final int width = Math.min(defaultDimension, roundToInt.applyAsInt(this.getWidth() - column * tileSize / scale)); 257 final int height = Math.min(defaultDimension, roundToInt.applyAsInt(this.getHeight() - row * tileSize / scale)); 258 return new Rectangle(x, y, width, height); 259 } 260 261 /** 262 * Get the tiles for a zoom level given a visible rectangle 263 * @param zoom The zoom to get 264 * @param visibleRect The rectangle to get 265 * @return A stream of tiles to images (may be parallel) 266 */ 267 default Stream<Pair<ImageTile, Image>> getTiles(int zoom, Rectangle visibleRect) { 268 // We very specifically want to "overscan" -- this fixes some issues where the image isn't fully loaded 269 final int startX = Math.max(0, Math.toIntExact(Math.round(Math.floor(visibleRect.getMinX() / this.getTileSize()))) - 1); 270 final int startY = Math.max(0, Math.toIntExact(Math.round(Math.floor(visibleRect.getMinY() / this.getTileSize()))) - 1); 271 final int endX = Math.min(this.getColumns(zoom), Math.toIntExact(Math.round(Math.ceil(visibleRect.getMaxX() / this.getTileSize()))) + 1); 272 final int endY = Math.min(this.getRows(zoom), Math.toIntExact(Math.round(Math.ceil(visibleRect.getMaxY() / this.getTileSize()))) + 1); 273 return IntStream.rangeClosed(startX, endX).mapToObj(x -> IntStream.rangeClosed(startY, endY).mapToObj(y -> new ImageTile(x, y, zoom))) 274 .flatMap(stream -> stream).parallel().map(tile -> new Pair<>(tile, this.getTileImage(tile.zoom, tile.getXIndex(), tile.getYIndex()))); 275 } 276 277 /** 278 * Check if tiling is enabled for this object. 279 * 280 * @return {@code true} if tiling should be u sed 281 */ 282 default boolean isTilingEnabled() { 283 return true; 284 } 285 } -
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 }