Ignore:
Timestamp:
2014-05-15T03:15:28+02:00 (9 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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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.