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)

This commit is contained in:
Karl Tauber
2020-11-10 11:56:59 +01:00
parent 847b41752c
commit 5b8f922273
3 changed files with 159 additions and 8 deletions

View File

@@ -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.
* <p>
* 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<Dimension, Image> producer ) {
return producer.apply( dimensions[baseImageIndex] );
}
/**
* Creates a multi-resolution image that maps images from another multi-resolution image
* using the given mapper function.
* <p>
* 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<Image, Image> mapper ) {
return mapper.apply( image );
}
/**
* Get the image variant that best matches the given width and height.
* <p>
* 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.
* <p>
* 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<Image> getResolutionVariants( Image image ) {
return Collections.singletonList( image );
}

View File

@@ -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<Dimension, Image> producer ) {
return new ProducerMultiResolutionImage( dimensions, producer );
}
public static Image map( Image image, Function<Image, Image> 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<Dimension, Image> producer;
private final IdentityHashMap<Dimension, Image> cache = new IdentityHashMap<>();
ProducerMultiResolutionImage( Dimension[] dimensions, Function<Dimension, Image> 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<Image> getResolutionVariants() {
List<Image> 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();
} );
}
}
}

View File

@@ -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<Dimension, Image> 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;