FlatTitlePaneIcon: use getResolutionVariant(width, height) instead of getResolutionVariants() to allow creation of requested size on demand and to avoids creation of all resolution variants

Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single multi-resolution image that creates requested image sizes on demand from SVG

(issue #323)
This commit is contained in:
Karl Tauber
2021-05-14 16:43:47 +02:00
parent 439e63b52f
commit 29e1dc6b55
7 changed files with 145 additions and 35 deletions

View File

@@ -32,6 +32,9 @@ FlatLaf Change Log
- Getters for icon name, classloader, etc. - Getters for icon name, classloader, etc.
- Extras: UI Inspector: Show class hierarchies when pressing <kbd>Alt</kbd> key - Extras: UI Inspector: Show class hierarchies when pressing <kbd>Alt</kbd> key
and prettified class names (dimmed package name). and prettified class names (dimmed package name).
- Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single
multi-resolution image that creates requested image sizes on demand from SVG
(Java 9+).
#### Fixed bugs #### Fixed bugs
@@ -58,6 +61,10 @@ FlatLaf Change Log
dialogs. (issue #315) dialogs. (issue #315)
- Fixed broken maximizing window (under special conditions) when restoring - Fixed broken maximizing window (under special conditions) when restoring
frame state at startup. frame state at startup.
- Title icon: For multi-resolution images now use `getResolutionVariant(width,
height)` (instead of `getResolutionVariants()`) to allow creation of
requested size on demand. This also avoids creation of all resolution
variants.
- Linux: Fixed/improved detection of user font settings. (issue #309) - Linux: Fixed/improved detection of user font settings. (issue #309)

View File

@@ -351,7 +351,7 @@ public class FlatTitlePane
// set icon // set icon
if( !images.isEmpty() ) if( !images.isEmpty() )
iconLabel.setIcon( FlatTitlePaneIcon.create( images, iconSize ) ); iconLabel.setIcon( new FlatTitlePaneIcon( images, iconSize ) );
else { else {
// no icon set on window --> use default icon // no icon set on window --> use default icon
Icon defaultIcon = UIManager.getIcon( "TitlePane.icon" ); Icon defaultIcon = UIManager.getIcon( "TitlePane.icon" );

View File

@@ -20,8 +20,6 @@ import java.awt.Dimension;
import java.awt.Image; import java.awt.Image;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.ScaledImageIcon; import com.formdev.flatlaf.util.ScaledImageIcon;
@@ -31,40 +29,43 @@ import com.formdev.flatlaf.util.ScaledImageIcon;
public class FlatTitlePaneIcon public class FlatTitlePaneIcon
extends ScaledImageIcon extends ScaledImageIcon
{ {
public static Icon create( List<Image> images, Dimension size ) { private final List<Image> images;
// collect all images including multi-resolution variants
/**
* @since 1.2
*/
public FlatTitlePaneIcon( List<Image> images, Dimension size ) {
super( null, size.width, size.height );
this.images = images;
}
@Override
protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) {
// collect all images including multi-resolution variants for requested size
List<Image> allImages = new ArrayList<>(); List<Image> allImages = new ArrayList<>();
for( Image image : images ) { for( Image image : images ) {
if( MultiResolutionImageSupport.isMultiResolutionImage( image ) ) if( MultiResolutionImageSupport.isMultiResolutionImage( image ) )
allImages.addAll( MultiResolutionImageSupport.getResolutionVariants( image ) ); allImages.add( MultiResolutionImageSupport.getResolutionVariant( image, destImageWidth, destImageHeight ) );
else else
allImages.add( image ); allImages.add( image );
} }
if( allImages.size() == 1 )
return allImages.get( 0 );
// sort images by size // sort images by size
allImages.sort( (image1, image2) -> { allImages.sort( (image1, image2) -> {
return image1.getWidth( null ) - image2.getWidth( null ); return image1.getWidth( null ) - image2.getWidth( null );
} ); } );
// create icon // search for optimal image size
return new FlatTitlePaneIcon( allImages, size ); for( Image image : allImages ) {
}
private final List<Image> images;
private FlatTitlePaneIcon( List<Image> images, Dimension size ) {
super( new ImageIcon( images.get( 0 ) ), size.width, size.height );
this.images = images;
}
@Override
protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) {
for( Image image : images ) {
if( destImageWidth <= image.getWidth( null ) && if( destImageWidth <= image.getWidth( null ) &&
destImageHeight <= image.getHeight( null ) ) destImageHeight <= image.getHeight( null ) )
return image; return image;
} }
return images.get( images.size() - 1 ); // use largest image
return allImages.get( allImages.size() - 1 );
} }
} }

View File

@@ -77,7 +77,7 @@ debug*/
double scaleFactor = systemScaleFactor * userScaleFactor; double scaleFactor = systemScaleFactor * userScaleFactor;
// paint input image icon if not necessary to scale // paint input image icon if not necessary to scale
if( scaleFactor == 1 && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) { if( scaleFactor == 1 && imageIcon != null && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) {
imageIcon.paintIcon( c, g, x, y ); imageIcon.paintIcon( c, g, x, y );
return; return;
} }

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.extras; package com.formdev.flatlaf.extras;
import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.RenderingHints; import java.awt.RenderingHints;
@@ -23,8 +24,10 @@ import java.awt.image.BufferedImage;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.JWindow; import javax.swing.JWindow;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.kitfox.svg.SVGCache; import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException; import com.kitfox.svg.SVGException;
@@ -40,22 +43,51 @@ public class FlatSVGUtils
* Creates from the given SVG a list of icon images with different sizes that * Creates from the given SVG a list of icon images with different sizes that
* can be used for windows headers. The SVG should have a size of 16x16, * can be used for windows headers. The SVG should have a size of 16x16,
* otherwise it is scaled. * otherwise it is scaled.
* <p>
* If running on Java 9 or later and multi-resolution image support is available,
* then a single multi-resolution image is returned that creates images on demand
* for requested sizes from SVG.
* This has the advantage that only images for used sizes are created.
* Also if unusual sizes are requested (e.g. 18x18), then they are created from SVG.
* *
* @param svgName the name of the SVG resource (a '/'-separated path) * @param svgName the name of the SVG resource (a '/'-separated path)
* @return list of icon images with different sizes (16x16, 24x24, 32x32, 48x48 and 64x64) * @return list of icon images with different sizes (16x16, 20x20, 24x24, 28x28, 32x32, 48x48 and 64x64)
* @throws RuntimeException if failed to load or render SVG file * @throws RuntimeException if failed to load or render SVG file
* @see JWindow#setIconImages(List) * @see JWindow#setIconImages(List)
*/ */
public static List<Image> createWindowIconImages( String svgName ) { public static List<Image> createWindowIconImages( String svgName ) {
SVGDiagram diagram = loadSVG( svgName ); SVGDiagram diagram = loadSVG( svgName );
return Arrays.asList( if( MultiResolutionImageSupport.isAvailable() ) {
svg2image( diagram, 16, 16 ), // use a multi-resolution image that creates images on demand for requested sizes
svg2image( diagram, 24, 24 ), return Collections.singletonList( MultiResolutionImageSupport.create( 0,
svg2image( diagram, 32, 32 ), new Dimension[] {
svg2image( diagram, 48, 48 ), // Listing all these sizes here is actually not necessary because
svg2image( diagram, 64, 64 ) // any size is created on demand when
); // MultiResolutionImage.getResolutionVariant(double destImageWidth, double destImageHeight)
// is invoked.
// This sizes are only used by MultiResolutionImage.getResolutionVariants().
new Dimension( 16, 16 ), // 100%
new Dimension( 20, 20 ), // 125%
new Dimension( 24, 24 ), // 150%
new Dimension( 28, 28 ), // 175%
new Dimension( 32, 32 ), // 200%
new Dimension( 48, 48 ), // 300%
new Dimension( 64, 64 ), // 400%
}, dim -> {
return svg2image( diagram, dim.width, dim.height );
} ) );
} else {
return Arrays.asList(
svg2image( diagram, 16, 16 ), // 100%
svg2image( diagram, 20, 20 ), // 125%
svg2image( diagram, 24, 24 ), // 150%
svg2image( diagram, 28, 28 ), // 175%
svg2image( diagram, 32, 32 ), // 200%
svg2image( diagram, 48, 48 ), // 300%
svg2image( diagram, 64, 64 ) // 400%
);
}
} }
/** /**

View File

@@ -19,12 +19,16 @@ package com.formdev.flatlaf.testing;
import java.awt.*; import java.awt.*;
import java.awt.Dialog.ModalityType; import java.awt.Dialog.ModalityType;
import java.awt.event.*; import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import javax.swing.*; import javax.swing.*;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import net.miginfocom.swing.*; import net.miginfocom.swing.*;
/** /**
@@ -355,6 +359,32 @@ public class FlatWindowDecorationsTest
window.setIconImages( images ); window.setIconImages( images );
else if( iconTestRandomRadioButton.isSelected() ) else if( iconTestRandomRadioButton.isSelected() )
window.setIconImage( images.get( (int) (Math.random() * images.size()) ) ); window.setIconImage( images.get( (int) (Math.random() * images.size()) ) );
else if( iconTestMRIRadioButton.isSelected() ) {
ArrayList<Image> sortedImages = new ArrayList<>( images );
sortedImages.sort( (image1, image2) -> {
return image1.getWidth( null ) - image2.getWidth( null );
} );
window.setIconImage( MultiResolutionImageSupport.create( 0, sortedImages.toArray( new Image[sortedImages.size()] ) ) );
} else if( iconTestDynMRIRadioButton.isSelected() ) {
window.setIconImage( MultiResolutionImageSupport.create( 0,
new Dimension[] {
new Dimension( 16, 16 ),
}, dim -> {
BufferedImage image = new BufferedImage( dim.width, dim.height, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = image.createGraphics();
try {
g.setColor( Color.getHSBColor( (dim.width - 16) / 64f, 1, 0.8f ) );
g.fillRect( 0, 0, dim.width, dim.height );
g.setColor( Color.white );
g.setFont( new Font( "Dialog", Font.PLAIN, (int) (dim.width * 0.8) ) );
FlatUIUtils.drawString( this, g, String.valueOf( dim.width ), 0, dim.height - 2 );
} finally {
g.dispose();
}
return image;
} ) );
}
} }
private JRootPane getWindowRootPane() { private JRootPane getWindowRootPane() {
@@ -403,6 +433,8 @@ public class FlatWindowDecorationsTest
iconNoneRadioButton = new JRadioButton(); iconNoneRadioButton = new JRadioButton();
iconTestAllRadioButton = new JRadioButton(); iconTestAllRadioButton = new JRadioButton();
iconTestRandomRadioButton = new JRadioButton(); iconTestRandomRadioButton = new JRadioButton();
iconTestMRIRadioButton = new JRadioButton();
iconTestDynMRIRadioButton = new JRadioButton();
JButton openDialogButton = new JButton(); JButton openDialogButton = new JButton();
JButton openFrameButton = new JButton(); JButton openFrameButton = new JButton();
menuBar = new JMenuBar(); menuBar = new JMenuBar();
@@ -642,6 +674,8 @@ public class FlatWindowDecorationsTest
// rows // rows
"[]" + "[]" +
"[]" + "[]" +
"[]" +
"[]" +
"[]")); "[]"));
//---- iconNoneRadioButton ---- //---- iconNoneRadioButton ----
@@ -659,6 +693,16 @@ public class FlatWindowDecorationsTest
iconTestRandomRadioButton.setText("test random"); iconTestRandomRadioButton.setText("test random");
iconTestRandomRadioButton.addActionListener(e -> iconChanged()); iconTestRandomRadioButton.addActionListener(e -> iconChanged());
panel2.add(iconTestRandomRadioButton, "cell 0 2"); panel2.add(iconTestRandomRadioButton, "cell 0 2");
//---- iconTestMRIRadioButton ----
iconTestMRIRadioButton.setText("test multi-resolution (Java 9+)");
iconTestMRIRadioButton.addActionListener(e -> iconChanged());
panel2.add(iconTestMRIRadioButton, "cell 0 3");
//---- iconTestDynMRIRadioButton ----
iconTestDynMRIRadioButton.setText("test dynamic multi-resolution (Java 9+)");
iconTestDynMRIRadioButton.addActionListener(e -> iconChanged());
panel2.add(iconTestDynMRIRadioButton, "cell 0 4");
} }
add(panel2, "cell 1 8"); add(panel2, "cell 1 8");
@@ -858,6 +902,8 @@ public class FlatWindowDecorationsTest
iconButtonGroup.add(iconNoneRadioButton); iconButtonGroup.add(iconNoneRadioButton);
iconButtonGroup.add(iconTestAllRadioButton); iconButtonGroup.add(iconTestAllRadioButton);
iconButtonGroup.add(iconTestRandomRadioButton); iconButtonGroup.add(iconTestRandomRadioButton);
iconButtonGroup.add(iconTestMRIRadioButton);
iconButtonGroup.add(iconTestDynMRIRadioButton);
// JFormDesigner - End of component initialization //GEN-END:initComponents // JFormDesigner - End of component initialization //GEN-END:initComponents
} }
@@ -892,6 +938,8 @@ public class FlatWindowDecorationsTest
private JRadioButton iconNoneRadioButton; private JRadioButton iconNoneRadioButton;
private JRadioButton iconTestAllRadioButton; private JRadioButton iconTestAllRadioButton;
private JRadioButton iconTestRandomRadioButton; private JRadioButton iconTestRandomRadioButton;
private JRadioButton iconTestMRIRadioButton;
private JRadioButton iconTestDynMRIRadioButton;
private JMenuBar menuBar; private JMenuBar menuBar;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
} }

View File

@@ -328,7 +328,7 @@ new FormModel {
} ) } )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$columnConstraints": "[fill]" "$columnConstraints": "[fill]"
"$rowConstraints": "[][][]" "$rowConstraints": "[][][][][]"
"$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0" "$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0"
} ) { } ) {
name: "panel2" name: "panel2"
@@ -366,6 +366,28 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2" "value": "cell 0 2"
} ) } )
add( new FormComponent( "javax.swing.JRadioButton" ) {
name: "iconTestMRIRadioButton"
"text": "test multi-resolution (Java 9+)"
"$buttonGroup": new FormReference( "iconButtonGroup" )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
add( new FormComponent( "javax.swing.JRadioButton" ) {
name: "iconTestDynMRIRadioButton"
"text": "test dynamic multi-resolution (Java 9+)"
"$buttonGroup": new FormReference( "iconButtonGroup" )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 8" "value": "cell 1 8"
} ) } )
@@ -386,7 +408,7 @@ new FormModel {
} ) } )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 690, 440 ) "size": new java.awt.Dimension( 690, 495 )
} ) } )
add( new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) { add( new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) {
name: "menuBar" name: "menuBar"
@@ -552,18 +574,18 @@ new FormModel {
} ) } )
} ) } )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 470 ) "location": new java.awt.Point( 0, 515 )
"size": new java.awt.Dimension( 255, 30 ) "size": new java.awt.Dimension( 255, 30 )
} ) } )
add( new FormNonVisual( "javax.swing.ButtonGroup" ) { add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
name: "styleButtonGroup" name: "styleButtonGroup"
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 520 ) "location": new java.awt.Point( 0, 565 )
} ) } )
add( new FormNonVisual( "javax.swing.ButtonGroup" ) { add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
name: "iconButtonGroup" name: "iconButtonGroup"
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 570 ) "location": new java.awt.Point( 0, 615 )
} ) } )
} }
} }