Changeset 7132 in josm


Ignore:
Timestamp:
2014-05-15T03:15:28+02:00 (10 years ago)
Author:
Don-vip
Message:

fix #9984 - Add support for WMS tiles defining a transparent color in RGB space (tRNS PNG chunk for example), instead of a proper alpha channel. Surprisingly, Java does not support that out of the box, ImageIO.read always returns opaque images. Allows to switch between this mode and standard mode using WMS layer contextual entry "Use Alpha Channel", for consistency with images defining an alpha channel. Does not impact other images than WMS tiles.

Location:
trunk
Files:
3 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java

    r7025 r7132  
    2222import org.openstreetmap.josm.gui.layer.ImageryLayer;
    2323import org.openstreetmap.josm.gui.layer.WMSLayer;
     24import org.openstreetmap.josm.tools.ImageProvider;
    2425
    2526public class GeorefImage implements Serializable {
     
    4647        return layer.getEastNorth(xIndex+1, yIndex+1);
    4748    }
    48 
    4949
    5050    public GeorefImage(WMSLayer layer) {
     
    5757            this.yIndex = yIndex;
    5858            this.image = null;
    59             flushedResizedCachedInstance();
     59            flushResizedCachedInstance();
    6060        }
    6161    }
     
    6565    }
    6666
     67    /**
     68     * Resets this image to initial state and release all resources being used.
     69     * @since 7132
     70     */
     71    public void resetImage() {
     72        if (image != null) {
     73            image.flush();
     74        }
     75        changeImage(null, null);
     76    }
     77
    6778    public void changeImage(State state, BufferedImage image) {
    68         flushedResizedCachedInstance();
     79        flushResizedCachedInstance();
    6980        this.image = image;
    7081        this.state = state;
    71 
     82        if (state == null)
     83            return;
    7284        switch (state) {
    7385        case FAILED:
     
    103115
    104116    public boolean paint(Graphics g, NavigatableComponent nc, int xIndex, int yIndex, int leftEdge, int bottomEdge) {
    105         if (image == null)
     117        if (getImage() == null)
    106118            return false;
    107119
     
    174186
    175187    private void fallbackDraw(Graphics g, Image img, int x, int y, int width, int height, boolean alphaChannel) {
    176         flushedResizedCachedInstance();
     188        flushResizedCachedInstance();
    177189        g.drawImage(
    178190                img, x, y, x + width, y + height,
     
    195207        boolean hasImage = in.readBoolean();
    196208        if (hasImage) {
    197             image = (ImageIO.read(ImageIO.createImageInputStream(in)));
     209            image = ImageProvider.read(ImageIO.createImageInputStream(in), true, WMSLayer.PROP_ALPHA_CHANNEL.get());
    198210        } else {
    199211            in.readObject(); // read null from input stream
     
    213225    }
    214226
    215     public void flushedResizedCachedInstance() {
     227    public void flushResizedCachedInstance() {
    216228        if (reImg != null) {
    217229            BufferedImage img = reImg.get();
     
    223235    }
    224236
    225 
    226237    public BufferedImage getImage() {
    227238        return image;
  • trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java

    r7033 r7132  
    4242import org.openstreetmap.josm.data.preferences.StringProperty;
    4343import org.openstreetmap.josm.data.projection.Projection;
     44import org.openstreetmap.josm.gui.layer.WMSLayer;
     45import org.openstreetmap.josm.tools.ImageProvider;
    4446import org.openstreetmap.josm.tools.Utils;
    4547
     
    4850    //TODO Property for maximum age of tile, automatically remove old tiles
    4951    //TODO Measure time for partially loading from cache, compare with time to download tile. If slower, disable partial cache
    50     //TODO Do loading from partial cache and downloading at the same time, don't wait for partical cache to load
     52    //TODO Do loading from partial cache and downloading at the same time, don't wait for partial cache to load
    5153
    5254    private static final StringProperty PROP_CACHE_PATH = new StringProperty("imagery.wms-cache.path", "wms");
     
    7072            this.bounds = new ProjectionBounds(east, north, east + tileSize / pixelPerDegree, north + tileSize / pixelPerDegree);
    7173            this.filename = filename;
     74        }
     75
     76        @Override
     77        public String toString() {
     78            return "CacheEntry [pixelPerDegree=" + pixelPerDegree + ", east=" + east + ", north=" + north + ", bounds="
     79                    + bounds + ", filename=" + filename + ", lastUsed=" + lastUsed + ", lastModified=" + lastModified
     80                    + "]";
    7281        }
    7382    }
     
    299308    }
    300309
    301     private BufferedImage loadImage(ProjectionEntries projectionEntries, CacheEntry entry) throws IOException {
    302 
     310    private BufferedImage loadImage(ProjectionEntries projectionEntries, CacheEntry entry, boolean enforceTransparency) throws IOException {
    303311        synchronized (this) {
    304312            entry.lastUsed = System.currentTimeMillis();
     
    307315            if (memCache != null) {
    308316                BufferedImage result = memCache.get();
    309                 if (result != null)
    310                     return result;
     317                if (result != null) {
     318                    if (enforceTransparency == ImageProvider.isTransparencyForced(result)) {
     319                        return result;
     320                    } else if (Main.isDebugEnabled()) {
     321                        Main.debug("Skipping "+entry+" from memory cache (transparency enforcement)");
     322                    }
     323                }
    311324            }
    312325        }
     
    314327        try {
    315328            // Reading can't be in synchronized section, it's too slow
    316             BufferedImage result = ImageIO.read(getImageFile(projectionEntries, entry));
     329            BufferedImage result = ImageProvider.read(getImageFile(projectionEntries, entry), true, enforceTransparency);
    317330            synchronized (this) {
    318331                if (result == null) {
     
    354367        if (entry != null) {
    355368            try {
    356                 return loadImage(projectionEntries, entry);
     369                return loadImage(projectionEntries, entry, WMSLayer.PROP_ALPHA_CHANNEL.get());
    357370            } catch (IOException e) {
    358371                Main.error("Unable to load file from wms cache");
     
    364377    }
    365378
    366     public  BufferedImage getPartialMatch(Projection projection, double pixelPerDegree, double east, double north) {
     379    public BufferedImage getPartialMatch(Projection projection, double pixelPerDegree, double east, double north) {
    367380        ProjectionEntries projectionEntries;
    368381        List<CacheEntry> matches;
     
    390403                return null;
    391404
    392 
    393405            Collections.sort(matches, new Comparator<CacheEntry>() {
    394406                @Override
     
    399411        }
    400412
    401         //TODO Use alpha layer only when enabled on wms layer
    402         BufferedImage result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_4BYTE_ABGR);
     413        // Use alpha layer only when enabled on wms layer
     414        boolean alpha = WMSLayer.PROP_ALPHA_CHANNEL.get();
     415        BufferedImage result = new BufferedImage(tileSize, tileSize,
     416                alpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
    403417        Graphics2D g = result.createGraphics();
    404 
    405418
    406419        boolean drawAtLeastOnce = false;
     
    409422            BufferedImage img;
    410423            try {
    411                 img = loadImage(projectionEntries, ce);
     424                // Enforce transparency only when alpha enabled on wms layer too
     425                img = loadImage(projectionEntries, ce, alpha);
    412426                localCache.put(ce, new SoftReference<>(img));
    413427            } catch (IOException e) {
  • trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java

    r7119 r7132  
    6666import org.openstreetmap.josm.io.imagery.WMSGrabber;
    6767import org.openstreetmap.josm.io.imagery.WMSRequest;
     68import org.openstreetmap.josm.tools.ImageProvider;
    6869
    6970/**
     
    813814            boolean alphaChannel = checkbox.isSelected();
    814815            PROP_ALPHA_CHANNEL.put(alphaChannel);
     816            Main.info("WMS Alpha channel changed to "+alphaChannel);
    815817
    816818            // clear all resized cached instances and repaint the layer
     
    818820                for (int y = 0; y < day; ++y) {
    819821                    GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
    820                     img.flushedResizedCachedInstance();
     822                    img.flushResizedCachedInstance();
     823                    BufferedImage bi = img.getImage();
     824                    // Completely erases images for which transparency has been forced,
     825                    // or images that should be forced now, as they need to be recreated
     826                    if (ImageProvider.isTransparencyForced(bi) || ImageProvider.hasTransparentColor(bi)) {
     827                        img.resetImage();
     828                    }
    821829                }
    822830            }
  • trunk/src/org/openstreetmap/josm/io/CacheFiles.java

    r7082 r7132  
    1515
    1616import org.openstreetmap.josm.Main;
     17import org.openstreetmap.josm.tools.ImageProvider;
    1718
    1819/**
     
    168169                img.setLastModified(System.currentTimeMillis());
    169170            }
    170             return ImageIO.read(img);
     171            return ImageProvider.read(img, false, false);
    171172        } catch (Exception e) {
    172173            Main.warn(e);
  • trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java

    r6883 r7132  
    4444                if (!layer.cache.hasExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth)) {
    4545                    attempt(request);
     46                } else if (Main.isDebugEnabled()) {
     47                    Main.debug("Ignoring "+request+" (precache only + exact match)");
    4648                }
    47             } else {
    48                 if(!loadFromCache(request)){
    49                     attempt(request);
    50                 }
     49            } else if (!loadFromCache(request)){
     50                attempt(request);
     51            } else if (Main.isDebugEnabled()) {
     52                Main.debug("Ignoring "+request+" (loaded from cache)");
    5153            }
    5254            layer.finishRequest(request);
     
    7072                    Main.debug("InterruptedException in "+getClass().getSimpleName()+" during WMS request");
    7173                }
    72                 if(i == maxTries) {
     74                if (i == maxTries) {
    7375                    Main.error(e);
    7476                    request.finish(State.FAILED, null);
  • trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java

    r7005 r7132  
    1212import java.util.StringTokenizer;
    1313
    14 import javax.imageio.ImageIO;
    15 
    1614import org.openstreetmap.josm.Main;
    1715import org.openstreetmap.josm.data.preferences.StringProperty;
    1816import org.openstreetmap.josm.gui.MapView;
    1917import org.openstreetmap.josm.gui.layer.WMSLayer;
     18import org.openstreetmap.josm.tools.ImageProvider;
    2019import org.openstreetmap.josm.tools.Utils;
    2120
     
    5150        Utils.copyStream(browser.getInputStream(), baos);
    5251        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    53         BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
     52        BufferedImage img = layer.normalizeImage(ImageProvider.read(bais, true, WMSLayer.PROP_ALPHA_CHANNEL.get()));
    5453        bais.reset();
    5554        layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
  • trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java

    r7082 r7132  
    2424import java.util.regex.Pattern;
    2525
    26 import javax.imageio.ImageIO;
    27 
    2826import org.openstreetmap.josm.Main;
    2927import org.openstreetmap.josm.data.coor.EastNorth;
     
    3533import org.openstreetmap.josm.io.OsmTransferException;
    3634import org.openstreetmap.josm.io.ProgressInputStream;
     35import org.openstreetmap.josm.tools.ImageProvider;
    3736import org.openstreetmap.josm.tools.Utils;
    38 
    3937
    4038public class WMSGrabber extends Grabber {
     
    137135    @Override
    138136    public boolean loadFromCache(WMSRequest request) {
    139         BufferedImage cached = layer.cache.getExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
     137        BufferedImage cached = layer.cache.getExactMatch(
     138                Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
    140139
    141140        if (cached != null) {
     
    143142            return true;
    144143        } else if (request.isAllowPartialCacheMatch()) {
    145             BufferedImage partialMatch = layer.cache.getPartialMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
     144            BufferedImage partialMatch = layer.cache.getPartialMatch(
     145                    Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
    146146            if (partialMatch != null) {
    147147                request.finish(State.PARTLY_IN_CACHE, partialMatch);
     
    179179
    180180        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    181         BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
     181        BufferedImage img = layer.normalizeImage(ImageProvider.read(bais, true, WMSLayer.PROP_ALPHA_CHANNEL.get()));
    182182        bais.reset();
    183183        layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
  • trunk/src/org/openstreetmap/josm/tools/ImageProvider.java

    r7090 r7132  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Color;
    67import java.awt.Cursor;
    78import java.awt.Dimension;
     
    1314import java.awt.RenderingHints;
    1415import java.awt.Toolkit;
     16import java.awt.Transparency;
    1517import java.awt.image.BufferedImage;
     18import java.awt.image.ColorModel;
     19import java.awt.image.FilteredImageSource;
     20import java.awt.image.ImageFilter;
     21import java.awt.image.ImageProducer;
     22import java.awt.image.RGBImageFilter;
     23import java.awt.image.WritableRaster;
    1624import java.io.ByteArrayInputStream;
    1725import java.io.File;
     
    2937import java.util.Collection;
    3038import java.util.HashMap;
     39import java.util.Hashtable;
     40import java.util.Iterator;
    3141import java.util.Map;
    3242import java.util.concurrent.ExecutorService;
     
    3747import java.util.zip.ZipFile;
    3848
     49import javax.imageio.IIOException;
    3950import javax.imageio.ImageIO;
     51import javax.imageio.ImageReadParam;
     52import javax.imageio.ImageReader;
     53import javax.imageio.metadata.IIOMetadata;
     54import javax.imageio.stream.ImageInputStream;
    4055import javax.swing.Icon;
    4156import javax.swing.ImageIcon;
     
    4661import org.openstreetmap.josm.io.MirroredInputStream;
    4762import org.openstreetmap.josm.plugins.PluginHandler;
     63import org.w3c.dom.Element;
     64import org.w3c.dom.Node;
    4865import org.xml.sax.Attributes;
    4966import org.xml.sax.EntityResolver;
     
    90107        OTHER
    91108    }
     109
     110    /**
     111     * Property set on {@code BufferedImage} returned by {@link #makeImageTransparent}.
     112     * @since 7132
     113     */
     114    public static String PROP_TRANSPARENCY_FORCED = "josm.transparency.forced";
     115
     116    /**
     117     * Property set on {@code BufferedImage} returned by {@link #read} if metadata is required.
     118     * @since 7132
     119     */
     120    public static String PROP_TRANSPARENCY_COLOR = "josm.transparency.color";
    92121
    93122    protected Collection<String> dirs;
     
    513542                BufferedImage img = null;
    514543                try {
    515                     img = ImageIO.read(Utils.fileToURL(is.getFile()));
     544                    img = read(Utils.fileToURL(is.getFile()), false, false);
    516545                } catch (IOException e) {
    517546                    Main.warn("IOException while reading HTTP image: "+e.getMessage());
     
    555584                } else {
    556585                    try {
    557                         return new ImageResource(ImageIO.read(new ByteArrayInputStream(bytes)));
     586                        return new ImageResource(read(new ByteArrayInputStream(bytes), false, false));
    558587                    } catch (IOException e) {
    559588                        Main.warn("IOException while reading image: "+e.getMessage());
     
    625654                        BufferedImage img = null;
    626655                        try {
    627                             img = ImageIO.read(new ByteArrayInputStream(buf));
     656                            img = read(new ByteArrayInputStream(buf), false, false);
    628657                        } catch (IOException e) {
    629658                            Main.warn(e);
     
    650679            BufferedImage img = null;
    651680            try {
    652                 img = ImageIO.read(path);
     681                img = read(path, false, false);
    653682            } catch (IOException e) {
    654683                Main.warn(e);
     
    10041033        return svgUniverse;
    10051034    }
     1035
     1036    /**
     1037     * Returns a <code>BufferedImage</code> as the result of decoding
     1038     * a supplied <code>File</code> with an <code>ImageReader</code>
     1039     * chosen automatically from among those currently registered.
     1040     * The <code>File</code> is wrapped in an
     1041     * <code>ImageInputStream</code>.  If no registered
     1042     * <code>ImageReader</code> claims to be able to read the
     1043     * resulting stream, <code>null</code> is returned.
     1044     *
     1045     * <p> The current cache settings from <code>getUseCache</code>and
     1046     * <code>getCacheDirectory</code> will be used to control caching in the
     1047     * <code>ImageInputStream</code> that is created.
     1048     *
     1049     * <p> Note that there is no <code>read</code> method that takes a
     1050     * filename as a <code>String</code>; use this method instead after
     1051     * creating a <code>File</code> from the filename.
     1052     *
     1053     * <p> This method does not attempt to locate
     1054     * <code>ImageReader</code>s that can read directly from a
     1055     * <code>File</code>; that may be accomplished using
     1056     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
     1057     *
     1058     * @param input a <code>File</code> to read from.
     1059     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color, if any.
     1060     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
     1061     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
     1062     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
     1063     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
     1064     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
     1065     *
     1066     * @return a <code>BufferedImage</code> containing the decoded
     1067     * contents of the input, or <code>null</code>.
     1068     *
     1069     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
     1070     * @throws IOException if an error occurs during reading.
     1071     * @since 7132
     1072     * @see BufferedImage#getProperty
     1073     */
     1074    public static BufferedImage read(File input, boolean readMetadata, boolean enforceTransparency) throws IOException {
     1075        CheckParameterUtil.ensureParameterNotNull(input, "input");
     1076        if (!input.canRead()) {
     1077            throw new IIOException("Can't read input file!");
     1078        }
     1079
     1080        ImageInputStream stream = ImageIO.createImageInputStream(input);
     1081        if (stream == null) {
     1082            throw new IIOException("Can't create an ImageInputStream!");
     1083        }
     1084        BufferedImage bi = read(stream, readMetadata, enforceTransparency);
     1085        if (bi == null) {
     1086            stream.close();
     1087        }
     1088        return bi;
     1089    }
     1090
     1091    /**
     1092     * Returns a <code>BufferedImage</code> as the result of decoding
     1093     * a supplied <code>InputStream</code> with an <code>ImageReader</code>
     1094     * chosen automatically from among those currently registered.
     1095     * The <code>InputStream</code> is wrapped in an
     1096     * <code>ImageInputStream</code>.  If no registered
     1097     * <code>ImageReader</code> claims to be able to read the
     1098     * resulting stream, <code>null</code> is returned.
     1099     *
     1100     * <p> The current cache settings from <code>getUseCache</code>and
     1101     * <code>getCacheDirectory</code> will be used to control caching in the
     1102     * <code>ImageInputStream</code> that is created.
     1103     *
     1104     * <p> This method does not attempt to locate
     1105     * <code>ImageReader</code>s that can read directly from an
     1106     * <code>InputStream</code>; that may be accomplished using
     1107     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
     1108     *
     1109     * <p> This method <em>does not</em> close the provided
     1110     * <code>InputStream</code> after the read operation has completed;
     1111     * it is the responsibility of the caller to close the stream, if desired.
     1112     *
     1113     * @param input an <code>InputStream</code> to read from.
     1114     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
     1115     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
     1116     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
     1117     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
     1118     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
     1119     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
     1120     *
     1121     * @return a <code>BufferedImage</code> containing the decoded
     1122     * contents of the input, or <code>null</code>.
     1123     *
     1124     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
     1125     * @throws IOException if an error occurs during reading.
     1126     * @since 7132
     1127     */
     1128    public static BufferedImage read(InputStream input, boolean readMetadata, boolean enforceTransparency) throws IOException {
     1129        CheckParameterUtil.ensureParameterNotNull(input, "input");
     1130
     1131        ImageInputStream stream = ImageIO.createImageInputStream(input);
     1132        BufferedImage bi = read(stream, readMetadata, enforceTransparency);
     1133        if (bi == null) {
     1134            stream.close();
     1135        }
     1136        return bi;
     1137    }
     1138
     1139    /**
     1140     * Returns a <code>BufferedImage</code> as the result of decoding
     1141     * a supplied <code>URL</code> with an <code>ImageReader</code>
     1142     * chosen automatically from among those currently registered.  An
     1143     * <code>InputStream</code> is obtained from the <code>URL</code>,
     1144     * which is wrapped in an <code>ImageInputStream</code>.  If no
     1145     * registered <code>ImageReader</code> claims to be able to read
     1146     * the resulting stream, <code>null</code> is returned.
     1147     *
     1148     * <p> The current cache settings from <code>getUseCache</code>and
     1149     * <code>getCacheDirectory</code> will be used to control caching in the
     1150     * <code>ImageInputStream</code> that is created.
     1151     *
     1152     * <p> This method does not attempt to locate
     1153     * <code>ImageReader</code>s that can read directly from a
     1154     * <code>URL</code>; that may be accomplished using
     1155     * <code>IIORegistry</code> and <code>ImageReaderSpi</code>.
     1156     *
     1157     * @param input a <code>URL</code> to read from.
     1158     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
     1159     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
     1160     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
     1161     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
     1162     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
     1163     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
     1164     *
     1165     * @return a <code>BufferedImage</code> containing the decoded
     1166     * contents of the input, or <code>null</code>.
     1167     *
     1168     * @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
     1169     * @throws IOException if an error occurs during reading.
     1170     * @since 7132
     1171     */
     1172    public static BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException {
     1173        CheckParameterUtil.ensureParameterNotNull(input, "input");
     1174
     1175        InputStream istream = null;
     1176        try {
     1177            istream = input.openStream();
     1178        } catch (IOException e) {
     1179            throw new IIOException("Can't get input stream from URL!", e);
     1180        }
     1181        ImageInputStream stream = ImageIO.createImageInputStream(istream);
     1182        BufferedImage bi;
     1183        try {
     1184            bi = read(stream, readMetadata, enforceTransparency);
     1185            if (bi == null) {
     1186                stream.close();
     1187            }
     1188        } finally {
     1189            istream.close();
     1190        }
     1191        return bi;
     1192    }
     1193
     1194    /**
     1195     * Returns a <code>BufferedImage</code> as the result of decoding
     1196     * a supplied <code>ImageInputStream</code> with an
     1197     * <code>ImageReader</code> chosen automatically from among those
     1198     * currently registered.  If no registered
     1199     * <code>ImageReader</code> claims to be able to read the stream,
     1200     * <code>null</code> is returned.
     1201     *
     1202     * <p> Unlike most other methods in this class, this method <em>does</em>
     1203     * close the provided <code>ImageInputStream</code> after the read
     1204     * operation has completed, unless <code>null</code> is returned,
     1205     * in which case this method <em>does not</em> close the stream.
     1206     *
     1207     * @param stream an <code>ImageInputStream</code> to read from.
     1208     * @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images, if any.
     1209     * In that case the color can be retrieved later through {@link #PROP_TRANSPARENCY_COLOR}.
     1210     * Always considered {@code true} if {@code enforceTransparency} is also {@code true}
     1211     * @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
     1212     * provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
     1213     * has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
     1214     *
     1215     * @return a <code>BufferedImage</code> containing the decoded
     1216     * contents of the input, or <code>null</code>.
     1217     *
     1218     * @throws IllegalArgumentException if <code>stream</code> is <code>null</code>.
     1219     * @throws IOException if an error occurs during reading.
     1220     * @since 7132
     1221     */
     1222    public static BufferedImage read(ImageInputStream stream, boolean readMetadata, boolean enforceTransparency) throws IOException {
     1223        CheckParameterUtil.ensureParameterNotNull(stream, "stream");
     1224
     1225        Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
     1226        if (!iter.hasNext()) {
     1227            return null;
     1228        }
     1229
     1230        ImageReader reader = iter.next();
     1231        ImageReadParam param = reader.getDefaultReadParam();
     1232        reader.setInput(stream, true, !readMetadata && !enforceTransparency);
     1233        BufferedImage bi;
     1234        try {
     1235            bi = reader.read(0, param);
     1236            if (bi.getTransparency() != Transparency.TRANSLUCENT && (readMetadata || enforceTransparency)) {
     1237                Color color = getTransparentColor(reader);
     1238                if (color != null) {
     1239                    Hashtable<String, Object> properties = new Hashtable<>(1);
     1240                    properties.put(PROP_TRANSPARENCY_COLOR, color);
     1241                    bi = new BufferedImage(bi.getColorModel(), bi.getRaster(), bi.isAlphaPremultiplied(), properties);
     1242                    if (enforceTransparency) {
     1243                        if (Main.isDebugEnabled()) {
     1244                            Main.debug("Enforcing image transparency of "+stream+" for "+color);
     1245                        }
     1246                        bi = makeImageTransparent(bi, color);
     1247                    }
     1248                }
     1249            }
     1250        } finally {
     1251            reader.dispose();
     1252            stream.close();
     1253        }
     1254        return bi;
     1255    }
     1256
     1257    /**
     1258     * Returns the {@code TransparentColor} defined in image reader metadata.
     1259     * @param reader The image reader
     1260     * @return the {@code TransparentColor} defined in image reader metadata, or {@code null}
     1261     * @throws IOException if an error occurs during reading
     1262     * @since 7132
     1263     * @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>
     1264     */
     1265    public static Color getTransparentColor(ImageReader reader) throws IOException {
     1266        IIOMetadata metadata = reader.getImageMetadata(0);
     1267        if (metadata != null) {
     1268            String[] formats = metadata.getMetadataFormatNames();
     1269            if (formats != null) {
     1270                for (String f : formats) {
     1271                    if ("javax_imageio_1.0".equals(f)) {
     1272                        Node root = metadata.getAsTree(f);
     1273                        if (root instanceof Element) {
     1274                            Node item = ((Element)root).getElementsByTagName("TransparentColor").item(0);
     1275                            if (item instanceof Element) {
     1276                                String value = ((Element)item).getAttribute("value");
     1277                                String[] s = value.split(" ");
     1278                                if (s.length == 3) {
     1279                                    int[] rgb = new int[3];
     1280                                    try {
     1281                                        for (int i = 0; i<3; i++) {
     1282                                            rgb[i] = Integer.parseInt(s[i]);
     1283                                        }
     1284                                        return new Color(rgb[0], rgb[1], rgb[2]);
     1285                                    } catch (IllegalArgumentException e) {
     1286                                        Main.error(e);
     1287                                    }
     1288                                }
     1289                            }
     1290                        }
     1291                        break;
     1292                    }
     1293                }
     1294            }
     1295        }
     1296        return null;
     1297    }
     1298
     1299    /**
     1300     * Returns a transparent version of the given image, based on the given transparent color.
     1301     * @param bi The image to convert
     1302     * @param color The transparent color
     1303     * @return The same image as {@code bi} where all pixels of the given color are transparent.
     1304     * This resulting image has also the special property {@link #PROP_TRANSPARENCY_FORCED} set to {@code color}
     1305     * @since 7132
     1306     * @see BufferedImage#getProperty
     1307     * @see #isTransparencyForced
     1308     */
     1309    public static BufferedImage makeImageTransparent(BufferedImage bi, Color color) {
     1310        // the color we are looking for. Alpha bits are set to opaque
     1311        final int markerRGB = color.getRGB() | 0xFFFFFFFF;
     1312        ImageFilter filter = new RGBImageFilter() {
     1313            @Override
     1314            public int filterRGB(int x, int y, int rgb) {
     1315                if ((rgb | 0xFF000000) == markerRGB) {
     1316                   // Mark the alpha bits as zero - transparent
     1317                   return 0x00FFFFFF & rgb;
     1318                } else {
     1319                   return rgb;
     1320                }
     1321            }
     1322        };
     1323        ImageProducer ip = new FilteredImageSource(bi.getSource(), filter);
     1324        Image img = Toolkit.getDefaultToolkit().createImage(ip);
     1325        ColorModel colorModel = ColorModel.getRGBdefault();
     1326        WritableRaster raster = colorModel.createCompatibleWritableRaster(img.getWidth(null), img.getHeight(null));
     1327        String[] names = bi.getPropertyNames();
     1328        Hashtable<String, Object> properties = new Hashtable<>(1 + (names != null ? names.length : 0));
     1329        if (names != null) {
     1330            for (String name : names) {
     1331                properties.put(name, bi.getProperty(name));
     1332            }
     1333        }
     1334        properties.put(PROP_TRANSPARENCY_FORCED, Boolean.TRUE);
     1335        BufferedImage result = new BufferedImage(colorModel, raster, false, properties);
     1336        Graphics2D g2 = result.createGraphics();
     1337        g2.drawImage(img, 0, 0, null);
     1338        g2.dispose();
     1339        return result;
     1340    }
     1341
     1342    /**
     1343     * Determines if the transparency of the given {@code BufferedImage} has been enforced by a previous call to {@link #makeImageTransparent}.
     1344     * @param bi The {@code BufferedImage} to test
     1345     * @return {@code true} if the transparency of {@code bi} has been enforced by a previous call to {@code makeImageTransparent}.
     1346     * @since 7132
     1347     * @see #makeImageTransparent
     1348     */
     1349    public static boolean isTransparencyForced(BufferedImage bi) {
     1350        return bi != null && !bi.getProperty(PROP_TRANSPARENCY_FORCED).equals(Image.UndefinedProperty);
     1351    }
     1352
     1353    /**
     1354     * Determines if the given {@code BufferedImage} has a transparent color determiend by a previous call to {@link #read}.
     1355     * @param bi The {@code BufferedImage} to test
     1356     * @return {@code true} if {@code bi} has a transparent color determined by a previous call to {@code read}.
     1357     * @since 7132
     1358     * @see #read
     1359     */
     1360    public static boolean hasTransparentColor(BufferedImage bi) {
     1361        return bi != null && !bi.getProperty(PROP_TRANSPARENCY_COLOR).equals(Image.UndefinedProperty);
     1362    }
    10061363}
Note: See TracChangeset for help on using the changeset viewer.