Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 12722)
@@ -82,4 +82,5 @@
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.HiDPISupport;
 import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.bugreport.BugReport;
@@ -417,5 +418,9 @@
                 }
             } else {
-                TexturePaint texture = new TexturePaint(fillImage.getImage(disabled),
+                Image img = fillImage.getImage(disabled);
+                // TexturePaint requires BufferedImage -> get base image from
+                // possible multi-resolution image
+                img = HiDPISupport.getBaseImage(img);
+                TexturePaint texture = new TexturePaint((BufferedImage) img,
                         new Rectangle(0, 0, fillImage.getWidth(), fillImage.getHeight()));
                 g.setPaint(texture);
@@ -657,5 +662,5 @@
         double startOffset = computeStartOffset(phase, repeat);
 
-        BufferedImage image = pattern.getImage(disabled);
+        Image image = pattern.getImage(disabled);
 
         path.visitClippedLine(repeat, (inLineOffset, start, end, startIsOldEnd) -> {
@@ -1150,5 +1155,5 @@
         displayText(() -> {
             AffineTransform defaultTransform = g.getTransform();
-            g.setTransform(at);
+            g.transform(at);
             g.setFont(text.font);
             g.drawString(name, 0, 0);
Index: trunk/src/org/openstreetmap/josm/gui/MapView.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/gui/MapView.java	(revision 12722)
@@ -9,4 +9,5 @@
 import java.awt.Point;
 import java.awt.Rectangle;
+import java.awt.Shape;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
@@ -15,4 +16,5 @@
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionListener;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.image.BufferedImage;
@@ -511,4 +513,29 @@
 
     private void drawMapContent(Graphics g) {
+        // In HiDPI-mode, the Graphics g will have a transform that scales
+        // everything by a factor of 2.0 or so. At the same time, the value returned
+        // by getWidth()/getHeight will be reduced by that factor.
+        //
+        // This would work as intended, if we were to draw directly on g. But
+        // with a temporary buffer image, we need to move the scale transform to
+        // the Graphics of the buffer image and (in the end) transfer the content
+        // of the temporary buffer pixel by pixel onto g, without scaling.
+        // (Otherwise, we would upscale a small buffer image and the result would be
+        // blurry, with 2x2 pixel blocks.)
+        Graphics2D gg = (Graphics2D) g;
+        AffineTransform trOrig = gg.getTransform();
+        double uiScaleX = gg.getTransform().getScaleX();
+        double uiScaleY = gg.getTransform().getScaleY();
+        // width/height in full-resolution screen pixels
+        int width = (int) Math.round(getWidth() * uiScaleX);
+        int height = (int) Math.round(getHeight() * uiScaleY);
+        // This transformation corresponds to the original transformation of g,
+        // except for the translation part. It will be applied to the temporary
+        // buffer images.
+        AffineTransform trDef = AffineTransform.getScaleInstance(uiScaleX, uiScaleY);
+        // The goal is to create the temporary image at full pixel resolution,
+        // so scale up the clip shape
+        Shape scaledClip = trDef.createTransformedShape(g.getClip());
+
         List<Layer> visibleLayers = layerManager.getVisibleLayersInZOrder();
 
@@ -529,20 +556,18 @@
                 && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size()));
 
-        if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
-            offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
-        }
-
-        Graphics2D tempG = offscreenBuffer.createGraphics();
-        tempG.setClip(g.getClip());
+        if (null == offscreenBuffer || offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) {
+            offscreenBuffer = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
+        }
 
         if (!canUseBuffer || nonChangedLayersBuffer == null) {
             if (null == nonChangedLayersBuffer
-                    || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
-                nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+                    || nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) {
+                nonChangedLayersBuffer = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
             }
             Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
-            g2.setClip(g.getClip());
+            g2.setClip(scaledClip);
+            g2.setTransform(trDef);
             g2.setColor(PaintColors.getBackgroundColor());
-            g2.fillRect(0, 0, getWidth(), getHeight());
+            g2.fillRect(0, 0, width, height);
 
             for (int i = 0; i < nonChangedLayersCount; i++) {
@@ -553,5 +578,6 @@
             if (nonChangedLayers.size() != nonChangedLayersCount) {
                 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
-                g2.setClip(g.getClip());
+                g2.setClip(scaledClip);
+                g2.setTransform(trDef);
                 for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
                     paintLayer(visibleLayers.get(i), g2);
@@ -565,5 +591,9 @@
         lastClipBounds = g.getClipBounds();
 
+        Graphics2D tempG = offscreenBuffer.createGraphics();
+        tempG.setClip(scaledClip);
+        tempG.setTransform(new AffineTransform());
         tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
+        tempG.setTransform(trDef);
 
         for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
@@ -572,5 +602,7 @@
 
         try {
-            drawTemporaryLayers(tempG, getLatLonBounds(g.getClipBounds()));
+            drawTemporaryLayers(tempG, getLatLonBounds(new Rectangle(
+                    (int) Math.round(g.getClipBounds().x * uiScaleX),
+                    (int) Math.round(g.getClipBounds().y * uiScaleY))));
         } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
             BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn();
@@ -597,5 +629,6 @@
 
         try {
-            g.drawImage(offscreenBuffer, 0, 0, null);
+            gg.setTransform(new AffineTransform(1, 0, 0, 1, trOrig.getTranslateX(), trOrig.getTranslateY()));
+            gg.drawImage(offscreenBuffer, 0, 0, null);
         } catch (ClassCastException e) {
             // See #11002 and duplicate tickets. On Linux with Java >= 8 Many users face this error here:
@@ -623,4 +656,6 @@
             // But the application seems to work fine after, so let's just log the error
             Logging.error(e);
+        } finally {
+            gg.setTransform(trOrig);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaElement.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaElement.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaElement.java	(revision 12722)
@@ -3,4 +3,6 @@
 
 import java.awt.Color;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
 import java.util.Objects;
 
@@ -16,4 +18,5 @@
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.HiDPISupport;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -79,6 +82,9 @@
         if (iconRef != null) {
             fillImage = new MapImage(iconRef.iconName, iconRef.source, false);
-
-            color = new Color(fillImage.getImage(false).getRGB(
+            Image img = fillImage.getImage(false);
+            // get base image from possible multi-resolution image, so we can
+            // cast to BufferedImage and get pixel value at the center of the image
+            img = HiDPISupport.getBaseImage(img);
+            color = new Color(((BufferedImage) img).getRGB(
                     fillImage.getWidth() / 2, fillImage.getHeight() / 2)
             );
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java	(revision 12722)
@@ -32,5 +32,5 @@
      * ImageIcon can change while the image is loading.
      */
-    private BufferedImage img;
+    private Image img;
 
     /**
@@ -102,5 +102,5 @@
      * @return the image
      */
-    public BufferedImage getImage(boolean disabled) {
+    public Image getImage(boolean disabled) {
         if (disabled) {
             return getDisabled();
@@ -110,5 +110,5 @@
     }
 
-    private BufferedImage getDisabled() {
+    private Image getDisabled() {
         if (disabledImgCache != null)
                 return disabledImgCache;
@@ -127,5 +127,5 @@
     }
 
-    private BufferedImage getImage() {
+    private Image getImage() {
         if (img != null)
             return img;
@@ -144,7 +144,7 @@
                             source.logWarning(tr("Failed to locate image ''{0}''", name));
                             ImageIcon noIcon = MapPaintStyles.getNoIconIcon(source);
-                            img = noIcon == null ? null : (BufferedImage) noIcon.getImage();
+                            img = noIcon == null ? null : noIcon.getImage();
                         } else {
-                            img = (BufferedImage) rescale(result.getImage());
+                            img = rescale(result.getImage());
                         }
                         if (temporary) {
@@ -160,5 +160,5 @@
         synchronized (this) {
             if (img == null) {
-                img = (BufferedImage) ImageProvider.get("clock").getImage();
+                img = ImageProvider.get("clock").getImage();
                 temporary = true;
             }
Index: trunk/src/org/openstreetmap/josm/tools/HiDPISupport.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/HiDPISupport.java	(revision 12722)
+++ trunk/src/org/openstreetmap/josm/tools/HiDPISupport.java	(revision 12722)
@@ -0,0 +1,258 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.awt.Dimension;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Helper class for HiDPI support.
+ *
+ * Gives access to the class <code>BaseMultiResolutionImage</code> via reflection,
+ * in case it is on classpath. This is to be expected for Java 9, but not for Java 8
+ * runtime.
+ *
+ * @since 12722
+ */
+public class HiDPISupport {
+
+    private static volatile Optional<Class<? extends Image>> baseMultiResolutionImageClass;
+    private static volatile Optional<Constructor<? extends Image>> baseMultiResolutionImageConstructor;
+    private static volatile Optional<Method> resolutionVariantsMethod;
+
+    /**
+     * Create a multi-resolution image from a base image and an {@link ImageResource}.
+     * <p>
+     * Will only return multi-resolution image, if HiDPI-mode is detected. Then
+     * the image stack will consist of the base image and one that fits the
+     * HiDPI scale of the main display.
+     * @param base the base image
+     * @param ir a corresponding image resource
+     * @return multi-resolution image if necessary and possible, the base image otherwise
+     */
+    public static Image getMultiResolutionImage(Image base, ImageResource ir) {
+        double uiScale = getHiDPIScale();
+        if (uiScale != 1.0 && getBaseMultiResolutionImageConstructor().isPresent()) {
+            ImageIcon zoomed = ir.getImageIcon(new Dimension(
+                    (int) Math.round(base.getWidth(null) * uiScale),
+                    (int) Math.round(base.getHeight(null) * uiScale)), false);
+            Image mrImg = getMultiResolutionImage(Arrays.asList(base, zoomed.getImage()));
+            if (mrImg != null) return mrImg;
+        }
+        return base;
+    }
+
+    /**
+     * Create a multi-resolution image from a list of images.
+     * @param imgs the images, supposedly the same image at different resolutions,
+     * must not be empty
+     * @return corresponding multi-resolution image, if possible, the first image
+     * in the list otherwise
+     */
+    public static Image getMultiResolutionImage(List<Image> imgs) {
+        CheckParameterUtil.ensure(imgs, "imgs", "not empty", ls -> !ls.isEmpty());
+        if (getBaseMultiResolutionImageConstructor().isPresent()) {
+            Constructor<? extends Image> c = getBaseMultiResolutionImageConstructor().get();
+            try {
+                return c.newInstance((Object) imgs.toArray(new Image[0]));
+            } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+                Logging.error("Unexpected error while instantiating object of class BaseMultiResolutionImage: " + ex);
+            }
+        }
+        return imgs.get(0);
+    }
+
+    /**
+     * Wrapper for the method <code>java.awt.image.BaseMultiResolutionImage#getBaseImage()</code>.
+     * <p>
+     * Will return the argument <code>img</code> unchanged, if it is not a multi-resolution
+     * image.
+     * @param img the image
+     * @return if <code>img</code> is a <code>java.awt.image.BaseMultiResolutionImage</code>,
+     * then the base image, otherwise the image itself
+     */
+    public static Image getBaseImage(Image img) {
+        if (!getBaseMultiResolutionImageClass().isPresent() || !getResolutionVariantsMethod().isPresent()) {
+            return img;
+        }
+        if (getBaseMultiResolutionImageClass().get().isInstance(img)) {
+            try {
+                @SuppressWarnings("unchecked")
+                List<Image> imgVars = (List) getResolutionVariantsMethod().get().invoke(img);
+                if (!imgVars.isEmpty()) {
+                    return imgVars.get(0);
+                }
+            } catch (IllegalAccessException | InvocationTargetException ex) {
+                Logging.error("Unexpected error while calling method: " + ex);
+            }
+        }
+        return img;
+    }
+
+    /**
+     * Wrapper for the method <code>java.awt.image.MultiResolutionImage#getResolutionVariants()</code>.
+     * <p>
+     * Will return the argument as a singleton list, in case it is not a multi-resolution
+     * image.
+     * @param img the image
+     * @return if <code>img</code> is a <code>java.awt.image.BaseMultiResolutionImage</code>,
+     * then the result of the method <code>#getResolutionVariants()</code>, otherwise the image
+     * itself as a singleton list
+     */
+    public static List<Image> getResolutionVariants(Image img) {
+        if (!getBaseMultiResolutionImageClass().isPresent() || !getResolutionVariantsMethod().isPresent()) {
+            return Collections.singletonList(img);
+        }
+        if (getBaseMultiResolutionImageClass().get().isInstance(img)) {
+            try {
+                @SuppressWarnings("unchecked")
+                List<Image> imgVars = (List) getResolutionVariantsMethod().get().invoke(img);
+                if (!imgVars.isEmpty()) {
+                    return imgVars;
+                }
+            } catch (IllegalAccessException | InvocationTargetException ex) {
+                Logging.error("Unexpected error while calling method: " + ex);
+            }
+        }
+        return Collections.singletonList(img);
+    }
+
+    /**
+     * Detect the GUI scale for HiDPI mode.
+     * <p>
+     * This method may not work as expected for a multi-monitor setup. It will
+     * only take the default screen device into account.
+     * @return the GUI scale for HiDPI mode, a value of 1.0 means standard mode.
+     */
+    private static double getHiDPIScale() {
+        GraphicsConfiguration gc = GraphicsEnvironment
+                .getLocalGraphicsEnvironment()
+                .getDefaultScreenDevice().
+                getDefaultConfiguration();
+        AffineTransform transform = gc.getDefaultTransform();
+        if (!Utils.equalsEpsilon(transform.getScaleX(), transform.getScaleY())) {
+            Logging.warn("Unexpected ui transform: " + transform);
+        }
+        return transform.getScaleX();
+    }
+
+    /**
+     * Perform an operation on multi-resolution images.
+     *
+     * When input image is not multi-resolution, it will simply apply the processor once.
+     * Otherwise, the processor will be called for each resolution variant and the
+     * resulting images assembled to become the output multi-resolution image.
+     * @param img input image, possibly multi-resolution
+     * @param processor processor taking a plain image as input and returning a single
+     * plain image as output
+     * @return multi-resolution image assembled from the output of calls to <code>processor</code>
+     * for each resolution variant
+     */
+    public static Image processMRImage(Image img, Function<Image, Image> processor) {
+        return processMRImages(Collections.singletonList(img), imgs -> processor.apply(imgs.get(0)));
+    }
+
+    /**
+     * Perform an operation on multi-resolution images.
+     *
+     * When input images are not multi-resolution, it will simply apply the processor once.
+     * Otherwise, the processor will be called for each resolution variant and the
+     * resulting images assembled to become the output multi-resolution image.
+     * @param imgs input images, possibly multi-resolution
+     * @param processor processor taking a list of plain images as input and returning
+     * a single plain image as output
+     * @return multi-resolution image assembled from the output of calls to <code>processor</code>
+     * for each resolution variant
+     */
+    public static Image processMRImages(List<Image> imgs, Function<List<Image>, Image> processor) {
+        CheckParameterUtil.ensureThat(imgs.size() >= 1, "at least on element expected");
+        if (!getBaseMultiResolutionImageClass().isPresent()) {
+            return processor.apply(imgs);
+        }
+        List<List<Image>> allVars = imgs.stream().map(HiDPISupport::getResolutionVariants).collect(Collectors.toList());
+        int maxVariants = allVars.stream().mapToInt(lst -> lst.size()).max().getAsInt();
+        if (maxVariants == 1)
+            return processor.apply(imgs);
+        List<Image> imgsProcessed = IntStream.range(0, maxVariants)
+                .mapToObj(
+                        k -> processor.apply(
+                                allVars.stream().map(vars -> vars.get(k)).collect(Collectors.toList())
+                        )
+                ).collect(Collectors.toList());
+        return getMultiResolutionImage(imgsProcessed);
+    }
+
+    private static Optional<Class<? extends Image>> getBaseMultiResolutionImageClass() {
+        if (baseMultiResolutionImageClass == null) {
+            synchronized (HiDPISupport.class) {
+                if (baseMultiResolutionImageClass == null) {
+                    try {
+                        @SuppressWarnings("unchecked")
+                        Class<? extends Image> c = (Class) Class.forName("java.awt.image.BaseMultiResolutionImage");
+                        baseMultiResolutionImageClass = Optional.ofNullable(c);
+                    } catch (ClassNotFoundException ex) {
+                        // class is not present in Java 8
+                        baseMultiResolutionImageClass = Optional.empty();
+                    }
+                }
+            }
+        }
+        return baseMultiResolutionImageClass;
+    }
+
+    private static Optional<Constructor<? extends Image>> getBaseMultiResolutionImageConstructor() {
+        if (baseMultiResolutionImageConstructor == null) {
+            synchronized (HiDPISupport.class) {
+                if (baseMultiResolutionImageConstructor == null) {
+                    getBaseMultiResolutionImageClass().ifPresent(klass -> {
+                        try {
+                            Constructor<? extends Image> constr = klass.getConstructor(Image[].class);
+                            baseMultiResolutionImageConstructor = Optional.ofNullable(constr);
+                        } catch (NoSuchMethodException ex) {
+                            Logging.error("Cannot find expected constructor: " + ex);
+                        }
+                    });
+                    if (baseMultiResolutionImageConstructor == null) {
+                        baseMultiResolutionImageConstructor = Optional.empty();
+                    }
+                }
+            }
+        }
+        return baseMultiResolutionImageConstructor;
+    }
+
+    private static Optional<Method> getResolutionVariantsMethod() {
+        if (resolutionVariantsMethod == null) {
+            synchronized (HiDPISupport.class) {
+                if (resolutionVariantsMethod == null) {
+                    getBaseMultiResolutionImageClass().ifPresent(klass -> {
+                        try {
+                            Method m = klass.getMethod("getResolutionVariants");
+                            resolutionVariantsMethod = Optional.ofNullable(m);
+                        } catch (NoSuchMethodException ex) {
+                            Logging.error("Cannot find expected method: "+ex);
+                        }
+                    });
+                    if (resolutionVariantsMethod == null) {
+                        resolutionVariantsMethod = Optional.empty();
+                    }
+                }
+            }
+        }
+        return resolutionVariantsMethod;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java	(revision 12722)
@@ -81,7 +81,5 @@
         }
         ImageIcon overlay;
-        if (width != -1 || height != -1) {
-            image = new ImageProvider(image).resetMaxSize(new Dimension(width, height));
-        }
+        image = new ImageProvider(image).setMaxSize(new Dimension(width, height));
         overlay = image.get();
         int x, y;
Index: trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 12722)
@@ -277,4 +277,6 @@
     /** <code>true</code> if icon must be grayed out */
     protected boolean isDisabled;
+    /** <code>true</code> if multi-resolution image is requested */
+    protected boolean multiResolution = true;
 
     private static SVGUniverse svgUniverse;
@@ -288,5 +290,5 @@
      * Caches the image data for rotated versions of the same image.
      */
-    private static final Map<Image, Map<Long, ImageResource>> ROTATE_CACHE = new HashMap<>();
+    private static final Map<Image, Map<Long, Image>> ROTATE_CACHE = new HashMap<>();
 
     private static final ExecutorService IMAGE_FETCHER =
@@ -334,4 +336,5 @@
         this.overlayInfo = image.overlayInfo;
         this.isDisabled = image.isDisabled;
+        this.multiResolution = image.multiResolution;
     }
 
@@ -596,4 +599,26 @@
 
     /**
+     * Decide, if multi-resolution image is requested (default <code>true</code>).
+     * <p>
+     * A <code>java.awt.image.MultiResolutionImage</code> is a Java 9 {@link Image}
+     * implementation, which adds support for HiDPI displays. The effect will be
+     * that in HiDPI mode, when GUI elements are scaled by a factor 1.5, 2.0, etc.,
+     * the images are not just up-scaled, but a higher resolution version of the
+     * image is rendered instead.
+     * <p>
+     * Use {@link HiDPISupport#getBaseImage(java.awt.Image)} to extract the original
+     * image from a multi-resolution image.
+     * <p>
+     * See {@link HiDPISupport#processMRImage} for how to process the image without
+     * removing the multi-resolution magic.
+     * @param multiResolution true, if multi-resolution image is requested
+     * @return the current object, for convenience
+     */
+    public ImageProvider setMultiResolution(boolean multiResolution) {
+        this.multiResolution = multiResolution;
+        return this;
+    }
+
+    /**
      * Execute the image request and scale result.
      * @return the requested image or null if the request failed
@@ -606,7 +631,7 @@
         }
         if (virtualMaxWidth != -1 || virtualMaxHeight != -1)
-            return ir.getImageIconBounded(new Dimension(virtualMaxWidth, virtualMaxHeight));
+            return ir.getImageIconBounded(new Dimension(virtualMaxWidth, virtualMaxHeight), multiResolution);
         else
-            return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight));
+            return ir.getImageIcon(new Dimension(virtualWidth, virtualHeight), multiResolution);
     }
 
@@ -1276,5 +1301,5 @@
 
     /**
-     * Creates a rotated version of the input image, scaled to the given dimension.
+     * Creates a rotated version of the input image.
      *
      * @param img the image to be rotated.
@@ -1282,6 +1307,5 @@
      * will mod it with 360 before using it. More over for caching performance, it will be rounded to
      * an entire value between 0 and 360.
-     * @param dimension The requested dimensions. Use (-1,-1) for the original size
-     * and (width, -1) to set the width, but otherwise scale the image proportionally.
+     * @param dimension ignored
      * @return the image after rotating and scaling.
      * @since 6172
@@ -1291,13 +1315,9 @@
 
         // convert rotatedAngle to an integer value from 0 to 360
-        Long originalAngle = Math.round(rotatedAngle % 360);
-        if (rotatedAngle != 0 && originalAngle == 0) {
-            originalAngle = 360L;
-        }
-
-        ImageResource imageResource;
+        Long angleLong = Math.round(rotatedAngle % 360);
+        Long originalAngle = rotatedAngle != 0 && angleLong == 0 ? 360L : angleLong;
 
         synchronized (ROTATE_CACHE) {
-            Map<Long, ImageResource> cacheByAngle = ROTATE_CACHE.get(img);
+            Map<Long, Image> cacheByAngle = ROTATE_CACHE.get(img);
             if (cacheByAngle == null) {
                 cacheByAngle = new HashMap<>();
@@ -1305,7 +1325,7 @@
             }
 
-            imageResource = cacheByAngle.get(originalAngle);
-
-            if (imageResource == null) {
+            Image rotatedImg = cacheByAngle.get(originalAngle);
+
+            if (rotatedImg == null) {
                 // convert originalAngle to a value from 0 to 90
                 double angle = originalAngle % 90;
@@ -1313,43 +1333,44 @@
                     angle = 90.0;
                 }
-
                 double radian = Utils.toRadians(angle);
 
-                new ImageIcon(img); // load completely
-                int iw = img.getWidth(null);
-                int ih = img.getHeight(null);
-                int w;
-                int h;
-
-                if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
-                    w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
-                    h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
-                } else {
-                    w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
-                    h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
-                }
-                Image image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
-                imageResource = new ImageResource(image);
-                cacheByAngle.put(originalAngle, imageResource);
-                Graphics g = image.getGraphics();
-                Graphics2D g2d = (Graphics2D) g.create();
-
-                // calculate the center of the icon.
-                int cx = iw / 2;
-                int cy = ih / 2;
-
-                // move the graphics center point to the center of the icon.
-                g2d.translate(w / 2, h / 2);
-
-                // rotate the graphics about the center point of the icon
-                g2d.rotate(Utils.toRadians(originalAngle));
-
-                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
-                g2d.drawImage(img, -cx, -cy, null);
-
-                g2d.dispose();
-                new ImageIcon(image); // load completely
-            }
-            return imageResource.getImageIcon(dimension).getImage();
+                rotatedImg = HiDPISupport.processMRImage(img, img0 -> {
+                    new ImageIcon(img0); // load completely
+                    int iw = img0.getWidth(null);
+                    int ih = img0.getHeight(null);
+                    int w;
+                    int h;
+
+                    if ((originalAngle >= 0 && originalAngle <= 90) || (originalAngle > 180 && originalAngle <= 270)) {
+                        w = (int) (iw * Math.sin(DEGREE_90 - radian) + ih * Math.sin(radian));
+                        h = (int) (iw * Math.sin(radian) + ih * Math.sin(DEGREE_90 - radian));
+                    } else {
+                        w = (int) (ih * Math.sin(DEGREE_90 - radian) + iw * Math.sin(radian));
+                        h = (int) (ih * Math.sin(radian) + iw * Math.sin(DEGREE_90 - radian));
+                    }
+                    Image image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+                    Graphics g = image.getGraphics();
+                    Graphics2D g2d = (Graphics2D) g.create();
+
+                    // calculate the center of the icon.
+                    int cx = iw / 2;
+                    int cy = ih / 2;
+
+                    // move the graphics center point to the center of the icon.
+                    g2d.translate(w / 2, h / 2);
+
+                    // rotate the graphics about the center point of the icon
+                    g2d.rotate(Utils.toRadians(originalAngle));
+
+                    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+                    g2d.drawImage(img0, -cx, -cy, null);
+
+                    g2d.dispose();
+                    new ImageIcon(image); // load completely
+                    return image;
+                });
+                cacheByAngle.put(originalAngle, rotatedImg);
+            }
+            return rotatedImg;
         }
     }
@@ -1412,5 +1433,5 @@
                         double scaleFactor = Math.min(backgroundRealWidth / (double) iconRealWidth, backgroundRealHeight
                                 / (double) iconRealHeight);
-                        BufferedImage iconImage = icon.getImage(false);
+                        Image iconImage = icon.getImage(false);
                         Image scaledIcon;
                         final int scaledWidth;
Index: trunk/src/org/openstreetmap/josm/tools/ImageResource.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageResource.java	(revision 12721)
+++ trunk/src/org/openstreetmap/josm/tools/ImageResource.java	(revision 12722)
@@ -31,5 +31,5 @@
      * Caches the image data for resized versions of the same image.
      */
-    private final Map<Dimension, Image> imgCache = new HashMap<>();
+    private final Map<Dimension, BufferedImage> imgCache = new HashMap<>();
     /**
      * SVG diagram information in case of SVG vector image.
@@ -151,59 +151,84 @@
 
     /**
-     * Get an ImageIcon object for the image of this resource
+     * Get an ImageIcon object for the image of this resource.
+     * <p>
+     * Will return a multi-resolution image by default (if possible).
      * @param  dim The requested dimensions. Use (-1,-1) for the original size and (width, -1)
      *         to set the width, but otherwise scale the image proportionally.
+     * @see #getImageIconBounded(java.awt.Dimension, boolean)
      * @return ImageIcon object for the image of this resource, scaled according to dim
      */
     public ImageIcon getImageIcon(Dimension dim) {
+        return getImageIcon(dim, true);
+    }
+    
+    /**
+     * Get an ImageIcon object for the image of this resource.
+     * @param  dim The requested dimensions. Use (-1,-1) for the original size and (width, -1)
+     *         to set the width, but otherwise scale the image proportionally.
+     * @param  multiResolution If true, return a multi-resolution image
+     * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}.
+     * When running Java 8, this flag has no effect and a plain image will be returned in any case.
+     * @return ImageIcon object for the image of this resource, scaled according to dim
+     * @since 12722
+     */
+    public ImageIcon getImageIcon(Dimension dim, boolean multiResolution) {
         if (dim.width < -1 || dim.width == 0 || dim.height < -1 || dim.height == 0)
             throw new IllegalArgumentException(dim+" is invalid");
-        Image img = imgCache.get(dim);
-        if (img != null) {
+        BufferedImage img = imgCache.get(dim);
+        if (img == null) {
+            if (svg != null) {
+                Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim);
+                img = ImageProvider.createImageFromSvg(svg, realDim);
+                if (img == null) {
+                    return null;
+                }
+            } else {
+                if (baseImage == null) throw new AssertionError();
+
+                int realWidth = GuiSizesHelper.getSizeDpiAdjusted(dim.width);
+                int realHeight = GuiSizesHelper.getSizeDpiAdjusted(dim.height);
+                ImageIcon icon = new ImageIcon(baseImage);
+                if (realWidth == -1 && realHeight == -1) {
+                    realWidth = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth());
+                    realHeight = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight());
+                } else if (realWidth == -1) {
+                    realWidth = Math.max(1, icon.getIconWidth() * realHeight / icon.getIconHeight());
+                } else if (realHeight == -1) {
+                    realHeight = Math.max(1, icon.getIconHeight() * realWidth / icon.getIconWidth());
+                }
+                Image i = icon.getImage().getScaledInstance(realWidth, realHeight, Image.SCALE_SMOOTH);
+                img = new BufferedImage(realWidth, realHeight, BufferedImage.TYPE_INT_ARGB);
+                img.getGraphics().drawImage(i, 0, 0, null);
+            }
+            if (overlayInfo != null) {
+                for (ImageOverlay o : overlayInfo) {
+                    o.process(img);
+                }
+            }
+            if (isDisabled) {
+                //Use default Swing functionality to make icon look disabled by applying grayscaling filter.
+                Icon disabledIcon = UIManager.getLookAndFeel().getDisabledIcon(null, new ImageIcon(img));
+                if (disabledIcon == null) {
+                    return null;
+                }
+
+                //Convert Icon to ImageIcon with BufferedImage inside
+                img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+                disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0);
+            }
+            imgCache.put(dim, img);
+        }
+
+        if (!multiResolution)
             return new ImageIcon(img);
-        }
-        BufferedImage bimg;
-        if (svg != null) {
-            Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim);
-            bimg = ImageProvider.createImageFromSvg(svg, realDim);
-            if (bimg == null) {
-                return null;
-            }
-        } else {
-            if (baseImage == null) throw new AssertionError();
-
-            int realWidth = GuiSizesHelper.getSizeDpiAdjusted(dim.width);
-            int realHeight = GuiSizesHelper.getSizeDpiAdjusted(dim.height);
-            ImageIcon icon = new ImageIcon(baseImage);
-            if (realWidth == -1 && realHeight == -1) {
-                realWidth = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth());
-                realHeight = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight());
-            } else if (realWidth == -1) {
-                realWidth = Math.max(1, icon.getIconWidth() * realHeight / icon.getIconHeight());
-            } else if (realHeight == -1) {
-                realHeight = Math.max(1, icon.getIconHeight() * realWidth / icon.getIconWidth());
-            }
-            Image i = icon.getImage().getScaledInstance(realWidth, realHeight, Image.SCALE_SMOOTH);
-            bimg = new BufferedImage(realWidth, realHeight, BufferedImage.TYPE_INT_ARGB);
-            bimg.getGraphics().drawImage(i, 0, 0, null);
-        }
-        if (overlayInfo != null) {
-            for (ImageOverlay o : overlayInfo) {
-                o.process(bimg);
-            }
-        }
-        if (isDisabled) {
-            //Use default Swing functionality to make icon look disabled by applying grayscaling filter.
-            Icon disabledIcon = UIManager.getLookAndFeel().getDisabledIcon(null, new ImageIcon(bimg));
-            if (disabledIcon == null) {
-                return null;
-            }
-
-            //Convert Icon to ImageIcon with BufferedImage inside
-            bimg = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
-            disabledIcon.paintIcon(new JPanel(), bimg.getGraphics(), 0, 0);
-        }
-        imgCache.put(dim, bimg);
-        return new ImageIcon(bimg);
+        else {
+            try {
+                Image mrImg = HiDPISupport.getMultiResolutionImage(img, this);
+                return new ImageIcon(mrImg);
+            } catch (NoClassDefFoundError e) {
+                return new ImageIcon(img);
+            } 
+        }
     }
 
@@ -211,10 +236,29 @@
      * Get image icon with a certain maximum size. The image is scaled down
      * to fit maximum dimensions. (Keeps aspect ratio)
+     * <p>
+     * Will return a multi-resolution image by default (if possible).
      *
      * @param maxSize The maximum size. One of the dimensions (width or height) can be -1,
      * which means it is not bounded.
      * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
+     * @see #getImageIconBounded(java.awt.Dimension, boolean)
      */
     public ImageIcon getImageIconBounded(Dimension maxSize) {
+        return getImageIconBounded(maxSize, true);
+    }
+
+    /**
+     * Get image icon with a certain maximum size. The image is scaled down
+     * to fit maximum dimensions. (Keeps aspect ratio)
+     *
+     * @param maxSize The maximum size. One of the dimensions (width or height) can be -1,
+     * which means it is not bounded.
+     * @param  multiResolution If true, return a multi-resolution image
+     * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}.
+     * When running Java 8, this flag has no effect and a plain image will be returned in any case.
+     * @return ImageIcon object for the image of this resource, scaled down if needed, according to maxSize
+     * @since 12722
+     */
+    public ImageIcon getImageIconBounded(Dimension maxSize, boolean multiResolution) {
         if (maxSize.width < -1 || maxSize.width == 0 || maxSize.height < -1 || maxSize.height == 0)
             throw new IllegalArgumentException(maxSize+" is invalid");
@@ -240,13 +284,13 @@
 
         if (maxWidth == -1 && maxHeight == -1)
-            return getImageIcon(DEFAULT_DIMENSION);
+            return getImageIcon(DEFAULT_DIMENSION, multiResolution);
         else if (maxWidth == -1)
-            return getImageIcon(new Dimension(-1, maxHeight));
+            return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
         else if (maxHeight == -1)
-            return getImageIcon(new Dimension(maxWidth, -1));
+            return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
         else if (sourceWidth / maxWidth > sourceHeight / maxHeight)
-            return getImageIcon(new Dimension(maxWidth, -1));
+            return getImageIcon(new Dimension(maxWidth, -1), multiResolution);
         else
-            return getImageIcon(new Dimension(-1, maxHeight));
+            return getImageIcon(new Dimension(-1, maxHeight), multiResolution);
    }
 }
