Index: /trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java	(revision 7132)
@@ -22,4 +22,5 @@
 import org.openstreetmap.josm.gui.layer.ImageryLayer;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
 
 public class GeorefImage implements Serializable {
@@ -46,5 +47,4 @@
         return layer.getEastNorth(xIndex+1, yIndex+1);
     }
-
 
     public GeorefImage(WMSLayer layer) {
@@ -57,5 +57,5 @@
             this.yIndex = yIndex;
             this.image = null;
-            flushedResizedCachedInstance();
+            flushResizedCachedInstance();
         }
     }
@@ -65,9 +65,21 @@
     }
 
+    /**
+     * Resets this image to initial state and release all resources being used.
+     * @since 7132
+     */
+    public void resetImage() {
+        if (image != null) {
+            image.flush();
+        }
+        changeImage(null, null);
+    }
+
     public void changeImage(State state, BufferedImage image) {
-        flushedResizedCachedInstance();
+        flushResizedCachedInstance();
         this.image = image;
         this.state = state;
-
+        if (state == null)
+            return;
         switch (state) {
         case FAILED:
@@ -103,5 +115,5 @@
 
     public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) {
-        if (image == null)
+        if (getImage() == null)
             return false;
 
@@ -174,5 +186,5 @@
 
     private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height, boolean alphaChannel) {
-        flushedResizedCachedInstance();
+        flushResizedCachedInstance();
         g.drawImage(
                 img, x, y, x + width, y + height,
@@ -195,5 +207,5 @@
         boolean hasImage = in.readBoolean();
         if (hasImage) {
-            image = (ImageIO.read(ImageIO.createImageInputStream(in)));
+            image = ImageProvider.read(ImageIO.createImageInputStream(in), true, WMSLayer.PROP_ALPHA_CHANNEL.get());
         } else {
             in.readObject(); // read null from input stream
@@ -213,5 +225,5 @@
     }
 
-    public void flushedResizedCachedInstance() {
+    public void flushResizedCachedInstance() {
         if (reImg != null) {
             BufferedImage img = reImg.get();
@@ -223,5 +235,4 @@
     }
 
-
     public BufferedImage getImage() {
         return image;
Index: /trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java	(revision 7132)
@@ -42,4 +42,6 @@
 import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -48,5 +50,5 @@
     //TODO Property for maximum age of tile, automatically remove old tiles
     //TODO Measure time for partially loading from cache, compare with time to download tile. If slower, disable partial cache
-    //TODO Do loading from partial cache and downloading at the same time, don't wait for partical cache to load
+    //TODO Do loading from partial cache and downloading at the same time, don't wait for partial cache to load
 
     private static final StringProperty PROP_CACHE_PATH = new StringProperty("imagery.wms-cache.path", "wms");
@@ -70,4 +72,11 @@
             this.bounds = new ProjectionBounds(east, north, east + tileSize / pixelPerDegree, north + tileSize / pixelPerDegree);
             this.filename = filename;
+        }
+
+        @Override
+        public String toString() {
+            return "CacheEntry [pixelPerDegree=" + pixelPerDegree + ", east=" + east + ", north=" + north + ", bounds="
+                    + bounds + ", filename=" + filename + ", lastUsed=" + lastUsed + ", lastModified=" + lastModified
+                    + "]";
         }
     }
@@ -299,6 +308,5 @@
     }
 
-    private BufferedImage loadImage(ProjectionEntries projectionEntries, CacheEntry entry) throws IOException {
-
+    private BufferedImage loadImage(ProjectionEntries projectionEntries, CacheEntry entry, boolean enforceTransparency) throws IOException {
         synchronized (this) {
             entry.lastUsed = System.currentTimeMillis();
@@ -307,6 +315,11 @@
             if (memCache != null) {
                 BufferedImage result = memCache.get();
-                if (result != null)
-                    return result;
+                if (result != null) {
+                    if (enforceTransparency == ImageProvider.isTransparencyForced(result)) {
+                        return result;
+                    } else if (Main.isDebugEnabled()) {
+                        Main.debug("Skipping "+entry+" from memory cache (transparency enforcement)");
+                    }
+                }
             }
         }
@@ -314,5 +327,5 @@
         try {
             // Reading can't be in synchronized section, it's too slow
-            BufferedImage result = ImageIO.read(getImageFile(projectionEntries, entry));
+            BufferedImage result = ImageProvider.read(getImageFile(projectionEntries, entry), true, enforceTransparency);
             synchronized (this) {
                 if (result == null) {
@@ -354,5 +367,5 @@
         if (entry != null) {
             try {
-                return loadImage(projectionEntries, entry);
+                return loadImage(projectionEntries, entry, WMSLayer.PROP_ALPHA_CHANNEL.get());
             } catch (IOException e) {
                 Main.error("Unable to load file from wms cache");
@@ -364,5 +377,5 @@
     }
 
-    public  BufferedImage getPartialMatch(Projection projection, double pixelPerDegree, double east, double north) {
+    public BufferedImage getPartialMatch(Projection projection, double pixelPerDegree, double east, double north) {
         ProjectionEntries projectionEntries;
         List<CacheEntry> matches;
@@ -390,5 +403,4 @@
                 return null;
 
-
             Collections.sort(matches, new Comparator<CacheEntry>() {
                 @Override
@@ -399,8 +411,9 @@
         }
 
-        //TODO Use alpha layer only when enabled on wms layer
-        BufferedImage result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_4BYTE_ABGR);
+        // Use alpha layer only when enabled on wms layer
+        boolean alpha = WMSLayer.PROP_ALPHA_CHANNEL.get();
+        BufferedImage result = new BufferedImage(tileSize, tileSize,
+                alpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
         Graphics2D g = result.createGraphics();
-
 
         boolean drawAtLeastOnce = false;
@@ -409,5 +422,6 @@
             BufferedImage img;
             try {
-                img = loadImage(projectionEntries, ce);
+                // Enforce transparency only when alpha enabled on wms layer too
+                img = loadImage(projectionEntries, ce, alpha);
                 localCache.put(ce, new SoftReference<>(img));
             } catch (IOException e) {
Index: /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 7132)
@@ -66,4 +66,5 @@
 import org.openstreetmap.josm.io.imagery.WMSGrabber;
 import org.openstreetmap.josm.io.imagery.WMSRequest;
+import org.openstreetmap.josm.tools.ImageProvider;
 
 /**
@@ -813,4 +814,5 @@
             boolean alphaChannel = checkbox.isSelected();
             PROP_ALPHA_CHANNEL.put(alphaChannel);
+            Main.info("WMS Alpha channel changed to "+alphaChannel);
 
             // clear all resized cached instances and repaint the layer
@@ -818,5 +820,11 @@
                 for (int y = 0; y < day; ++y) {
                     GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
-                    img.flushedResizedCachedInstance();
+                    img.flushResizedCachedInstance();
+                    BufferedImage bi = img.getImage();
+                    // Completely erases images for which transparency has been forced,
+                    // or images that should be forced now, as they need to be recreated
+                    if (ImageProvider.isTransparencyForced(bi) || ImageProvider.hasTransparentColor(bi)) {
+                        img.resetImage();
+                    }
                 }
             }
Index: /trunk/src/org/openstreetmap/josm/io/CacheFiles.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/CacheFiles.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/io/CacheFiles.java	(revision 7132)
@@ -15,4 +15,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ImageProvider;
 
 /**
@@ -168,5 +169,5 @@
                 img.setLastModified(System.currentTimeMillis());
             }
-            return ImageIO.read(img);
+            return ImageProvider.read(img, false, false);
         } catch (Exception e) {
             Main.warn(e);
Index: /trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java	(revision 7132)
@@ -44,9 +44,11 @@
                 if (!layer.cache.hasExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth)) {
                     attempt(request);
+                } else if (Main.isDebugEnabled()) {
+                    Main.debug("Ignoring "+request+" (precache only + exact match)");
                 }
-            } else {
-                if(!loadFromCache(request)){
-                    attempt(request);
-                }
+            } else if (!loadFromCache(request)){
+                attempt(request);
+            } else if (Main.isDebugEnabled()) {
+                Main.debug("Ignoring "+request+" (loaded from cache)");
             }
             layer.finishRequest(request);
@@ -70,5 +72,5 @@
                     Main.debug("InterruptedException in "+getClass().getSimpleName()+" during WMS request");
                 }
-                if(i == maxTries) {
+                if (i == maxTries) {
                     Main.error(e);
                     request.finish(State.FAILED, null);
Index: /trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java	(revision 7132)
@@ -12,10 +12,9 @@
 import java.util.StringTokenizer;
 
-import javax.imageio.ImageIO;
-
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -51,5 +50,5 @@
         Utils.copyStream(browser.getInputStream(), baos);
         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
+        BufferedImage img = layer.normalizeImage(ImageProvider.read(bais, true, WMSLayer.PROP_ALPHA_CHANNEL.get()));
         bais.reset();
         layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
Index: /trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java	(revision 7132)
@@ -24,6 +24,4 @@
 import java.util.regex.Pattern;
 
-import javax.imageio.ImageIO;
-
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.coor.EastNorth;
@@ -35,6 +33,6 @@
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.io.ProgressInputStream;
+import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Utils;
-
 
 public class WMSGrabber extends Grabber {
@@ -137,5 +135,6 @@
     @Override
     public boolean loadFromCache(WMSRequest request) {
-        BufferedImage cached = layer.cache.getExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
+        BufferedImage cached = layer.cache.getExactMatch(
+                Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
 
         if (cached != null) {
@@ -143,5 +142,6 @@
             return true;
         } else if (request.isAllowPartialCacheMatch()) {
-            BufferedImage partialMatch = layer.cache.getPartialMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
+            BufferedImage partialMatch = layer.cache.getPartialMatch(
+                    Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
             if (partialMatch != null) {
                 request.finish(State.PARTLY_IN_CACHE, partialMatch);
@@ -179,5 +179,5 @@
 
         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
+        BufferedImage img = layer.normalizeImage(ImageProvider.read(bais, true, WMSLayer.PROP_ALPHA_CHANNEL.get()));
         bais.reset();
         layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
Index: /trunk/src/org/openstreetmap/josm/tools/ImageProvider.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 7131)
+++ /trunk/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 7132)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Dimension;
@@ -13,5 +14,12 @@
 import java.awt.RenderingHints;
 import java.awt.Toolkit;
+import java.awt.Transparency;
 import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.ImageFilter;
+import java.awt.image.ImageProducer;
+import java.awt.image.RGBImageFilter;
+import java.awt.image.WritableRaster;
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -29,4 +37,6 @@
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -37,5 +47,10 @@
 import java.util.zip.ZipFile;
 
+import javax.imageio.IIOException;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.stream.ImageInputStream;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
@@ -46,4 +61,6 @@
 import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.plugins.PluginHandler;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 import org.xml.sax.Attributes;
 import org.xml.sax.EntityResolver;
@@ -90,4 +107,16 @@
         OTHER
     }
+
+    /**
+     * Property set on {@code BufferedImage} returned by {@link #makeImageTransparent}.
+     * @since 7132
+     */
+    public static String PROP_TRANSPARENCY_FORCED = "josm.transparency.forced";
+
+    /**
+     * Property set on {@code BufferedImage} returned by {@link #read} if metadata is required.
+     * @since 7132
+     */
+    public static String PROP_TRANSPARENCY_COLOR = "josm.transparency.color";
 
     protected Collection<String> dirs;
@@ -513,5 +542,5 @@
                 BufferedImage img = null;
                 try {
-                    img = ImageIO.read(Utils.fileToURL(is.getFile()));
+                    img = read(Utils.fileToURL(is.getFile()), false, false);
                 } catch (IOException e) {
                     Main.warn("IOException while reading HTTP image: "+e.getMessage());
@@ -555,5 +584,5 @@
                 } else {
                     try {
-                        return new ImageResource(ImageIO.read(new ByteArrayInputStream(bytes)));
+                        return new ImageResource(read(new ByteArrayInputStream(bytes), false, false));
                     } catch (IOException e) {
                         Main.warn("IOException while reading image: "+e.getMessage());
@@ -625,5 +654,5 @@
                         BufferedImage img = null;
                         try {
-                            img = ImageIO.read(new ByteArrayInputStream(buf));
+                            img = read(new ByteArrayInputStream(buf), false, false);
                         } catch (IOException e) {
                             Main.warn(e);
@@ -650,5 +679,5 @@
             BufferedImage img = null;
             try {
-                img = ImageIO.read(path);
+                img = read(path, false, false);
             } catch (IOException e) {
                 Main.warn(e);
@@ -1004,3 +1033,331 @@
         return svgUniverse;
     }
+
+    /**
+     * Returns a <code>BufferedImage</code> as the result of decoding
+     * a supplied <code>File</code> with an <code>ImageReader</code>
+     * chosen automatically from among those currently registered.
+     * The <code>File</code> is wrapped in an
+     * <code>ImageInputStream</code>.  If no registered
+     * <code>ImageReader</code> claims to be able to read the
+     * resulting stream, <code>null</code> is returned.
+     *
+     * <p> The current cache settings from <code>getUseCache</code>and
+     * <code>getCacheDirectory</code> will be used to control caching in the
+     * <code>ImageInputStream</code> that is created.
+     *
+     * <p> Note that there is no <code>read</code> method that takes a
+     * filename as a <code>String</code>; use this method instead after
+     * creating a <code>File</code> from the filename.
+     *
+     * <p> This method does not attempt to locate
+     * <code>ImageReader</code>s that can read directly from a
+     * <code>File</code>; that may be accomplished using
+     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
+     *
+     * @param input a <code>File</code> to read from.
+     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color, if any.
+     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
+     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
+     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
+     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
+     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
+     *
+     * @return a <code>BufferedImage</code> containing the decoded
+     * contents of the input, or <code>null</code>.
+     *
+     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
+     * @throws IOException if an error occurs during reading.
+     * @since 7132
+     * @see BufferedImage#getProperty
+     */
+    public static BufferedImage read(File input, boolean readMetadata, boolean enforceTransparency) throws IOException {
+        CheckParameterUtil.ensureParameterNotNull(input, "input");
+        if (!input.canRead()) {
+            throw new IIOException("Can't read input file!");
+        }
+
+        ImageInputStream stream = ImageIO.createImageInputStream(input);
+        if (stream == null) {
+            throw new IIOException("Can't create an ImageInputStream!");
+        }
+        BufferedImage bi = read(stream, readMetadata, enforceTransparency);
+        if (bi == null) {
+            stream.close();
+        }
+        return bi;
+    }
+
+    /**
+     * Returns a <code>BufferedImage</code> as the result of decoding
+     * a supplied <code>InputStream</code> with an <code>ImageReader</code>
+     * chosen automatically from among those currently registered.
+     * The <code>InputStream</code> is wrapped in an
+     * <code>ImageInputStream</code>.  If no registered
+     * <code>ImageReader</code> claims to be able to read the
+     * resulting stream, <code>null</code> is returned.
+     *
+     * <p> The current cache settings from <code>getUseCache</code>and
+     * <code>getCacheDirectory</code> will be used to control caching in the
+     * <code>ImageInputStream</code> that is created.
+     *
+     * <p> This method does not attempt to locate
+     * <code>ImageReader</code>s that can read directly from an
+     * <code>InputStream</code>; that may be accomplished using
+     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
+     *
+     * <p> This method <em>does not</em> close the provided
+     * <code>InputStream</code> after the read operation has completed;
+     * it is the responsibility of the caller to close the stream, if desired.
+     *
+     * @param input an <code>InputStream</code> to read from.
+     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
+     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
+     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
+     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
+     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
+     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
+     *
+     * @return a <code>BufferedImage</code> containing the decoded
+     * contents of the input, or <code>null</code>.
+     *
+     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
+     * @throws IOException if an error occurs during reading.
+     * @since 7132
+     */
+    public static BufferedImage read(InputStream input, boolean readMetadata, boolean enforceTransparency) throws IOException {
+        CheckParameterUtil.ensureParameterNotNull(input, "input");
+
+        ImageInputStream stream = ImageIO.createImageInputStream(input);
+        BufferedImage bi = read(stream, readMetadata, enforceTransparency);
+        if (bi == null) {
+            stream.close();
+        }
+        return bi;
+    }
+
+    /**
+     * Returns a <code>BufferedImage</code> as the result of decoding
+     * a supplied <code>URL</code> with an <code>ImageReader</code>
+     * chosen automatically from among those currently registered.  An
+     * <code>InputStream</code> is obtained from the <code>URL</code>,
+     * which is wrapped in an <code>ImageInputStream</code>.  If no
+     * registered <code>ImageReader</code> claims to be able to read
+     * the resulting stream, <code>null</code> is returned.
+     *
+     * <p> The current cache settings from <code>getUseCache</code>and
+     * <code>getCacheDirectory</code> will be used to control caching in the
+     * <code>ImageInputStream</code> that is created.
+     *
+     * <p> This method does not attempt to locate
+     * <code>ImageReader</code>s that can read directly from a
+     * <code>URL</code>; that may be accomplished using
+     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
+     *
+     * @param input a <code>URL</code> to read from.
+     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
+     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
+     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
+     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
+     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
+     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
+     *
+     * @return a <code>BufferedImage</code> containing the decoded
+     * contents of the input, or <code>null</code>.
+     *
+     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
+     * @throws IOException if an error occurs during reading.
+     * @since 7132
+     */
+    public static BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException {
+        CheckParameterUtil.ensureParameterNotNull(input, "input");
+
+        InputStream istream = null;
+        try {
+            istream = input.openStream();
+        } catch (IOException e) {
+            throw new IIOException("Can't get input stream from URL!", e);
+        }
+        ImageInputStream stream = ImageIO.createImageInputStream(istream);
+        BufferedImage bi;
+        try {
+            bi = read(stream, readMetadata, enforceTransparency);
+            if (bi == null) {
+                stream.close();
+            }
+        } finally {
+            istream.close();
+        }
+        return bi;
+    }
+
+    /**
+     * Returns a <code>BufferedImage</code> as the result of decoding
+     * a supplied <code>ImageInputStream</code> with an
+     * <code>ImageReader</code> chosen automatically from among those
+     * currently registered.  If no registered
+     * <code>ImageReader</code> claims to be able to read the stream,
+     * <code>null</code> is returned.
+     *
+     * <p> Unlike most other methods in this class, this method <em>does</em>
+     * close the provided <code>ImageInputStream</code> after the read
+     * operation has completed, unless <code>null</code> is returned,
+     * in which case this method <em>does not</em> close the stream.
+     *
+     * @param stream an <code>ImageInputStream</code> to read from.
+     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
+     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
+     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
+     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
+     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
+     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
+     *
+     * @return a <code>BufferedImage</code> containing the decoded
+     * contents of the input, or <code>null</code>.
+     *
+     * @throws IllegalArgumentException if <code>stream</code> is <code>null</code>.
+     * @throws IOException if an error occurs during reading.
+     * @since 7132
+     */
+    public static BufferedImage read(ImageInputStream stream, boolean readMetadata, boolean enforceTransparency) throws IOException {
+        CheckParameterUtil.ensureParameterNotNull(stream, "stream");
+
+        Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
+        if (!iter.hasNext()) {
+            return null;
+        }
+
+        ImageReader reader = iter.next();
+        ImageReadParam param = reader.getDefaultReadParam();
+        reader.setInput(stream, true, !readMetadata && !enforceTransparency);
+        BufferedImage bi;
+        try {
+            bi = reader.read(0, param);
+            if (bi.getTransparency() != Transparency.TRANSLUCENT && (readMetadata || enforceTransparency)) {
+                Color color = getTransparentColor(reader);
+                if (color != null) {
+                    Hashtable<String, Object> properties = new Hashtable<>(1);
+                    properties.put(PROP_TRANSPARENCY_COLOR, color);
+                    bi = new BufferedImage(bi.getColorModel(), bi.getRaster(), bi.isAlphaPremultiplied(), properties);
+                    if (enforceTransparency) {
+                        if (Main.isDebugEnabled()) {
+                            Main.debug("Enforcing image transparency of "+stream+" for "+color);
+                        }
+                        bi = makeImageTransparent(bi, color);
+                    }
+                }
+            }
+        } finally {
+            reader.dispose();
+            stream.close();
+        }
+        return bi;
+    }
+
+    /**
+     * Returns the {@code TransparentColor} defined in image reader metadata.
+     * @param reader The image reader
+     * @return the {@code TransparentColor} defined in image reader metadata, or {@code null}
+     * @throws IOException if an error occurs during reading
+     * @since 7132
+     * @see <a href="http://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html">javax_imageio_1.0 metadata</a>
+     */
+    public static Color getTransparentColor(ImageReader reader) throws IOException {
+        IIOMetadata metadata = reader.getImageMetadata(0);
+        if (metadata != null) {
+            String[] formats = metadata.getMetadataFormatNames();
+            if (formats != null) {
+                for (String f : formats) {
+                    if ("javax_imageio_1.0".equals(f)) {
+                        Node root = metadata.getAsTree(f);
+                        if (root instanceof Element) {
+                            Node item = ((Element)root).getElementsByTagName("TransparentColor").item(0);
+                            if (item instanceof Element) {
+                                String value = ((Element)item).getAttribute("value");
+                                String[] s = value.split(" ");
+                                if (s.length == 3) {
+                                    int[] rgb = new int[3];
+                                    try {
+                                        for (int i = 0; i<3; i++) {
+                                            rgb[i] = Integer.parseInt(s[i]);
+                                        }
+                                        return new Color(rgb[0], rgb[1], rgb[2]);
+                                    } catch (IllegalArgumentException e) {
+                                        Main.error(e);
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a transparent version of the given image, based on the given transparent color.
+     * @param bi The image to convert
+     * @param color The transparent color
+     * @return The same image as {@code bi} where all pixels of the given color are transparent.
+     * This resulting image has also the special property {@link #PROP_TRANSPARENCY_FORCED} set to {@code color}
+     * @since 7132
+     * @see BufferedImage#getProperty
+     * @see #isTransparencyForced
+     */
+    public static BufferedImage makeImageTransparent(BufferedImage bi, Color color) {
+        // the color we are looking for. Alpha bits are set to opaque
+        final int markerRGB = color.getRGB() | 0xFFFFFFFF;
+        ImageFilter filter = new RGBImageFilter() {
+            @Override
+            public int filterRGB(int x, int y, int rgb) {
+                if ((rgb | 0xFF000000) == markerRGB) {
+                   // Mark the alpha bits as zero - transparent
+                   return 0x00FFFFFF & rgb;
+                } else {
+                   return rgb;
+                }
+            }
+        };
+        ImageProducer ip = new FilteredImageSource(bi.getSource(), filter);
+        Image img = Toolkit.getDefaultToolkit().createImage(ip);
+        ColorModel colorModel = ColorModel.getRGBdefault();
+        WritableRaster raster = colorModel.createCompatibleWritableRaster(img.getWidth(null), img.getHeight(null));
+        String[] names = bi.getPropertyNames();
+        Hashtable<String, Object> properties = new Hashtable<>(1 + (names != null ? names.length : 0));
+        if (names != null) {
+            for (String name : names) {
+                properties.put(name, bi.getProperty(name));
+            }
+        }
+        properties.put(PROP_TRANSPARENCY_FORCED, Boolean.TRUE);
+        BufferedImage result = new BufferedImage(colorModel, raster, false, properties);
+        Graphics2D g2 = result.createGraphics();
+        g2.drawImage(img, 0, 0, null);
+        g2.dispose();
+        return result;
+    }
+
+    /**
+     * Determines if the transparency of the given {@code BufferedImage} has been enforced by a previous call to {@link #makeImageTransparent}.
+     * @param bi The {@code BufferedImage} to test
+     * @return {@code true} if the transparency of {@code bi} has been enforced by a previous call to {@code makeImageTransparent}.
+     * @since 7132
+     * @see #makeImageTransparent
+     */
+    public static boolean isTransparencyForced(BufferedImage bi) {
+        return bi != null && !bi.getProperty(PROP_TRANSPARENCY_FORCED).equals(Image.UndefinedProperty);
+    }
+
+    /**
+     * Determines if the given {@code BufferedImage} has a transparent color determiend by a previous call to {@link #read}.
+     * @param bi The {@code BufferedImage} to test
+     * @return {@code true} if {@code bi} has a transparent color determined by a previous call to {@code read}.
+     * @since 7132
+     * @see #read
+     */
+    public static boolean hasTransparentColor(BufferedImage bi) {
+        return bi != null && !bi.getProperty(PROP_TRANSPARENCY_COLOR).equals(Image.UndefinedProperty);
+    }
 }
Index: /trunk/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java	(revision 7132)
+++ /trunk/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java	(revision 7132)
@@ -0,0 +1,31 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.awt.Transparency;
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Test;
+import org.openstreetmap.josm.TestUtils;
+
+/**
+ * Unit tests of {@link ImageProvider} class.
+ */
+public class ImageProviderTest {
+
+    /**
+     * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/9984">#9984</a>
+     * @throws IOException if an error occurs during reading
+     */
+    @Test
+    public void testTicket9984() throws IOException {
+        File file = new File(TestUtils.getTestDataRoot()+"regress/9984/tile.png");
+        assertThat(ImageProvider.read(file, true, true).getTransparency(), is(Transparency.TRANSLUCENT));
+        assertThat(ImageProvider.read(file, false, true).getTransparency(), is(Transparency.TRANSLUCENT));
+        assertThat(ImageProvider.read(file, false, false).getTransparency(), is(Transparency.OPAQUE));
+        assertThat(ImageProvider.read(file, true, false).getTransparency(), is(Transparency.OPAQUE));
+    }
+}
