Index: trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java	(revision 8095)
+++ trunk/src/org/openstreetmap/josm/tools/ImageOverlay.java	(revision 8095)
@@ -0,0 +1,103 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+import javax.swing.ImageIcon;
+
+/** class to describe how image overlay
+ * @since 8095
+ */
+public class ImageOverlay {
+    /** the image ressource to use as overlay */
+    public ImageProvider image;
+    /** offset of the image from left border, values between 0 and 1 */
+    private double offsetLeft;
+    /** offset of the image from top border, values between 0 and 1 */
+    private double offsetRight;
+    /** offset of the image from right border, values between 0 and 1*/
+    private double offsetTop;
+    /** offset of the image from bottom border, values between 0 and 1 */
+    private double offsetBottom;
+    
+    /**
+     * Create an overlay info. All values are relative sizes between 0 and 1. Size of the image
+     * is the result of the difference between left/right and top/bottom.
+     *
+     * @param image imager provider for the overlay icon
+     * @param offsetLeft offset of the image from left border, values between 0 and 1, -1 for auto-calculation
+     * @param offsetTop offset of the image from top border, values between 0 and 1, -1 for auto-calculation
+     * @param offsetRight offset of the image from right border, values between 0 and 1, -1 for auto-calculation
+     * @param offsetBottom offset of the image from bottom border, values between 0 and 1, -1 for auto-calculation
+     * @since 8095
+     */
+    public ImageOverlay(ImageProvider image, double offsetLeft, double offsetTop, double offsetRight, double offsetBottom) {
+        this.image = image;
+        this.offsetLeft = offsetLeft;
+        this.offsetTop = offsetTop;
+        this.offsetRight = offsetRight;
+        this.offsetBottom = offsetBottom;
+    }
+    
+    /**
+     * Create an overlay in southeast corner. All values are relative sizes between 0 and 1.
+     * Size of the image is the result of the difference between left/right and top/bottom.
+     * Right and bottom values are set to 1.
+     *
+     * @param image imager provider for the overlay icon
+     * @see #ImageOverlay(ImageProvider, double, double, double, double)
+     * @since 8095
+     */
+    public ImageOverlay(ImageProvider image) {
+        this.image = image;
+        this.offsetLeft = -1.0;
+        this.offsetTop = -1.0;
+        this.offsetRight = 1.0;
+        this.offsetBottom = 1.0;
+    }
+
+    /**
+     * Handle overlay. The image passed as argument is modified!
+     *
+     * @param ground the base image for the overlay (gets modified!)
+     * @return the modified image (same as argument)
+     * @since 8095
+     */
+    public BufferedImage apply(BufferedImage ground) {
+        /* get base dimensions for calculation */
+        int w = ground.getWidth();
+        int h = ground.getHeight();
+        int width = -1;
+        int height = -1;
+        if (offsetRight > 0 && offsetLeft > 0) {
+            width = new Double(w*(offsetRight-offsetLeft)).intValue();
+        }
+        if (offsetTop > 0 && offsetBottom > 0) {
+            width = new Double(h*(offsetBottom-offsetTop)).intValue();
+        }
+        ImageIcon overlay;
+        if(width != -1 || height != -1) {
+            /* Don't modify ImageProvider, but apply maximum size, probably cloning the ImageProvider
+               would be a better approach. */
+            overlay = image.getResource().getImageIconBounded(new Dimension(width, height));
+        } else {
+            overlay = image.get();
+        }
+        int x, y;
+        if (width == -1 && offsetLeft < 0) {
+            x = new Double(w*offsetRight).intValue() - overlay.getIconWidth();
+        } else {
+            x = new Double(w*offsetLeft).intValue();
+        }
+        if (height == -1 && offsetTop < 0) {
+            y = new Double(h*offsetBottom).intValue() - overlay.getIconHeight();
+        } else {
+            y = new Double(h*offsetTop).intValue();
+        }
+        overlay.paintIcon(null, ground.getGraphics(), x, y);
+        return ground;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 8093)
+++ trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 8095)
@@ -39,4 +39,6 @@
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -80,5 +82,5 @@
  * How to use:
  *
- * <code>ImageIcon icon = new ImageProvider(name).setMaxWidth(24).setMaxHeight(24).get();</code>
+ * <code>ImageIcon icon = new ImageProvider(name).setMaxSize(ImageSizes.MAP).get();</code>
  * (there are more options, see below)
  *
@@ -127,9 +129,13 @@
         /** LARGE_ICON_KEY value of on Action */
         LARGEICON,
-        /** MAP icon */
+        /** map icon */
         MAP,
-        /** MAP icon maximum size */
+        /** map icon maximum size */
         MAPMAX,
-        /** MENU icon size */
+        /** menu icon size */
+        CURSOR,
+        /** Cursor overlay icon size */
+        CURSOROVERLAY,
+        /** Cursor icon size */
         MENU,
     }
@@ -147,17 +153,32 @@
     public static String PROP_TRANSPARENCY_COLOR = "josm.transparency.color";
 
+    /** directories in which images are searched */
     protected Collection<String> dirs;
+    /** caching identifier */
     protected String id;
+    /** sub directory the image can be found in */
     protected String subdir;
+    /** image file name */
     protected String name;
+    /** archive file to take image from */
     protected File archive;
+    /** directory inside the archive */
     protected String inArchiveDir;
+    /** width of the resulting image, -1 when original image data should be used */
     protected int width = -1;
+    /** height of the resulting image, -1 when original image data should be used */
     protected int height = -1;
+    /** maximum width of the resulting image, -1 for no restriction */
     protected int maxWidth = -1;
+    /** maximum height of the resulting image, -1 for no restriction */
     protected int maxHeight = -1;
+    /** In case of errors do not throw exception but return <code>null</code> for missing image */
     protected boolean optional;
+    /** <code>true</code> if warnings should be suppressed */
     protected boolean suppressWarnings;
+    /** list of class loaders to take images from */
     protected Collection<ClassLoader> additionalClassLoaders;
+    /** ordered list of overlay images */
+    protected List<ImageOverlay> overlayInfo = null;
 
     private static SVGUniverse svgUniverse;
@@ -200,7 +221,7 @@
     /**
      * Constructs a new {@code ImageProvider} from a filename in a given directory.
-     * @param subdir    subdirectory the image lies in
-     * @param name      the name of the image. If it does not end with '.png' or '.svg',
-     *                  both extensions are tried.
+     * @param subdir subdirectory the image lies in
+     * @param name the name of the image. If it does not end with '.png' or '.svg',
+     * both extensions are tried.
      */
     public ImageProvider(String subdir, String name) {
@@ -211,6 +232,6 @@
     /**
      * Constructs a new {@code ImageProvider} from a filename.
-     * @param name      the name of the image. If it does not end with '.png' or '.svg',
-     *                  both extensions are tried.
+     * @param name the name of the image. If it does not end with '.png' or '.svg',
+     * both extensions are tried.
      */
     public ImageProvider(String name) {
@@ -232,4 +253,5 @@
      * If name starts with <tt>http://</tt> Id is not used for the cache.
      * (A URL is unique anyway.)
+     * @param id the id for the cached image
      * @return the current object, for convenience
      */
@@ -256,8 +278,24 @@
      *
      * (optional)
+     * @param inArchiveDir path inside the archive
      * @return the current object, for convenience
      */
     public ImageProvider setInArchiveDir(String inArchiveDir) {
         this.inArchiveDir = inArchiveDir;
+        return this;
+    }
+
+    /**
+     * Add an overlay over the image. Multiple overlays are possible.
+     *
+     * @param overlay overlay image and placement specification
+     * @return the current object, for convenience
+     * @since 8095
+     */
+    public ImageProvider addOverlay(ImageOverlay overlay) {
+        if (overlayInfo == null) {
+            overlayInfo = new LinkedList<ImageOverlay>();
+        }
+        overlayInfo.add(overlay);
         return this;
     }
@@ -277,4 +315,6 @@
         case MENU: /* MENU is SMALLICON - only provided in case of future changes */
         case SMALLICON: sizeval = Main.pref.getInteger("iconsize.smallicon", 16); break;
+        case CURSOROVERLAY: /* same as cursor - only provided in case of future changes */
+        case CURSOR: sizeval = Main.pref.getInteger("iconsize.cursor", 32); break;
         default: sizeval = Main.pref.getInteger("iconsize.default", 24); break;
         }
@@ -288,4 +328,5 @@
      * The width part of the dimension can be -1. Then it will only set the height but
      * keep the aspect ratio. (And the other way around.)
+     * @param size final dimensions of the image
      * @return the current object, for convenience
      */
@@ -300,4 +341,5 @@
      *
      * If not specified, the original size of the image is used.
+     * @param size final dimensions of the image
      * @return the current object, for convenience
      * @since 7687
@@ -308,4 +350,6 @@
 
     /**
+     * Set image width
+     * @param width final width of the image
      * @see #setSize
      * @return the current object, for convenience
@@ -317,4 +361,6 @@
 
     /**
+     * Set image height
+     * @param height final height of the image
      * @see #setSize
      * @return the current object, for convenience
@@ -332,4 +378,5 @@
      *
      * 'size' and 'maxSize' are not compatible, you should set only one of them.
+     * @param maxSize maximum image size
      * @return the current object, for convenience
      */
@@ -347,4 +394,5 @@
      *
      * 'size' and 'maxSize' are not compatible, you should set only one of them.
+     * @param size maximum image size
      * @return the current object, for convenience
      * @since 7687
@@ -356,4 +404,5 @@
     /**
      * Convenience method, see {@link #setMaxSize(Dimension)}.
+     * @param maxSize maximum image size
      * @return the current object, for convenience
      */
@@ -363,4 +412,6 @@
 
     /**
+     * Limit the maximum width of the image.
+     * @param maxWidth maximum image width
      * @see #setMaxSize
      * @return the current object, for convenience
@@ -372,4 +423,6 @@
 
     /**
+     * Limit the maximum height of the image.
+     * @param maxHeight maximum image height
      * @see #setMaxSize
      * @return the current object, for convenience
@@ -398,4 +451,5 @@
      *
      * In combination with setOptional(true);
+     * @param suppressWarnings if <code>true</code> warnings are suppressed
      * @return the current object, for convenience
      */
@@ -407,4 +461,5 @@
     /**
      * Add a collection of additional class loaders to search image for.
+     * @param additionalClassLoaders class loaders to add to the internal list
      * @return the current object, for convenience
      */
@@ -430,4 +485,5 @@
     /**
      * Execute the image request.
+     *
      * @return the requested image or null if the request failed
      * @since 7693
@@ -446,4 +502,7 @@
             }
         }
+        if (overlayInfo != null) {
+            ir = new ImageResource(ir, overlayInfo);
+        }
         return ir;
     }
@@ -539,4 +598,7 @@
 
     /**
+     * Load an image with a given file name, but do not throw an exception
+     * when the image cannot be found.
+     *
      * @param name The icon name (base name with or without '.png' or '.svg' extension)
      * @return the requested image or null if the request failed
@@ -554,4 +616,10 @@
             "^data:([a-zA-Z]+/[a-zA-Z+]+)?(;base64)?,(.+)$");
 
+    /**
+     * Internal implementation of the image request.
+     *
+     * @param additionalClassLoaders the list of class loaders to use
+     * @return the requested image or null if the request failed
+     */
     private ImageResource getIfAvailableImpl(Collection<ClassLoader> additionalClassLoaders) {
         synchronized (cache) {
@@ -661,4 +729,11 @@
     }
 
+    /**
+     * Internal implementation of the image request for URL's.
+     *
+     * @param url URL of the image
+     * @param type data type of the image
+     * @return the requested image or null if the request failed
+     */
     private static ImageResource getIfAvailableHttp(String url, ImageType type) {
         CachedFile cf = new CachedFile(url)
@@ -689,4 +764,10 @@
     }
 
+    /**
+     * Internal implementation of the image request for inline images (<b>data:</b> urls).
+     *
+     * @param url the data URL for image extraction
+     * @return the requested image or null if the request failed
+     */
     private static ImageResource getIfAvailableDataUrl(String url) {
         try {
@@ -737,4 +818,11 @@
     }
 
+    /**
+     * Internal implementation of the image request for wiki images.
+     *
+     * @param name image file name
+     * @param type data type of the image
+     * @return the requested image or null if the request failed
+     */
     private static ImageResource getIfAvailableWiki(String name, ImageType type) {
         final Collection<String> defaultBaseUrls = Arrays.asList(
@@ -767,4 +855,13 @@
     }
 
+    /**
+     * Internal implementation of the image request for images in Zip archives.
+     *
+     * @param fullName image file name
+     * @param archive the archive to get image from
+     * @param inArchiveDir directory of the image inside the archive or <code>null</code>
+     * @param type data type of the image
+     * @return the requested image or null if the request failed
+     */
     private static ImageResource getIfAvailableZip(String fullName, File archive, String inArchiveDir, ImageType type) {
         try (ZipFile zipFile = new ZipFile(archive, StandardCharsets.UTF_8)) {
@@ -814,4 +911,11 @@
     }
 
+    /**
+     * Internal implementation of the image request for local images.
+     *
+     * @param path image file path
+     * @param type data type of the image
+     * @return the requested image or null if the request failed
+     */
     private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
         switch (type) {
@@ -929,5 +1033,9 @@
 
     /**
-     * Reads the wiki page on a certain file in html format in order to find the real image URL.
+     * Return URL of wiki image for an Wiki image described by and Wiki file info page
+     *
+     * @param base base URL for Wiki image
+     * @param fn filename of the Wiki image
+     * @return image URL for a Wiki image or null in case of error
      */
     private static String getImgUrlFromWikiInfoPage(final String base, final String fn) {
@@ -977,5 +1085,7 @@
         ImageIcon img = get("cursor", name);
         if (overlay != null) {
-            img = overlay(img, ImageProvider.get("cursor/modifier/" + overlay), OverlayPosition.SOUTHEAST);
+            img = new ImageProvider("cursor", name).setMaxSize(ImageSizes.CURSOR)
+                .addOverlay(new ImageOverlay(new ImageProvider("cursor/modifier/" + overlay)
+                    .setMaxSize(ImageSizes.CURSOROVERLAY))).get();
         }
         if (GraphicsEnvironment.isHeadless()) {
Index: trunk/src/org/openstreetmap/josm/tools/ImageResource.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ImageResource.java	(revision 8093)
+++ trunk/src/org/openstreetmap/josm/tools/ImageResource.java	(revision 8095)
@@ -6,4 +6,5 @@
 import java.awt.image.BufferedImage;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
@@ -18,6 +19,6 @@
  * It can be backed by a svg or raster image.
  *
- * In the first case, 'svg' is not null and in the latter case, 'imgCache' has
- * at least one entry for the key DEFAULT_DIMENSION.
+ * In the first case, <code>svg</code> is not <code>null</code> and in the latter case,
+ * <code>baseImage</code> is not <code>null</code>.
  * @since 4271
  */
@@ -28,4 +29,7 @@
      */
     private Map<Dimension, Image> imgCache = new HashMap<>();
+    /**
+     * SVG diagram information in case of SVG vector image.
+     */
     private SVGDiagram svg;
     /**
@@ -33,4 +37,9 @@
      */
     public static final Dimension DEFAULT_DIMENSION = new Dimension(-1, -1);
+    /**
+     * ordered list of overlay images
+     */
+    protected List<ImageOverlay> overlayInfo = null;
+    private Image baseImage = null;
 
     /**
@@ -40,4 +49,5 @@
     public ImageResource(Image img) {
         CheckParameterUtil.ensureParameterNotNull(img);
+        this.baseImage = img;
         imgCache.put(DEFAULT_DIMENSION, img);
     }
@@ -50,4 +60,16 @@
         CheckParameterUtil.ensureParameterNotNull(svg);
         this.svg = svg;
+    }
+
+    /**
+     * Constructs a new {@code ImageResource} from another one and sets overlays.
+     * @param res the existing resource
+     * @param overlayInfo the overlay to apply
+     * @since 8095
+     */
+    public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
+        this.svg = res.svg;
+        this.baseImage = res.baseImage;
+        this.overlayInfo = overlayInfo;
     }
 
@@ -87,18 +109,25 @@
         }
         if (svg != null) {
-            img = ImageProvider.createImageFromSvg(svg, dim);
-            if (img == null) {
+            BufferedImage bimg = ImageProvider.createImageFromSvg(svg, dim);
+            if (bimg == null) {
                 return null;
             }
-            imgCache.put(dim, img);
-            return new ImageIcon(img);
+            if (overlayInfo != null) {
+                for (ImageOverlay o : overlayInfo) {
+                    o.apply(bimg);
+                }
+            }
+            imgCache.put(dim, bimg);
+            return new ImageIcon(bimg);
         } else {
-            Image base = imgCache.get(DEFAULT_DIMENSION);
-            if (base == null) throw new AssertionError();
+            if (baseImage == null) throw new AssertionError();
 
             int width = dim.width;
             int height = dim.height;
-            ImageIcon icon = new ImageIcon(base);
-            if (width == -1) {
+            ImageIcon icon = new ImageIcon(baseImage);
+            if (width == -1 && height == -1) {
+                width = icon.getIconWidth();
+                height = icon.getIconHeight();
+            } else if (width == -1) {
                 width = Math.max(1, icon.getIconWidth() * height / icon.getIconHeight());
             } else if (height == -1) {
@@ -106,8 +135,13 @@
             }
             Image i = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
-            img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-            img.getGraphics().drawImage(i, 0, 0, null);
-            imgCache.put(dim, img);
-            return new ImageIcon(img);
+            BufferedImage bimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            bimg.getGraphics().drawImage(i, 0, 0, null);
+            if (overlayInfo != null) {
+                for (ImageOverlay o : overlayInfo) {
+                    o.apply(bimg);
+                }
+            }
+            imgCache.put(dim, bimg);
+            return new ImageIcon(bimg);
         }
     }
@@ -117,5 +151,5 @@
      * to fit maximum dimensions. (Keeps aspect ratio)
      *
-     * @param maxSize The maximum size. One of the dimensions (widht or height) can be -1,
+     * @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
@@ -130,7 +164,6 @@
             realHeight = svg.getHeight();
         } else {
-            Image base = imgCache.get(DEFAULT_DIMENSION);
-            if (base == null) throw new AssertionError();
-            ImageIcon icon = new ImageIcon(base);
+            if (baseImage == null) throw new AssertionError();
+            ImageIcon icon = new ImageIcon(baseImage);
             realWidth = icon.getIconWidth();
             realHeight = icon.getIconHeight();
@@ -152,9 +185,8 @@
         else if (maxHeight == -1)
             return getImageIcon(new Dimension(maxWidth, -1));
+        else if (realWidth / maxWidth > realHeight / maxHeight)
+            return getImageIcon(new Dimension(maxWidth, -1));
         else
-            if (realWidth / maxWidth > realHeight / maxHeight)
-                return getImageIcon(new Dimension(maxWidth, -1));
-            else
-                return getImageIcon(new Dimension(-1, maxHeight));
+            return getImageIcon(new Dimension(-1, maxHeight));
    }
 }
