diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java index 95b7852a..6baeec70 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java @@ -19,11 +19,13 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.io.File; import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.filechooser.FileView; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.metal.MetalFileChooserUI; +import com.formdev.flatlaf.util.ScaledImageIcon; import com.formdev.flatlaf.util.UIScale; /** @@ -157,19 +159,32 @@ public class FlatFileChooserUI { @Override public Icon getIcon( File f ) { + // get cached icon Icon icon = getCachedIcon( f ); if( icon != null ) return icon; + // get system icon if( f != null ) { icon = getFileChooser().getFileSystemView().getSystemIcon( f ); + if( icon != null ) { + if( icon instanceof ImageIcon ) + icon = new ScaledImageIcon( (ImageIcon) icon ); cacheIcon( f, icon ); return icon; } } - return super.getIcon( f ); + // get default icon + icon = super.getIcon( f ); + + if( icon instanceof ImageIcon ) { + icon = new ScaledImageIcon( (ImageIcon) icon ); + cacheIcon( f, icon ); + } + + return icon; } } } 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 a250c2ed..259db6ff 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 @@ -17,6 +17,8 @@ package com.formdev.flatlaf.util; import java.awt.Image; +import java.util.Collections; +import java.util.List; import java.util.function.Function; /** @@ -41,4 +43,12 @@ public class MultiResolutionImageSupport public static Image map( Image image, Function mapper ) { return mapper.apply( image ); } + + public static Image getResolutionVariant( Image image, int destImageWidth, int destImageHeight ) { + return image; + } + + public static List getResolutionVariants( Image image ) { + return Collections.singletonList( image ); + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java new file mode 100644 index 00000000..91c5549f --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java @@ -0,0 +1,172 @@ +/* + * Copyright 2020 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import javax.swing.Icon; +import javax.swing.ImageIcon; + +/** + * Scales the given image icon using the system and user scale factors and + * paints the icon at system scale factor 1x. This gives best scaling quality. + * If the given image icon supports multiple resolutions, the best resolution + * variant is used. The last scaled image is cached for faster repainting. + * + * @author Karl Tauber + */ +public class ScaledImageIcon + implements Icon +{ + private final ImageIcon imageIcon; + + private double lastSystemScaleFactor; + private float lastUserScaleFactor; + private Image lastImage; + + public ScaledImageIcon( ImageIcon imageIcon ) { + this.imageIcon = imageIcon; + } + + @Override + public int getIconWidth() { + return UIScale.scale( imageIcon.getIconWidth() ); + } + + @Override + public int getIconHeight() { + return UIScale.scale( imageIcon.getIconHeight() ); + } + + @Override + public void paintIcon( Component c, Graphics g, int x, int y ) { +/*debug + g.setColor( Color.red ); + g.drawRect( x, y, getIconWidth(), getIconHeight() ); +debug*/ + + // scale factor + double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); + float userScaleFactor = UIScale.getUserScaleFactor(); + double scaleFactor = systemScaleFactor * userScaleFactor; + + // paint input image icon if not necessary to scale + if( scaleFactor == 1 ) { + imageIcon.paintIcon( c, g, x, y ); + return; + } + + // paint cached scaled icon + if( systemScaleFactor == lastSystemScaleFactor && + userScaleFactor == lastUserScaleFactor && + lastImage != null ) + { + paintLastImage( g, x, y ); + return; + } + + // destination image size + int destImageWidth = (int) Math.round( imageIcon.getIconWidth() * scaleFactor ); + int destImageHeight = (int) Math.round( imageIcon.getIconHeight() * scaleFactor ); + + // get resolution variant of image if it is a multi-resolution image + Image image = MultiResolutionImageSupport.getResolutionVariant( + imageIcon.getImage(), destImageWidth, destImageHeight ); + + // size of image + int imageWidth = image.getWidth( null ); + int imageHeight = image.getHeight( null ); + + // scale image if necessary to destination size + if( imageWidth != destImageWidth || imageHeight != destImageHeight ) { + // determine scaling method; default is "quality" + Object scalingInterpolation = RenderingHints.VALUE_INTERPOLATION_BICUBIC; + float imageScaleFactor = (float) destImageWidth / (float) imageWidth; + if( ((int) imageScaleFactor) == imageScaleFactor && + imageScaleFactor > 1f && + imageWidth <= 16 && + imageHeight <= 16 ) + { + // use "speed" scaling for small icons if the scale factor is an integer + // to avoid blurred icons + scalingInterpolation = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; + } + + // scale image + BufferedImage bufferedImage = image2bufferedImage( image ); + image = scaleImage( bufferedImage, destImageWidth, destImageHeight, scalingInterpolation ); + } + + // cache image + lastSystemScaleFactor = systemScaleFactor; + lastUserScaleFactor = userScaleFactor; + lastImage = image; + + // paint image + paintLastImage( g, x, y ); + } + + private void paintLastImage( Graphics g, int x, int y ) { + if( lastSystemScaleFactor > 1 ) { + HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, 100, 100, // width and height are not used + (g2, x2, y2, width2, height2, scaleFactor2) -> { + g2.drawImage( lastImage, x2, y2, null ); + } ); + } else + g.drawImage( lastImage, x, y, null ); + } + + /** + * Scales the given image to the target dimensions. + * + * This is the same what imgscalr library (https://github.com/rkalla/imgscalr) + * would do when invoking Scalr.resize(). + */ + private BufferedImage scaleImage( BufferedImage image, int targetWidth, int targetHeight, + Object scalingInterpolation ) + { + BufferedImage bufferedImage = new BufferedImage( targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = bufferedImage.createGraphics(); + try { + g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, scalingInterpolation ); + g.drawImage( image, 0, 0, targetWidth, targetHeight, null ); + } finally { + g.dispose(); + } + return bufferedImage; + + } + + private BufferedImage image2bufferedImage( Image image ) { + if( image instanceof BufferedImage ) + return (BufferedImage) image; + + BufferedImage bufferedImage = new BufferedImage( image.getWidth( null ), + image.getHeight( null ), BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = bufferedImage.createGraphics(); + try { + g.drawImage( image, 0, 0, null ); + } finally { + g.dispose(); + } + return bufferedImage; + } +} 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 8865c2e1..b23178d9 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 @@ -21,6 +21,7 @@ import java.awt.image.AbstractMultiResolutionImage; import java.awt.image.BaseMultiResolutionImage; import java.awt.image.MultiResolutionImage; import java.util.ArrayList; +import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.function.Function; @@ -51,6 +52,18 @@ public class MultiResolutionImageSupport : mapper.apply( image ); } + public static Image getResolutionVariant( Image image, int destImageWidth, int destImageHeight ) { + return (image instanceof MultiResolutionImage) + ? ((MultiResolutionImage)image).getResolutionVariant( destImageWidth, destImageHeight ) + : image; + } + + public static List getResolutionVariants( Image image ) { + return (image instanceof MultiResolutionImage) + ? ((MultiResolutionImage)image).getResolutionVariants() + : Collections.singletonList( image ); + } + //---- class MappedMultiResolutionImage ----------------------------------- private static class MappedMultiResolutionImage diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.java index 0d1b7328..e37944b9 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.java @@ -62,7 +62,7 @@ public class FlatChooserTest "[]", // rows "[top]" + - "[top]" + + "[grow,top]" + "[]")); //---- colorChooserLabel ---- @@ -73,7 +73,7 @@ public class FlatChooserTest //---- fileChooserLabel ---- fileChooserLabel.setText("JFileChooser:"); add(fileChooserLabel, "cell 0 1"); - add(fileChooser1, "cell 1 1"); + add(fileChooser1, "cell 1 1,growy"); //---- label1 ---- label1.setText("icons:"); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.jfd index d304aa13..6cf2bf4f 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatChooserTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.0.0.194" Java: "11.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.1.0.272" Java: "13.0.2" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -9,7 +9,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "ltr,insets dialog,hidemode 3" "$columnConstraints": "[][]" - "$rowConstraints": "[top][top][]" + "$rowConstraints": "[top][grow,top][]" } ) { name: "this" add( new FormComponent( "javax.swing.JLabel" ) { @@ -32,7 +32,7 @@ new FormModel { add( new FormComponent( "javax.swing.JFileChooser" ) { name: "fileChooser1" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 1" + "value": "cell 1 1,growy" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label1"