From 5b8f92227357cd6010a457c2c887b55132e75e27 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 10 Nov 2020 11:56:59 +0100 Subject: [PATCH] FlatSVGIcon: getImage() now returns a multi-resolution image (on Java 9+) for HiDPI disabled icons in other LaFs that support multi-resolution images when producing disabled icons in LookAndFeel.getDisabledIcon() (e.g. Windows or Nimbus Laf) (issue #205) --- .../util/MultiResolutionImageSupport.java | 69 +++++++++++++++++++ .../util/MultiResolutionImageSupport.java | 60 ++++++++++++++++ .../formdev/flatlaf/extras/FlatSVGIcon.java | 38 +++++++--- 3 files changed, 159 insertions(+), 8 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java index 259db6ff..ace4a720 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java @@ -16,11 +16,20 @@ package com.formdev.flatlaf.util; +import java.awt.Dimension; import java.awt.Image; import java.util.Collections; import java.util.List; import java.util.function.Function; +// +// NOTE: +// This implementation is for Java 8 only. +// There is also a variant for Java 9 and later. +// +// Make sure that the API is in sync. +// + /** * Support for multi-resolution images available since Java 9. * @@ -28,26 +37,86 @@ import java.util.function.Function; */ public class MultiResolutionImageSupport { + /** + * Checks whether multi-resolution image support is available. + * + * @return {@code true} when running on Java 9 or later; {@code false} on Java 8 + */ public static boolean isAvailable() { return false; } + /** + * Checks whether the given image is a multi-resolution image that implements + * the interface {@code java.awt.image.MultiResolutionImage}. + */ public static boolean isMultiResolutionImage( Image image ) { return false; } + /** + * Creates a multi-resolution image from the given resolution variants. + * + * @param baseImageIndex index of the base image in the resolution variants array + * @param resolutionVariants image resolution variants (sorted by size; smallest first) + * @return a multi-resolution image on Java 9 or later; the base image on Java 8 + */ public static Image create( int baseImageIndex, Image... resolutionVariants ) { return resolutionVariants[baseImageIndex]; } + /** + * Creates a multi-resolution image for the given dimensions. + * Initially the image does not contain any image data. + * The real images are created (and cached) on demand by invoking the given producer function. + *

+ * The given dimensions array is only used for {@link #getResolutionVariants(Image)}. + * The producer function may be invoked with any dimension (that is not contained in + * dimensions array) and is expected to produce a image for the passed in dimension. + * + * @param baseImageIndex index of the base image in the dimensions array + * @param dimensions dimensions of resolution variants (sorted by size; smallest first) + * @param producer producer function that creates a real image for the requested size + * @return a multi-resolution image on Java 9 or later; the base image on Java 8 + */ + public static Image create( int baseImageIndex, Dimension[] dimensions, Function producer ) { + return producer.apply( dimensions[baseImageIndex] ); + } + + /** + * Creates a multi-resolution image that maps images from another multi-resolution image + * using the given mapper function. + *

+ * Can be used to apply filter to multi-resolution images on demand. + * E.g. passed in image is for "enabled" state and mapper function creates images + * for "disabled" state. + * + * @param image a multi-resolution image that is mapped using the given mapper function + * @param mapper mapper function that maps a single resolution variant to a new image (e.g. applying an filter) + * @return a multi-resolution image on Java 9 or later; a mapped image on Java 8 + */ public static Image map( Image image, Function mapper ) { return mapper.apply( image ); } + /** + * Get the image variant that best matches the given width and height. + *

+ * If the given image is a multi-resolution image then invokes + * {@code java.awt.image.MultiResolutionImage.getResolutionVariant(destImageWidth, destImageHeight)}. + * Otherwise returns the given image. + */ public static Image getResolutionVariant( Image image, int destImageWidth, int destImageHeight ) { return image; } + /** + * Get a list of all resolution variants. + *

+ * If the given image is a multi-resolution image then invokes + * {@code java.awt.image.MultiResolutionImage.getResolutionVariants()}. + * Otherwise returns a list containing only the given image. + */ public static List getResolutionVariants( Image image ) { return Collections.singletonList( image ); } diff --git a/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java b/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java index b23178d9..dd73d202 100644 --- a/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java +++ b/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.util; +import java.awt.Dimension; import java.awt.Image; import java.awt.image.AbstractMultiResolutionImage; import java.awt.image.BaseMultiResolutionImage; @@ -27,6 +28,14 @@ import java.util.List; import java.util.function.Function; import javax.swing.ImageIcon; +// +// NOTE: +// This implementation is for Java 9 and later. +// There is also a variant for Java 8. +// +// Make sure that the API is in sync. +// + /** * Support for multi-resolution images available since Java 9. * @@ -46,6 +55,10 @@ public class MultiResolutionImageSupport return new BaseMultiResolutionImage( baseImageIndex, resolutionVariants ); } + public static Image create( int baseImageIndex, Dimension[] dimensions, Function producer ) { + return new ProducerMultiResolutionImage( dimensions, producer ); + } + public static Image map( Image image, Function mapper ) { return image instanceof MultiResolutionImage ? new MappedMultiResolutionImage( image, mapper ) @@ -66,6 +79,9 @@ public class MultiResolutionImageSupport //---- class MappedMultiResolutionImage ----------------------------------- + /** + * A multi-resolution image implementation that maps images on demand for requested sizes. + */ private static class MappedMultiResolutionImage extends AbstractMultiResolutionImage { @@ -102,8 +118,52 @@ public class MultiResolutionImageSupport private Image mapAndCacheImage( Image image ) { return cache.computeIfAbsent( image, img -> { + // using ImageIcon here makes sure that the image is loaded return new ImageIcon( mapper.apply( img ) ).getImage(); } ); } } + + //---- class ProducerMultiResolutionImage --------------------------------- + + /** + * A multi-resolution image implementation that produces images on demand for requested sizes. + */ + private static class ProducerMultiResolutionImage + extends AbstractMultiResolutionImage + { + private final Dimension[] dimensions; + private final Function producer; + private final IdentityHashMap cache = new IdentityHashMap<>(); + + ProducerMultiResolutionImage( Dimension[] dimensions, Function producer ) { + this.dimensions = dimensions; + this.producer = producer; + } + + @Override + public Image getResolutionVariant( double destImageWidth, double destImageHeight ) { + return produceAndCacheImage( new Dimension( (int) destImageWidth, (int) destImageHeight ) ); + } + + @Override + public List getResolutionVariants() { + List mappedVariants = new ArrayList<>(); + for( Dimension size : dimensions ) + mappedVariants.add( produceAndCacheImage( size ) ); + return mappedVariants; + } + + @Override + protected Image getBaseImage() { + return produceAndCacheImage( dimensions[0] ); + } + + private Image produceAndCacheImage( Dimension size ) { + return cache.computeIfAbsent( size, size2 -> { + // using ImageIcon here makes sure that the image is loaded + return new ImageIcon( producer.apply( size2 ) ).getImage(); + } ); + } + } } diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java index c6789e84..ff7655b4 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.extras; import java.awt.Color; import java.awt.Component; +import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; @@ -30,6 +31,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.UIManager; @@ -39,6 +41,7 @@ import com.formdev.flatlaf.FlatLaf.DisabledIconProvider; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.GrayFilter; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.UIScale; import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGException; @@ -349,14 +352,33 @@ public class FlatSVGIcon public Image getImage() { update(); - BufferedImage image = new BufferedImage( getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB ); - Graphics2D g = image.createGraphics(); - try { - paintIcon( null, g, 0, 0 ); - } finally { - g.dispose(); - } - return image; + // base size + int iconWidth = getIconWidth(); + int iconHeight = getIconHeight(); + + Dimension[] dimensions = new Dimension[] { + new Dimension( iconWidth, iconHeight ), + new Dimension( iconWidth * 2, iconHeight * 2 ), + }; + + Function producer = size -> { + BufferedImage image = new BufferedImage( size.width, size.height, BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = image.createGraphics(); + try { + // scale from base size to passed size + double sx = (size.width > 0) ? (float) size.width / iconWidth : 1; + double sy = (size.height > 0) ? (float) size.height / iconHeight : 1; + if( sx != 1 || sy != 1 ) + g.scale( sx, sy ); + + paintIcon( null, g, 0, 0 ); + } finally { + g.dispose(); + } + return image; + }; + + return MultiResolutionImageSupport.create( 0, dimensions, producer ); } private static Boolean darkLaf;