All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.util.ImageUtilities Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */

package org.openide.util;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.MediaTracker;
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.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.awt.image.RGBImageFilter;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;

/** 
 * Useful static methods for manipulation with images/icons, results are cached.
 * 
 * @author Jaroslav Tulach, Tomas Holy
 * @since 7.15
 */
public final class ImageUtilities {
    /** separator for individual parts of tool tip text */
    static final String TOOLTIP_SEPAR = "
"; // NOI18N /** a value that indicates that the icon does not exists */ private static final ActiveRef NO_ICON = new ActiveRef(null, null, null); private static final Map> cache = new HashMap>(128); private static final Map> localizedCache = new HashMap>(128); private static final Map> compositeCache = new HashMap>(128); private static final Map> imageToolTipCache = new HashMap>(128); /** Resource paths for which we have had to strip initial slash. * @see "#20072" */ private static final Set extraInitialSlashes = new HashSet(); private static volatile Object currentLoader; private static Lookup.Result loaderQuery = null; private static boolean noLoaderWarned = false; private static final Component component = new Component() { }; private static final MediaTracker tracker = new MediaTracker(component); private static int mediaTrackerID; private static ImageReader PNG_READER; // private static ImageReader GIF_READER; private static final Logger ERR = Logger.getLogger(ImageUtilities.class.getName()); private ImageUtilities() { } static { ImageIO.setUseCache(false); PNG_READER = ImageIO.getImageReadersByMIMEType("image/png").next(); // GIF_READER = ImageIO.getImageReadersByMIMEType("image/gif").next(); } /** * Loads an image from the specified resource ID. The image is loaded using the "system" classloader registered in * Lookup. * @param resourceID resource path of the icon (no initial slash) * @return icon's Image, or null, if the icon cannot be loaded. */ public static final Image loadImage(String resourceID) { return getIcon(resourceID, false); } /** * Loads an image based on resource path. * Exactly like {@link #loadImage(String)} but may do a localized search. * For example, requesting org/netbeans/modules/foo/resources/foo.gif * might actually find org/netbeans/modules/foo/resources/foo_ja.gif * or org/netbeans/modules/foo/resources/foo_mybranding.gif. * *

Caching of loaded images can be used internally to improve performance. *

Since version 8.12 the returned image object responds to call * image.getProperty("url", null) by returning the internal * {@link URL} of the found and loaded resource. * * @param resource resource path of the image (no initial slash) * @param localized true for localized search * @return icon's Image or null if the icon cannot be loaded */ public static final Image loadImage(String resource, boolean localized) { return getIcon(resource, localized); } /** * Loads an icon based on resource path. * Similar to {@link #loadImage(String, boolean)}, returns ImageIcon instead of Image. * @param resource resource path of the icon (no initial slash) * @param localized localized resource should be used * @return ImageIcon or null, if the icon cannot be loaded. * @since 7.22 */ public static final ImageIcon loadImageIcon(String resource, boolean localized) { Image image = getIcon(resource, localized); return image == null ? null : (ImageIcon) image2Icon(image); } /** This method merges two images into the new one. The second image is drawn * over the first one with its top-left corner at x, y. Images need not be of the same size. * New image will have a size of max(second image size + top-left corner, first image size). * Method is used mostly when second image contains transparent pixels (e.g. for badging). * Method that attempts to find the merged image in the cache first, then * creates the image if it was not found. * @param image1 underlying image * @param image2 second image * @param x x position of top-left corner * @param y y position of top-left corner * @return new merged image */ public static final Image mergeImages(Image image1, Image image2, int x, int y) { if (image1 == null || image2 == null) { throw new NullPointerException(); } CompositeImageKey k = new CompositeImageKey(image1, image2, x, y); Image cached; synchronized (compositeCache) { ActiveRef r = compositeCache.get(k); if (r != null) { cached = r.get(); if (cached != null) { return cached; } } cached = doMergeImages(image1, image2, x, y); compositeCache.put(k, new ActiveRef(cached, compositeCache, k)); return cached; } } /** * Converts given image to an icon. * @param image to be converted * @return icon corresponding icon */ public static final Icon image2Icon(Image image) { if (image instanceof ToolTipImage) { return ((ToolTipImage) image).getIcon(); } else { return new ImageIcon(image); } } /** * Converts given icon to a {@link java.awt.Image}. * * @param icon {@link javax.swing.Icon} to be converted. */ public static final Image icon2Image(Icon icon) { if (icon instanceof ImageIcon) { return ((ImageIcon) icon).getImage(); } else { ToolTipImage image = new ToolTipImage("", icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = image.getGraphics(); icon.paintIcon(new JLabel(), g, 0, 0); g.dispose(); return image; } } /** * Assign tool tip text to given image (creates new or returns cached, original remains unmodified) * Text can contain HTML tags e.g. "<b>my</b> text" * @param image image to which tool tip should be set * @param text tool tip text * @return Image with attached tool tip */ public static final Image assignToolTipToImage(Image image, String text) { Parameters.notNull("image", image); Parameters.notNull("text", text); ToolTipImageKey key = new ToolTipImageKey(image, text); Image cached; synchronized (imageToolTipCache) { ActiveRef r = imageToolTipCache.get(key); if (r != null) { cached = r.get(); if (cached != null) { return cached; } } cached = ToolTipImage.createNew(text, image, null); imageToolTipCache.put(key, new ActiveRef(cached, imageToolTipCache, key)); return cached; } } /** * Get tool tip text for given image * @param image image which is asked for tool tip text * @return String containing attached tool tip text */ public static final String getImageToolTip(Image image) { if (image instanceof ToolTipImage) { return ((ToolTipImage) image).toolTipText; } else { return ""; } } /** * Add text to tool tip for given image (creates new or returns cached, original remains unmodified) * Text can contain HTML tags e.g. "<b>my</b> text" * @param text text to add to tool tip * @return Image with attached tool tip */ public static final Image addToolTipToImage(Image image, String text) { if (image instanceof ToolTipImage) { ToolTipImage tti = (ToolTipImage) image; StringBuilder str = new StringBuilder(tti.toolTipText); if (str.length() > 0 && text.length() > 0) { str.append(TOOLTIP_SEPAR); } str.append(text); return assignToolTipToImage(image, str.toString()); } else { return assignToolTipToImage(image, text); } } /** * Creates disabled (color saturation lowered) icon. * Icon image conversion is performed lazily. * @param icon original icon used for conversion * @return less saturated Icon * @since 7.28 */ public static Icon createDisabledIcon(Icon icon) { Parameters.notNull("icon", icon); return new LazyDisabledIcon(icon2Image(icon)); } /** * Creates disabled (color saturation lowered) image. * @param image original image used for conversion * @return less saturated Image * @since 7.28 */ public static Image createDisabledImage(Image image) { Parameters.notNull("image", image); return LazyDisabledIcon.createDisabledImage(image); } /** * Get the class loader from lookup. * Since this is done very frequently, it is wasteful to query lookup each time. * Instead, remember the last result and just listen for changes. */ static ClassLoader getLoader() { Object is = currentLoader; if (is instanceof ClassLoader) { return (ClassLoader)is; } currentLoader = Thread.currentThread(); if (loaderQuery == null) { loaderQuery = Lookup.getDefault().lookup(new Lookup.Template(ClassLoader.class)); loaderQuery.addLookupListener( new LookupListener() { public void resultChanged(LookupEvent ev) { ERR.fine("Loader cleared"); // NOI18N currentLoader = null; } } ); } Iterator it = loaderQuery.allInstances().iterator(); if (it.hasNext()) { ClassLoader toReturn = (ClassLoader) it.next(); if (currentLoader == Thread.currentThread()) { currentLoader = toReturn; } ERR.fine("Loader computed: " + currentLoader); // NOI18N return toReturn; } else { if (!noLoaderWarned) { noLoaderWarned = true; ERR.warning( "No ClassLoader instance found in " + Lookup.getDefault() // NOI18N ); } return null; } } static Image getIcon(String resource, boolean localized) { if (localized) { synchronized (localizedCache) { ActiveRef ref = localizedCache.get(resource); Image img = null; // no icon for this name (already tested) if (ref == NO_ICON) { return null; } if (ref != null) { // then it is SoftRefrence img = ref.get(); } // icon found if (img != null) { return img; } // find localized or base image ClassLoader loader = getLoader(); // we'll keep the String probably for long time, optimize it resource = new String(resource).intern(); // NOPMD String base; String ext; int idx = resource.lastIndexOf('.'); if ((idx != -1) && (idx > resource.lastIndexOf('/'))) { base = resource.substring(0, idx); ext = resource.substring(idx); } else { base = resource; ext = ""; // NOI18N } // #31008. [PENDING] remove in case package cache is precomputed java.net.URL baseurl = (loader != null) ? loader.getResource(resource) // NOPMD : ImageUtilities.class.getClassLoader().getResource(resource); Iterator it = NbBundle.getLocalizingSuffixes(); while (it.hasNext()) { String suffix = it.next(); Image i; if (suffix.length() == 0) { i = getIcon(resource, loader, false); } else { i = getIcon(base + suffix + ext, loader, true); } if (i != null) { localizedCache.put(resource, new ActiveRef(i, localizedCache, resource)); return i; } } localizedCache.put(resource, NO_ICON); return null; } } else { return getIcon(resource, getLoader(), false); } } /** Finds image for given resource. * @param name name of the resource * @param loader classloader to use for locating it, or null to use classpath * @param localizedQuery whether the name contains some localization suffix * and is not optimized/interned */ private static Image getIcon(String name, ClassLoader loader, boolean localizedQuery) { ActiveRef ref = cache.get(name); Image img = null; // no icon for this name (already tested) if (ref == NO_ICON) { return null; } if (ref != null) { img = ref.get(); } // icon found if (img != null) { return img; } synchronized (cache) { // again under the lock ref = cache.get(name); // no icon for this name (already tested) if (ref == NO_ICON) { return null; } if (ref != null) { // then it is SoftRefrence img = ref.get(); } if (img != null) { // cannot be NO_ICON, since it never disappears from the map. return img; } // path for bug in classloader String n; boolean warn; if (name.startsWith("/")) { // NOI18N warn = true; n = name.substring(1); } else { warn = false; n = name; } // we have to load it java.net.URL url = (loader != null) ? loader.getResource(n) : ImageUtilities.class.getClassLoader().getResource(n); // img = (url == null) ? null : Toolkit.getDefaultToolkit().createImage(url); Image result = null; try { if (url != null) { if (name.endsWith(".png")) { ImageInputStream stream = ImageIO.createImageInputStream(url.openStream()); ImageReadParam param = PNG_READER.getDefaultReadParam(); try { PNG_READER.setInput(stream, true, true); result = PNG_READER.read(0, param); } catch (IOException ioe1) { ERR.log(Level.INFO, "Image "+name+" is not PNG", ioe1); } stream.close(); } /* else if (name.endsWith(".gif")) { ImageInputStream stream = ImageIO.createImageInputStream(url.openStream()); ImageReadParam param = GIF_READER.getDefaultReadParam(); try { GIF_READER.setInput(stream, true, true); result = GIF_READER.read(0, param); } catch (IOException ioe1) { ERR.log(Level.INFO, "Image "+name+" is not GIF", ioe1); } stream.close(); } */ if (result == null) { result = ImageIO.read(url); } } } catch (IOException ioe) { ERR.log(Level.WARNING, "Cannot load " + name + " image", ioe); } if (result != null) { if (warn && extraInitialSlashes.add(name)) { ERR.warning( "Initial slashes in Utilities.loadImage deprecated (cf. #20072): " + name ); // NOI18N } // Image img2 = toBufferedImage(result); ERR.log(Level.FINE, "loading icon {0} = {1}", new Object[] {n, result}); name = new String(name).intern(); // NOPMD result = ToolTipImage.createNew("", result, url); cache.put(name, new ActiveRef(result, cache, name)); return result; } else { // no icon found if (!localizedQuery) { cache.put(name, NO_ICON); } return null; } } } /** The method creates a BufferedImage which represents the same Image as the * parameter but consumes less memory. */ static final Image toBufferedImage(Image img) { // load the image new javax.swing.ImageIcon(img, ""); if (img.getHeight(null)*img.getWidth(null) > 24*24) { return img; } java.awt.image.BufferedImage rep = createBufferedImage(img.getWidth(null), img.getHeight(null)); java.awt.Graphics g = rep.createGraphics(); g.drawImage(img, 0, 0, null); g.dispose(); img.flush(); return rep; } private static void ensureLoaded(Image image) { if ( (Toolkit.getDefaultToolkit().checkImage(image, -1, -1, null) & (ImageObserver.ALLBITS | ImageObserver.FRAMEBITS)) != 0 ) { return; } synchronized (tracker) { int id = ++mediaTrackerID; tracker.addImage(image, id); try { tracker.waitForID(id, 0); } catch (InterruptedException e) { System.out.println("INTERRUPTED while loading Image"); } assert (tracker.statusID(id, false) == MediaTracker.COMPLETE) : "Image loaded"; tracker.removeImage(image, id); } } private static final Image doMergeImages(Image image1, Image image2, int x, int y) { ensureLoaded(image1); ensureLoaded(image2); int w = Math.max(image1.getWidth(null), x + image2.getWidth(null)); int h = Math.max(image1.getHeight(null), y + image2.getHeight(null)); boolean bitmask = (image1 instanceof Transparency) && ((Transparency)image1).getTransparency() != Transparency.TRANSLUCENT && (image2 instanceof Transparency) && ((Transparency)image2).getTransparency() != Transparency.TRANSLUCENT; StringBuilder str = new StringBuilder(image1 instanceof ToolTipImage ? ((ToolTipImage)image1).toolTipText : ""); if (image2 instanceof ToolTipImage) { String toolTip = ((ToolTipImage)image2).toolTipText; if (str.length() > 0 && toolTip.length() > 0) { str.append(TOOLTIP_SEPAR); } str.append(toolTip); } ColorModel model = colorModel(bitmask? Transparency.BITMASK: Transparency.TRANSLUCENT); ToolTipImage buffImage = new ToolTipImage(str.toString(), model, model.createCompatibleWritableRaster(w, h), model.isAlphaPremultiplied(), null, null ); java.awt.Graphics g = buffImage.createGraphics(); g.drawImage(image1, 0, 0, null); g.drawImage(image2, x, y, null); g.dispose(); return buffImage; } /** Creates BufferedImage with Transparency.TRANSLUCENT */ static final java.awt.image.BufferedImage createBufferedImage(int width, int height) { if (Utilities.isMac()) { return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); } ColorModel model = colorModel(java.awt.Transparency.TRANSLUCENT); java.awt.image.BufferedImage buffImage = new java.awt.image.BufferedImage( model, model.createCompatibleWritableRaster(width, height), model.isAlphaPremultiplied(), null ); return buffImage; } static private ColorModel colorModel(int transparency) { ColorModel model; try { model = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration() .getColorModel(transparency); } catch(HeadlessException he) { model = ColorModel.getRGBdefault(); } return model; } /** * Key used for composite images -- it holds image identities */ private static class CompositeImageKey { Image baseImage; Image overlayImage; int x; int y; CompositeImageKey(Image base, Image overlay, int x, int y) { this.x = x; this.y = y; this.baseImage = base; this.overlayImage = overlay; } @Override public boolean equals(Object other) { if (!(other instanceof CompositeImageKey)) { return false; } CompositeImageKey k = (CompositeImageKey) other; return (x == k.x) && (y == k.y) && (baseImage == k.baseImage) && (overlayImage == k.overlayImage); } @Override public int hashCode() { int hash = ((x << 3) ^ y) << 4; hash = hash ^ baseImage.hashCode() ^ overlayImage.hashCode(); return hash; } @Override public String toString() { return "Composite key for " + baseImage + " + " + overlayImage + " at [" + x + ", " + y + "]"; // NOI18N } } /** * Key used for ToolTippedImage */ private static class ToolTipImageKey { Image image; String str; ToolTipImageKey(Image image, String str) { this.image = image; this.str = str; } @Override public boolean equals(Object other) { if (!(other instanceof ToolTipImageKey)) { return false; } ToolTipImageKey k = (ToolTipImageKey) other; return (str.equals(k.str)) && (image == k.image); } @Override public int hashCode() { int hash = image.hashCode() ^ str.hashCode(); return hash; } @Override public String toString() { return "ImageStringKey for " + image + " + " + str; // NOI18N } } /** Cleaning reference. */ private static final class ActiveRef extends SoftReference implements Runnable { private final Map> holder; private final T key; public ActiveRef(Image o, Map> holder, T key) { super(o, Utilities.activeReferenceQueue()); this.holder = holder; this.key = key; } public void run() { synchronized (holder) { holder.remove(key); } } } // end of ActiveRef /** * Image with tool tip text (for icons with badges) */ private static class ToolTipImage extends BufferedImage implements Icon { final String toolTipText; ImageIcon imageIcon; final URL url; public static ToolTipImage createNew(String toolTipText, Image image, URL url) { ImageUtilities.ensureLoaded(image); boolean bitmask = (image instanceof Transparency) && ((Transparency) image).getTransparency() != Transparency.TRANSLUCENT; ColorModel model = colorModel(bitmask ? Transparency.BITMASK : Transparency.TRANSLUCENT); int w = image.getWidth(null); int h = image.getHeight(null); ToolTipImage newImage = new ToolTipImage( toolTipText, model, model.createCompatibleWritableRaster(w, h), model.isAlphaPremultiplied(), null, url ); java.awt.Graphics g = newImage.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return newImage; } public ToolTipImage( String toolTipText, ColorModel cm, WritableRaster raster, boolean isRasterPremultiplied, Hashtable properties, URL url ) { super(cm, raster, isRasterPremultiplied, properties); this.toolTipText = toolTipText; this.url = url; } public ToolTipImage(String toolTipText, int width, int height, int imageType) { super(width, height, imageType); this.toolTipText = toolTipText; this.url = null; } synchronized ImageIcon getIcon() { if (imageIcon == null) { imageIcon = new ImageIcon(this); } return imageIcon; } public int getIconHeight() { return super.getHeight(); } public int getIconWidth() { return super.getWidth(); } public void paintIcon(Component c, Graphics g, int x, int y) { g.drawImage(this, x, y, null); } @Override public Object getProperty(String name, ImageObserver observer) { if ("url".equals(name)) { // NOI18N if (url != null) { return url; } else { return imageIcon.getImage().getProperty("url", observer); } } return super.getProperty(name, observer); } } private static class LazyDisabledIcon implements Icon { /** Shared instance of filter for disabled icons */ private static final RGBImageFilter DISABLED_BUTTON_FILTER = new DisabledButtonFilter(); private Image img; private Icon disabledIcon; public LazyDisabledIcon(Image img) { assert null != img; this.img = img; } public void paintIcon(Component c, Graphics g, int x, int y) { getDisabledIcon().paintIcon(c, g, x, y); } public int getIconWidth() { return getDisabledIcon().getIconWidth(); } public int getIconHeight() { return getDisabledIcon().getIconHeight(); } private synchronized Icon getDisabledIcon() { if (null == disabledIcon) { disabledIcon = new ImageIcon(createDisabledImage(img)); } return disabledIcon; } static Image createDisabledImage(Image img) { ImageProducer prod = new FilteredImageSource(img.getSource(), DISABLED_BUTTON_FILTER); return Toolkit.getDefaultToolkit().createImage(prod); } } private static class DisabledButtonFilter extends RGBImageFilter { DisabledButtonFilter() { canFilterIndexColorModel = true; } public int filterRGB(int x, int y, int rgb) { // Reduce the color bandwidth in quarter (>> 2) and Shift 0x88. return (rgb & 0xff000000) + 0x888888 + ((((rgb >> 16) & 0xff) >> 2) << 16) + ((((rgb >> 8) & 0xff) >> 2) << 8) + (((rgb) & 0xff) >> 2); } // override the superclass behaviour to not pollute // the heap with useless properties strings. Saves tens of KBs @Override public void setProperties(Hashtable props) { props = (Hashtable) props.clone(); consumer.setProperties(props); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy