diff --git a/CHANGELOG.md b/CHANGELOG.md index c3018a7b..cfaf36ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ FlatLaf Change Log ================== +## Unreleased + +- Improved creation of disabled grayscale icons used in disabled buttons, labels + and tabs. They now have more contrast and are lighter in light themes and + darker in dark themes. (issue #70) + + ## 0.30 - Windows: Fixed rendering of Unicode characters. Previously not all Unicode diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index 37ee9bec..b1b18053 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -21,12 +21,16 @@ import java.awt.Component; import java.awt.Container; import java.awt.EventQueue; import java.awt.Font; +import java.awt.Image; import java.awt.KeyEventPostProcessor; import java.awt.KeyboardFocusManager; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.KeyEvent; +import java.awt.image.FilteredImageSource; +import java.awt.image.ImageFilter; +import java.awt.image.ImageProducer; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; @@ -41,6 +45,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractButton; import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JRootPane; @@ -54,12 +59,11 @@ import javax.swing.UnsupportedLookAndFeelException; import javax.swing.UIDefaults.ActiveValue; import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.FontUIResource; -import javax.swing.plaf.IconUIResource; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; -import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -123,7 +127,21 @@ public abstract class FlatLaf @Override public Icon getDisabledIcon( JComponent component, Icon icon ) { - return (icon == null) ? null : new IconUIResource( FlatUIUtils.getDisabledIcon( icon ) ); + if( icon instanceof ImageIcon ) { + Object grayFilter = UIManager.get( "Component.grayFilter" ); + if( !(grayFilter instanceof ImageFilter) ) { + // fallback + grayFilter = isDark() + ? new GrayFilter( -20, -70, 100 ) + : new GrayFilter( 25, -25, 100 ); + } + + Image image = ((ImageIcon)icon).getImage(); + ImageProducer producer = new FilteredImageSource( image.getSource(), (ImageFilter) grayFilter ); + return new ImageIconUIResource( Toolkit.getDefaultToolkit().createImage( producer ) ); + } + + return null; } @Override @@ -629,4 +647,15 @@ public abstract class FlatLaf return font; } } + + //---- class ImageIconUIResource ------------------------------------------ + + private static class ImageIconUIResource + extends ImageIcon + implements UIResource + { + ImageIconUIResource( Image image ) { + super( image ); + } + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java index da3557d5..56269090 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -41,6 +41,7 @@ import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.DerivedColor; +import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.HSLColor; import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.SystemInfo; @@ -222,7 +223,7 @@ class UIDefaultsLoader } private enum ValueType { UNKNOWN, STRING, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, - SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS } + SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER } static Object parseValue( String key, String value ) { return parseValue( key, value, v -> v, Collections.emptyList() ); @@ -289,6 +290,8 @@ class UIDefaultsLoader valueType = ValueType.CHARACTER; else if( key.endsWith( "UI" ) ) valueType = ValueType.STRING; + else if( key.endsWith( "grayFilter" ) ) + valueType = ValueType.GRAYFILTER; } // parse value @@ -308,6 +311,7 @@ class UIDefaultsLoader case SCALEDDIMENSION:return parseScaledDimension( value ); case INSTANCE: return parseInstance( value, addonClassLoaders ); case CLASS: return parseClass( value, addonClassLoaders ); + case GRAYFILTER: return parseGrayFilter( value ); case UNKNOWN: default: // colors @@ -664,6 +668,21 @@ class UIDefaultsLoader }; } + private static Object parseGrayFilter( String value ) { + List numbers = split( value, ',' ); + try { + int brightness = Integer.parseInt( numbers.get( 0 ) ); + int contrast = Integer.parseInt( numbers.get( 1 ) ); + int alpha = Integer.parseInt( numbers.get( 2 ) ); + + return (LazyValue) t -> { + return new GrayFilter( brightness, contrast, alpha ); + }; + } catch( NumberFormatException ex ) { + throw new IllegalArgumentException( "invalid gray filter '" + value + "'" ); + } + } + /** * Split string and trim parts. */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index 2ad5baf1..4264dfec 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -23,15 +23,10 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Image; import java.awt.Insets; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; -import java.awt.Toolkit; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; @@ -39,13 +34,7 @@ import java.awt.event.MouseEvent; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.FilteredImageSource; -import java.awt.image.ImageProducer; -import java.awt.image.RGBImageFilter; import java.util.function.Consumer; -import javax.swing.Icon; -import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.UIManager; @@ -444,59 +433,6 @@ public class FlatUIUtils return explicitlySet; } - public static Icon getDisabledIcon( Icon icon ) { - Image image = safeGetImage( icon ); - int grayMinValue = FlatUIUtils.getUIInt( "Button.disabledGrayMinValue", 180 ); - int grayMaxValue = FlatUIUtils.getUIInt( "Button.disabledGrayMaxValue", 215 ); - DisabledImageFilter imageFilter = new DisabledImageFilter( grayMinValue, grayMaxValue ); - ImageProducer producer = new FilteredImageSource( image.getSource(), imageFilter ); - return new ImageIcon( Toolkit.getDefaultToolkit().createImage( producer ) ); - } - - private static Image safeGetImage( Icon icon ) { - if( icon instanceof ImageIcon ) { - return ((ImageIcon)icon).getImage(); - } else { - int width = icon.getIconWidth(); - int height = icon.getIconHeight(); - GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice device = environment.getDefaultScreenDevice(); - GraphicsConfiguration configuration = device.getDefaultConfiguration(); - BufferedImage image = configuration.createCompatibleImage( width, height ); - Graphics2D g = image.createGraphics(); - icon.paintIcon( null, g, 0, 0 ); - g.dispose(); - return image; - } - } - - //---- class DisabledImageFilter ------------------------------------------ - - private static class DisabledImageFilter - extends RGBImageFilter - { - private final float min; - private final float factor; - - DisabledImageFilter( int min, int max ) { - this.min = min; - this.factor = (max - min) / 255f; - - canFilterIndexColorModel = true; - } - - @Override - public int filterRGB( int x, int y, int rgb ) { - // https://en.wikipedia.org/wiki/Grayscale - float linearLuminance = - (0.2126f * ((rgb >> 16) & 0xff)) + - (0.7152f * ((rgb >> 8) & 0xff)) + - (0.0722f * (rgb & 0xff)); - int gray = Math.min( (int) ((linearLuminance + .5f) * factor + min), 255 ); - return (rgb & 0xff000000) | (gray << 16) | (gray << 8) | gray; - } - } - //---- class HoverListener ------------------------------------------------ public static class HoverListener diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/GrayFilter.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/GrayFilter.java new file mode 100644 index 00000000..00bd4850 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/GrayFilter.java @@ -0,0 +1,81 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.formdev.flatlaf.util; + +import java.awt.image.RGBImageFilter; + +// based on https://github.com/JetBrains/intellij-community/blob/3840eab54746f5c4f301bb3ac78f00a980b5fd6e/platform/util/ui/src/com/intellij/util/ui/UIUtil.java#L253-L347 + +/** + * An image filter that turns an image into a grayscale image. + * Used for icons in disabled buttons and labels. + */ +public class GrayFilter + extends RGBImageFilter +{ + private final float brightness; + private final float contrast; + private final int alpha; + + private final int origContrast; + private final int origBrightness; + + /** + * @param brightness in range [-100..100] where 0 has no effect + * @param contrast in range [-100..100] where 0 has no effect + * @param alpha in range [0..100] where 0 is transparent, 100 has no effect + */ + public GrayFilter( int brightness, int contrast, int alpha ) { + this.origBrightness = Math.max( -100, Math.min( 100, brightness ) ); + this.origContrast = Math.max( -100, Math.min( 100, contrast ) ); + this.alpha = Math.max( 0, Math.min( 100, alpha ) ); + + this.brightness = (float) (Math.pow( origBrightness, 3 ) / (100f * 100f)); // cubic in [0..100] + this.contrast = origContrast / 100f; + + canFilterIndexColorModel = true; + } + + public GrayFilter() { + this( 0, 0, 100 ); + } + + public int getBrightness() { + return origBrightness; + } + + public int getContrast() { + return origContrast; + } + + public int getAlpha() { + return alpha; + } + + @Override + public int filterRGB( int x, int y, int rgb ) { + // use NTSC conversion formula + int gray = (int)( + 0.30 * (rgb >> 16 & 0xff) + + 0.59 * (rgb >> 8 & 0xff) + + 0.11 * (rgb & 0xff)); + + if( brightness >= 0 ) + gray = (int) ((gray + brightness * 255) / (1 + brightness)); + else + gray = (int) (gray / (1 - brightness)); + + if( contrast >= 0 ) { + if( gray >= 127 ) + gray = (int) (gray + (255 - gray) * contrast); + else + gray = (int) (gray - gray * contrast); + } else + gray = (int) (127 + (gray - 127) * (contrast + 1)); + + int a = (alpha != 100) + ? (((rgb >> 24) & 0xff) * alpha / 100) << 24 + : (rgb & 0xff000000); + + return a | (gray << 16) | (gray << 8) | gray; + } +} diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties index 014cf7a4..bd5a7be3 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties @@ -129,6 +129,7 @@ Component.disabledBorderColor=#646464 Component.focusedBorderColor=#466d94 Component.focusColor=#3d6185 Component.linkColor=#589df6 +Component.grayFilter=-20,-70,100 #---- Desktop ---- diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties index c643863f..9f75255c 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties @@ -131,6 +131,7 @@ Component.disabledBorderColor=#cfcfcf Component.focusedBorderColor=#87afda Component.focusColor=#97c3f3 Component.linkColor=#2470B3 +Component.grayFilter=25,-25,100 #---- Desktop ---- diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties index b70f564d..1136d937 100644 --- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties +++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties @@ -128,6 +128,7 @@ Component.focusedBorderColor=#466d94 Component.focusColor=#97c3f3 #Component.focusWidth=5 #Component.arc=8 +Component.grayFilter=25,25,100 #---- Desktop ----