Added new utility methods to ImageUtilities to ease migration from other image loading methods used throughout the NetBeans codebase. + Loading icons via ImageUtilities is preferred as it permits alternative SVG versions to be loaded automatically when available.
+If the current look and feel is 'dark' (UIManager.getBoolean("nb.dark.theme"))
+ * then the method first attempts to load image
+ * <original file name>_dark.<original extension>. If such file
+ * doesn't exist the default one is loaded instead.
*
*
If the default lookup contains a service provider for the {@link SVGLoader} interface, and * there exists an SVG version of the requested image (e.g. "icon.svg" exists when "icon.png" @@ -177,6 +184,13 @@ private ImageUtilities() { * When painting on HiDPI-capable {@code Graphics2D} instances provided by Swing, the * appropriate transform will already be in place. * + *
Since version 8.12 the returned image object responds to call
+ * image.getProperty({@link #PROPERTY_URL}, null) by returning the internal
+ * {@link URL} of the found and loaded resource. The convenience method
+ * {@link #findImageBaseURL} should be used in preference to direct property access.
+ *
+ *
Caching of loaded images can be used internally to improve performance.
+ *
* @param resourceID resource path of the icon (no initial slash)
* @return icon's Image, or null, if the icon cannot be loaded.
*/
@@ -185,31 +199,93 @@ public static final Image loadImage(String resourceID) {
}
/**
- * Loads an image based on resource path.
+ * Loads an image based on a 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({@link #PROPERTY_URL}, null) by returning the internal
- * {@link URL} of the found and loaded resource. Convenience method {@link #findImageBaseURL}
- * should be used in preference to direct property access.
- *
- *
If the current look and feel is 'dark' (UIManager.getBoolean("nb.dark.theme"))
- * then the method first attempts to load image <original file name>_dark.<original extension>.
- * If such file doesn't exist the default one is loaded instead.
- *
This method is intended for use only when a URL or URI must be used instead of a resource + * path, e.g. in the implementation of pre-existing NetBeans APIs. External URLs should be + * avoided, as they may be disallowed in the future. Do not use this method for new code; prefer + * image loading by resource paths instead (e.g. {@link #loadImage(String)}). + * + * @param uri the URI of the image, possibly with the nbresloc protocol + * @return the loaded image, or either null or an uninitialized image if the image was not + * available + * @since 7.36 + */ + public static final Image loadImage(URI uri) { + Parameters.notNull("icon", uri); + String scheme = uri.getScheme(); + if (scheme.equals("nbresloc")) { // NOI18N + // Apply our dedicated handling logic. Omit the initial slash of the path. + return loadImage(uri.getPath().substring(1), false); + } else { + if (!(scheme.equals("file") || + scheme.equals("jar") && uri.toString().startsWith("jar:file:") || + scheme.equals("file"))) + { + LOGGER.log(Level.WARNING, "loadImage(URI) called with unusual URI: {0}", uri); + } + try { + /* Observed to return an image with size (-1, -1) if URL points to a non-existent + file (after ensureLoaded(Image) is called). */ + return Toolkit.getDefaultToolkit().createImage(uri.toURL()); + } catch (MalformedURLException e) { + LOGGER.log(Level.WARNING, "Malformed URL passed to loadImage(URI)", e); + return null; + } + } + } + + /** + * Convert an {@link Icon} instance to a delegating {@link ImageIcon} instance. If the supplied + * Icon instance is an SVG icon or has other HiDPI capabilities provided by the methods in this + * class, they are preserved by the conversion. + * + *
This method is intended for use only when existing APIs require the use of ImageIcon. In + * most other situations it is preferable to use the more general Icon type. + * + * @param icon the icon to be converted; may not be null + * @return the converted instance + * @since 7.36 + */ + public static final ImageIcon icon2ImageIcon(Icon icon) { + if (icon == null) { + /* Log a warning and return null rather than throwing an exception here. That way, + refactoring of existing code that wraps an existing expression in icon2ImageIcon will + not break existing functionality even if it accidentally relied on being able to pass + null in the past. */ + LOGGER.log(Level.WARNING, "Passing null to icon2ImageIcon is not permitted", + new NullPointerException()); + return null; + } + if (icon instanceof ImageIcon) { + return (ImageIcon) icon; + } else if (icon instanceof ToolTipImage) { + return ((ToolTipImage) icon).asImageIcon(); + } else { + Image image = icon2Image(icon); + ToolTipImage tti = (image instanceof ToolTipImage) + ? (ToolTipImage) image : assignToolTipToImageInternal(image, ""); + return tti.asImageIcon(); + } + } + /* Private version of the method showing the more specific return type. We always return a ToolTipImage, to take advantage of its rendering tweaks for HiDPI screens. */ private static ToolTipImage loadImageInternal(String resource, boolean localized) { @@ -235,16 +311,15 @@ private static ToolTipImage loadImageInternal(String resource, boolean localized } /** - * Loads an icon based on resource path. - * Similar to {@link #loadImage(String, boolean)}, returns ImageIcon instead of Image. - * - *
If the current look and feel is 'dark' (UIManager.getBoolean("nb.dark.theme"))
- * then the method first attempts to load image <original file name>_dark.<original extension>.
- * If such file doesn't exist the default one is loaded instead.
- *
When a general Icon instance can be used rather than more specifically an ImageIcon, it is + * recommended to use {@link #loadIcon(java.lang.String, boolean)} instead. This method remains + * for compatibility. * * @param resource resource path of the icon (no initial slash) - * @param localized localized resource should be used + * @param localized true for localized search * @return ImageIcon or null, if the icon cannot be loaded. * @since 7.22 */ @@ -255,6 +330,35 @@ public static final ImageIcon loadImageIcon( String resource, boolean localized } return image.asImageIcon(); } + + /** + * Loads an icon based on a resource path. Similar to {@link #loadImage(String, boolean)}, but + * returns {@link Icon} instead of {@link Image}. + * + * @param resource resource path of the icon (no initial slash) + * @param localized true for localized search + * @return ImageIcon or null, if the icon cannot be loaded. + * @since 7.36 + */ + public static final Icon loadIcon( String resource, boolean localized ) { + ToolTipImage image = loadImageInternal(resource, localized); + if( image == null ) { + return null; + } + return image.asImageIconIfRequiredForRetina(); + } + + /** + * Loads an icon based on a resource path. Similar to {@link #loadImage(String)}, but returns + * {@link Icon} instead of {@link Image}. + * + * @param resource resource path of the icon (no initial slash) + * @return ImageIcon or null, if the icon cannot be loaded. + * @since 7.36 + */ + public static final Icon loadIcon( String resource ) { + return loadIcon(resource, false); + } private static boolean isDarkLaF() { return UIManager.getBoolean("nb.dark.theme"); //NOI18N @@ -284,12 +388,14 @@ private static RGBImageFilter getImageIconFilter() { return imageIconFilter; } - /** This method merges two images into the new one. The second image is drawn + /** This method merges two images into the a 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. + * The new image will have a size of max(second image size + top-left corner, first image size). + * This method is used mostly when second image contains transparent pixels (e.g. for badging). + * + *
The implementation 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
@@ -316,8 +422,34 @@ public static final Image mergeImages(Image image1, Image image2, int x, int y)
compositeCache.put(k, new ActiveRef Similar to {@link #mergeImages(Image, Image, int, int)}, but on {@link Icon} instances
+ * rather than {@link Image} instances. This method is provided as a shortcut to avoid the need
+ * for conversions in client code.
+ *
+ * @param icon1 underlying icon
+ * @param icon2 second icon
+ * @param x x position of top-left corner
+ * @param y y position of top-left corner
+ * @return new merged icon
+ * @since 7.36
+ */
+ public static final Icon mergeIcons(Icon icon1, Icon icon2, int x, int y) {
+ if (icon1 == null || icon2 == null) {
+ throw new NullPointerException();
+ }
+ /* Use the Image-based mergeImages implementation rather than just creating a new
+ MergedIcon instance, to take advantage of the former's cache. Icon instances tend to get
+ converted back and forth between Icon and Image anyway. */
+ return image2Icon(mergeImages(icon2Image(icon1), icon2Image(icon2), x, y));
+ }
+
/**
* Converts given image to an icon.
* @param image to be converted
@@ -384,7 +516,7 @@ methods in this class may be called from any thread, while JLabel's methods and
}
/**
- * Assign tool tip text to given image (creates new or returns cached, original remains unmodified)
+ * 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
@@ -428,7 +560,7 @@ public static final String getImageToolTip(Image image) {
}
/**
- * Add text to tool tip for given image (creates new or returns cached, original remains unmodified)
+ * 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
@@ -449,7 +581,7 @@ public static final Image addToolTipToImage(Image image, String 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
@@ -465,6 +597,7 @@ public static Icon createDisabledIcon(Icon icon) {
/**
* Creates disabled (color saturation lowered) image.
+ *
* @param image original image used for conversion
* @return less saturated Image
* @since 7.28
@@ -849,7 +982,7 @@ private static void ensureLoaded(Image image) {
}
}
- private static final ToolTipImage doMergeImages(Image image1, Image image2, int x, int y) {
+ private static ToolTipImage doMergeImages(Image image1, Image image2, int x, int y) {
ensureLoaded(image1);
ensureLoaded(image2);