improved creation of disabled grayscale icons (issue #70)

This commit is contained in:
Karl Tauber
2020-04-24 00:46:16 +02:00
parent a39ae5a8c5
commit 0660f9a511
8 changed files with 143 additions and 68 deletions

View File

@@ -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

View File

@@ -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 );
}
}
}

View File

@@ -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<String> 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.
*/

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -129,6 +129,7 @@ Component.disabledBorderColor=#646464
Component.focusedBorderColor=#466d94
Component.focusColor=#3d6185
Component.linkColor=#589df6
Component.grayFilter=-20,-70,100
#---- Desktop ----

View File

@@ -131,6 +131,7 @@ Component.disabledBorderColor=#cfcfcf
Component.focusedBorderColor=#87afda
Component.focusColor=#97c3f3
Component.linkColor=#2470B3
Component.grayFilter=25,-25,100
#---- Desktop ----

View File

@@ -128,6 +128,7 @@ Component.focusedBorderColor=#466d94
Component.focusColor=#97c3f3
#Component.focusWidth=5
#Component.arc=8
Component.grayFilter=25,25,100
#---- Desktop ----