Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FlatSVGIcon color filters #303

Merged
merged 4 commits into from
Apr 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@

import javax.swing.*;
import com.formdev.flatlaf.extras.*;
import com.formdev.flatlaf.extras.FlatSVGIcon.ColorFilter;
import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox;
import com.formdev.flatlaf.util.GrayFilter;
import net.miginfocom.swing.*;
import java.awt.*;

/**
* @author Karl Tauber
*/
public class ExtrasPanel
extends JPanel
{
public int counter = 0;

public ExtrasPanel() {
initComponents();

Expand Down Expand Up @@ -69,6 +74,12 @@ private void initComponents() {
label2 = new JLabel();
svgIconsPanel = new JPanel();
label3 = new JLabel();
separator1 = new JSeparator();
label5 = new JLabel();
label6 = new JLabel();
label7 = new JLabel();
rainbowIcon = new JLabel();
toggleButton1 = new JToggleButton();

//======== this ========
setLayout(new MigLayout(
Expand All @@ -81,6 +92,9 @@ private void initComponents() {
"[]para" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]"));

//---- label4 ----
Expand Down Expand Up @@ -119,9 +133,55 @@ private void initComponents() {
//---- label3 ----
label3.setText("The icons may change colors when switching to another theme.");
add(label3, "cell 1 3 2 1");

add(separator1, "cell 1 4, grow");

//---- label5 ----
label5.setText("Color filters can be also applied to icons. Globally or for each instance.");
add(label5, "cell 1 5");

//---- label6 ----
label6.setText( "Rainbow color filter" );
add(label6, "cell 1 6");

//---- rainbowIcon ----
rainbowIcon = createRainbowIcon("informationDialog.svg");
add(rainbowIcon, "cell 1 6");

//---- label7 ----
label7.setText( "Global icon color filter" );
add(label7, "cell 1 7");

// ---- button1 ----
toggleButton1.setText( "Toggle brighter" );
add(toggleButton1, "cell 1 7");

// ---- toggleButton1 ----
toggleButton1.addActionListener( (e) -> {
if (toggleButton1.isSelected())
FlatSVGIcon.ColorFilter.getInstance().setFilter( color -> color.brighter().brighter() );
else
FlatSVGIcon.ColorFilter.getInstance().setFilter( null );
SwingUtilities.getRootPane( toggleButton1 ).repaint();
} );

// JFormDesigner - End of component initialization //GEN-END:initComponents
}

private JLabel createRainbowIcon(String name) {
FlatSVGIcon rainbowIcon = new FlatSVGIcon( "com/formdev/flatlaf/demo/extras/svg/" + name);
rainbowIcon.setFilter( new ColorFilter( (color) -> {
counter+=1;
counter%=255;
return Color.getHSBColor(counter/255f, 1, 1);
}) );
JLabel label = new JLabel(rainbowIcon);
new Timer(30, (e) -> {
label.repaint();
}).start();
return label;
}

// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JLabel label4;
private JLabel label1;
Expand All @@ -130,5 +190,11 @@ private void initComponents() {
private JLabel label2;
private JPanel svgIconsPanel;
private JLabel label3;
private JLabel label5;
private JLabel label6;
private JLabel label7;
private JSeparator separator1;
private JLabel rainbowIcon;
private JToggleButton toggleButton1;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class FlatSVGIcon
private final boolean disabled;
private final ClassLoader classLoader;

private ColorFilter userColorFilter = null;

private SVGDiagram diagram;
private boolean dark;

Expand Down Expand Up @@ -159,7 +161,7 @@ public FlatSVGIcon( String name, float scale, ClassLoader classLoader ) {
this( name, -1, -1, scale, false, classLoader );
}

private FlatSVGIcon( String name, int width, int height, float scale, boolean disabled, ClassLoader classLoader ) {
protected FlatSVGIcon( String name, int width, int height, float scale, boolean disabled, ClassLoader classLoader ) {
this.name = name;
this.classLoader = classLoader;
this.width = width;
Expand All @@ -168,6 +170,32 @@ private FlatSVGIcon( String name, int width, int height, float scale, boolean di
this.disabled = disabled;
}

/**
* Sets a color filter that can freely modify colors of this icon during painting.<br>
* <br>
* This method accepts a {@link ColorFilter}. Usually you would want to use a ColorFilter created using the
* {@link ColorFilter#ColorFilter(Function)} constructor.<br>
* <br>
* This can be used to brighten colors of the icon:
* <pre>icon.setFilter( new FlatSVGIcon.ColorFilter( color -> color.brighter() ) );</pre><br>
* <br>
* Using a filter, icons can also be turned monochrome (painted with a single color):
* <pre>icon.setFilter( new FlatSVGIcon.ColorFilter( color -> Color.RED ) );</pre><br>
* <br>
* Note: If a filter is already set, it will be replaced.
* @param filter The color filter
*/
public void setFilter(ColorFilter filter) {
this.userColorFilter = filter;
}

/**
* @return The currently active {@link ColorFilter} or null.
*/
public ColorFilter getFilter() {
return userColorFilter;
}

/**
* Creates a new icon with given width and height, which is derived from this icon.
*
Expand Down Expand Up @@ -303,7 +331,7 @@ public void paintIcon( Component c, Graphics g, int x, int y ) {
: GrayFilter.createDisabledIconFilter( dark );
}

Graphics2D g2 = new GraphicsFilter( (Graphics2D) g.create(), ColorFilter.getInstance(), grayFilter );
Graphics2D g2 = new GraphicsFilter( (Graphics2D) g.create(), ColorFilter.getInstance(), this.userColorFilter, grayFilter );

try {
FlatUIUtils.setRenderingHints( g2 );
Expand Down Expand Up @@ -401,37 +429,131 @@ private static void lafChanged() {

//---- class ColorFilter --------------------------------------------------

/**
* A color filter that can modify colors of a painted {@link FlatSVGIcon}.<br>
* <br>
* The ColorFilter modifes color in two ways.<br>
* Either using a color map, where specific colors are mapped to different ones.<br>
* And/or by modifying the colors directly, applying a modification to their rgba values.<br>
* <br>
* When filtering a color. Mappings are applied first, then an rgb color filter is applied.<br>
* <br>
* Global {@link FlatSVGIcon} ColorFilter can be retrieved using the {@link ColorFilter#getInstance()} method.
*
* @see ColorFilter#ColorFilter(Function)
* @see ColorFilter#ColorFilter(boolean)
*/
public static class ColorFilter
{
private static ColorFilter instance;

//Color maps
private final Map<Integer, String> rgb2keyMap = new HashMap<>();
private final Map<Color, Color> color2colorMap = new HashMap<>();

//Color modification
private Function<Color, Color> colorFilter = null;

/**
* Returns the global ColorFilter instance. If one doesn't exist, a new one is created with default color maps.
*/
public static ColorFilter getInstance() {
if( instance == null )
instance = new ColorFilter();
return instance;
}

/**
* Creates a color filter with default color mappings.
*/
public ColorFilter() {
for( FlatIconColors c : FlatIconColors.values() )
rgb2keyMap.put( c.rgb, c.key );
this(true);
}

/**
* Creates a color filter. Default color maps can be optionally created.
* @param createDefaultColorMaps If true, default FlatLaf color maps are created.
*/
public ColorFilter(boolean createDefaultColorMaps) {
if (createDefaultColorMaps) {
for( FlatIconColors c : FlatIconColors.values() )
rgb2keyMap.put( c.rgb, c.key );
}
}

/**
* Creates a color filter with a color modifying function that changes painted colors.<br>
* The {@link Function} gets passed the original color and returns a modified one.
* <br>
* Examples:
* A ColorFilter can be used to brighten colors of the icon:
* <pre>new ColorFilter( color -> color.brighter() );</pre>
* <br>
* Using a ColorFilter, icons can also be turned monochrome (painted with a single color):
* <pre>new ColorFilter( color -> Color.RED );</pre>
* <br>
* @param filter The color filter function
*/
public ColorFilter(Function<Color, Color> filter) {
setFilter(filter);
}

/**
* Sets a color modifying function that changes painted colors.<br>
* The {@link Function} gets passed the original color and returns a modified one.
* <br>
* Examples:
* A ColorFilter can be used to brighten colors of the icon:
* <pre>filter.setFilter( color -> color.brighter() );</pre>
* <br>
* Using a ColorFilter, icons can also be turned monochrome (painted with a single color):
* <pre>filter.setFilter( color -> Color.RED );</pre>
* <br>
* @param filter The color filter function
*/
public void setFilter(Function<Color, Color> filter) {
this.colorFilter = filter;
}

/**
* Adds a color mappings.
*/
public void addAll( Map<Color, Color> from2toMap ) {
color2colorMap.putAll( from2toMap );
}

/**
* Adds a color mapping.
*/
public void add( Color from, Color to ) {
color2colorMap.put( from, to );
}

/**
* Removes a specific color mapping.
*/
public void remove( Color from ) {
color2colorMap.remove( from );
}

/**
* Removes all current color mappings.
*/
public void removeAll() {
for ( Color from : color2colorMap.keySet()) {
color2colorMap.remove( from );
}
}

public Color filter( Color color ) {
color = filterMappings( color );
if (colorFilter != null) {
color = colorFilter.apply( color );
}
return color;
};

protected Color filterMappings( Color color ) {
Color newColor = color2colorMap.get( color );
if( newColor != null )
return newColor;
Expand All @@ -447,7 +569,20 @@ public Color filter( Color color ) {
return (newColor.getAlpha() != color.getAlpha())
? new Color( (newColor.getRGB() & 0x00ffffff) | (color.getRGB() & 0xff000000) )
: newColor;
};
}

/**
* Creates a color modifying function that changes color brightness, contrast and alpha.
* Can be set to a {@link ColorFilter} using {@link ColorFilter#setFilter(Function)}.
*
* @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
* @see GrayFilter
*/
public static Function<Color, Color> createGrayFilterFunction(int brightness, int contrast, int alpha) {
return color -> new Color(new GrayFilter(brightness, contrast, alpha).filterRGB( 0, 0, color.getRGB() ));
}
}

//---- class GraphicsFilter -----------------------------------------------
Expand All @@ -457,11 +592,14 @@ private static class GraphicsFilter
{
private final ColorFilter colorFilter;
private final RGBImageFilter grayFilter;
private final ColorFilter userFilter;

public GraphicsFilter( Graphics2D delegate, ColorFilter colorFilter, RGBImageFilter grayFilter ) {
public GraphicsFilter( Graphics2D delegate, ColorFilter globalColorFilter, ColorFilter userColorFilter,
RGBImageFilter grayFilter ) {
super( delegate );
this.colorFilter = colorFilter;
this.colorFilter = globalColorFilter;
this.grayFilter = grayFilter;
this.userFilter = userColorFilter;
}

@Override
Expand All @@ -477,6 +615,8 @@ public void setPaint( Paint paint ) {
}

private Color filterColor( Color color ) {
if( userFilter != null )
color = userFilter.filter( color );
if( colorFilter != null )
color = colorFilter.filter( color );
if( grayFilter != null ) {
Expand Down