在 Java Swing 中从原始图像生成 HiDPI ImageIcon
Generating HiDPI ImageIcon from original in Java Swing
我有一个应用程序,它使用 JLabel 和一个图标大小为 32x32 的 ImageIcon。
我现在想使用 64x64 图像,如果 DPI 较低,则加载它并将其调整为 32x32,否则将其用作高 DPI 图像。
调整大小很容易,这个技巧很有效,例如:
ImageIcon icon = ...
Image lowRes = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
return new ImageIcon(lowRes);
但是,我找不到将 ImageIcon 设置为高 DPI 图像的方法。
我试过使用 MultiResolutionImage 但没有成功。
编辑:尝试以天真的方式使用 MultiResolutionImage:
private ImageIcon loadIcon(String iconName)
{
ImageIcon icon = new ImageIcon(getClass().getClassLoader()
.getResource("res/icons/toolbar/" + iconName));
BaseMultiResolutionImage baseMultiResolutionImage = new BaseMultiResolutionImage(
icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH),
icon.getImage()
);
return new ImageIcon(baseMultiResolutionImage);
}
堆栈跟踪:
2019-06-11 14:00:45,962 ERROR [AWT-EventQueue-0] Catch.all - Uncaught exception on [AWT-EventQueue-0]: Invalid Image variant
java.lang.IllegalArgumentException: Invalid Image variant
at java.desktop/sun.awt.image.SurfaceManager.getManager(SurfaceManager.java:82)
at java.desktop/sun.java2d.SurfaceData.getSourceSurfaceData(SurfaceData.java:218)
at java.desktop/sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:572)
at java.desktop/sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67)
at java.desktop/sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1027)
at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3415)
at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3391)
at java.desktop/javax.swing.ImageIcon.paintIcon(ImageIcon.java:425)
at java.desktop/com.apple.laf.AquaButtonUI.paintIcon(AquaButtonUI.java:395)
at java.desktop/com.apple.laf.AquaButtonUI.paint(AquaButtonUI.java:304)
at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:590)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5262)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618)
at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1060)
at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:78)
at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:115)
at java.desktop/java.awt.Container.paint(Container.java:2002)
at java.desktop/java.awt.Window.paint(Window.java:3926)
at java.desktop/javax.swing.RepaintManager.run(RepaintManager.java:876)
at java.desktop/javax.swing.RepaintManager.run(RepaintManager.java:848)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
好的,那么问题如下:
ImageIcon
(和 ImageIcon
上的 getScaledInstance
)创建的图像属于 ToolkitImage
类型。这些不是 Swing 所期望的 BufferedImage
。
一个可行的解决方案是拍摄图像,然后将它们转换为两个 BufferedImage 实例。这是破解上面代码的丑陋解决方案:
ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource("res/icons/toolbar/" + iconName));
BufferedImage ax = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
BufferedImage bx = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics g = ax.createGraphics();
new ImageIcon(icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH)).paintIcon(null, g, 0, 0);
g.dispose();
g = bx.createGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
BaseMultiResolutionImage baseMultiResolutionImage = new BaseMultiResolutionImage(ax, bx);
return new ImageIcon(baseMultiResolutionImage);
这是获取 SVG 图标并创建 multi-resolution 图像以支持 hidpi 屏幕的示例,如先前评论中所述:
// Create a multi-resolution image with all 0.25 scaling steps up to 3x
final int size = ...; // base size = 16, 24, 32 etc.
// Create all resolution variants that Windows 10 offers by default
// Could probably drop some, e.g. 1.25 = 2.50 / 2 (Swing should handle that...)
final List< Integer > sizes = ImmutableList.of(
(int) ( size * 1.00 ), // Base image
(int) ( size * 1.25 ),
(int) ( size * 1.50 ),
(int) ( size * 1.75 ),
(int) ( size * 2.00 ),
(int) ( size * 2.25 ),
(int) ( size * 2.50 ),
(int) ( size * 2.75 ),
(int) ( size * 3.00 ) );
final byte[] rawSvgBytes = ...; // Read bytes from SVG file
Image[] images = new Image[ sizes.size() ];
for ( int isize = 0; isize < sizes.size(); isize++ )
{
// Create a PNG transcoder
PNGTranscoder t = new PNGTranscoder();
// Set the transcoding hints
t.addTranscodingHint( SVGAbstractTranscoder.KEY_WIDTH, Float.valueOf( sizes.get( isize ) ) );
t.addTranscodingHint( SVGAbstractTranscoder.KEY_HEIGHT, Float.valueOf( sizes.get( isize ) ) );
// Create the transcoder input
TranscoderInput input = new TranscoderInput();
input.setInputStream( new ByteArrayInputStream( rawSvgBytes ) );
// Create the transcoder output
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput( ostream );
// Transcode the image
t.transcode( input, output );
// Create an image and ensure its size is initialised
Image image = Toolkit.getDefaultToolkit().createImage( ostream.toByteArray() );
while ( image.getWidth( null ) == -1 )
{
// HACK! Wait for the image to be loaded, else icons may not render at the correct
// location as the width and height returned to Swing are -1
}
images[ isize ] = image;
}
return new ImageIcon( new BaseMultiResolutionImage( images ) ); // First image always the base image
我有一个应用程序,它使用 JLabel 和一个图标大小为 32x32 的 ImageIcon。
我现在想使用 64x64 图像,如果 DPI 较低,则加载它并将其调整为 32x32,否则将其用作高 DPI 图像。
调整大小很容易,这个技巧很有效,例如:
ImageIcon icon = ...
Image lowRes = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
return new ImageIcon(lowRes);
但是,我找不到将 ImageIcon 设置为高 DPI 图像的方法。
我试过使用 MultiResolutionImage 但没有成功。
编辑:尝试以天真的方式使用 MultiResolutionImage:
private ImageIcon loadIcon(String iconName)
{
ImageIcon icon = new ImageIcon(getClass().getClassLoader()
.getResource("res/icons/toolbar/" + iconName));
BaseMultiResolutionImage baseMultiResolutionImage = new BaseMultiResolutionImage(
icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH),
icon.getImage()
);
return new ImageIcon(baseMultiResolutionImage);
}
堆栈跟踪:
2019-06-11 14:00:45,962 ERROR [AWT-EventQueue-0] Catch.all - Uncaught exception on [AWT-EventQueue-0]: Invalid Image variant
java.lang.IllegalArgumentException: Invalid Image variant
at java.desktop/sun.awt.image.SurfaceManager.getManager(SurfaceManager.java:82)
at java.desktop/sun.java2d.SurfaceData.getSourceSurfaceData(SurfaceData.java:218)
at java.desktop/sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:572)
at java.desktop/sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67)
at java.desktop/sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1027)
at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3415)
at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3391)
at java.desktop/javax.swing.ImageIcon.paintIcon(ImageIcon.java:425)
at java.desktop/com.apple.laf.AquaButtonUI.paintIcon(AquaButtonUI.java:395)
at java.desktop/com.apple.laf.AquaButtonUI.paint(AquaButtonUI.java:304)
at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:590)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5262)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618)
at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1060)
at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:78)
at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:115)
at java.desktop/java.awt.Container.paint(Container.java:2002)
at java.desktop/java.awt.Window.paint(Window.java:3926)
at java.desktop/javax.swing.RepaintManager.run(RepaintManager.java:876)
at java.desktop/javax.swing.RepaintManager.run(RepaintManager.java:848)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
好的,那么问题如下:
ImageIcon
(和 ImageIcon
上的 getScaledInstance
)创建的图像属于 ToolkitImage
类型。这些不是 Swing 所期望的 BufferedImage
。
一个可行的解决方案是拍摄图像,然后将它们转换为两个 BufferedImage 实例。这是破解上面代码的丑陋解决方案:
ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource("res/icons/toolbar/" + iconName));
BufferedImage ax = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
BufferedImage bx = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics g = ax.createGraphics();
new ImageIcon(icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH)).paintIcon(null, g, 0, 0);
g.dispose();
g = bx.createGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
BaseMultiResolutionImage baseMultiResolutionImage = new BaseMultiResolutionImage(ax, bx);
return new ImageIcon(baseMultiResolutionImage);
这是获取 SVG 图标并创建 multi-resolution 图像以支持 hidpi 屏幕的示例,如先前评论中所述:
// Create a multi-resolution image with all 0.25 scaling steps up to 3x
final int size = ...; // base size = 16, 24, 32 etc.
// Create all resolution variants that Windows 10 offers by default
// Could probably drop some, e.g. 1.25 = 2.50 / 2 (Swing should handle that...)
final List< Integer > sizes = ImmutableList.of(
(int) ( size * 1.00 ), // Base image
(int) ( size * 1.25 ),
(int) ( size * 1.50 ),
(int) ( size * 1.75 ),
(int) ( size * 2.00 ),
(int) ( size * 2.25 ),
(int) ( size * 2.50 ),
(int) ( size * 2.75 ),
(int) ( size * 3.00 ) );
final byte[] rawSvgBytes = ...; // Read bytes from SVG file
Image[] images = new Image[ sizes.size() ];
for ( int isize = 0; isize < sizes.size(); isize++ )
{
// Create a PNG transcoder
PNGTranscoder t = new PNGTranscoder();
// Set the transcoding hints
t.addTranscodingHint( SVGAbstractTranscoder.KEY_WIDTH, Float.valueOf( sizes.get( isize ) ) );
t.addTranscodingHint( SVGAbstractTranscoder.KEY_HEIGHT, Float.valueOf( sizes.get( isize ) ) );
// Create the transcoder input
TranscoderInput input = new TranscoderInput();
input.setInputStream( new ByteArrayInputStream( rawSvgBytes ) );
// Create the transcoder output
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput( ostream );
// Transcode the image
t.transcode( input, output );
// Create an image and ensure its size is initialised
Image image = Toolkit.getDefaultToolkit().createImage( ostream.toByteArray() );
while ( image.getWidth( null ) == -1 )
{
// HACK! Wait for the image to be loaded, else icons may not render at the correct
// location as the width and height returned to Swing are -1
}
images[ isize ] = image;
}
return new ImageIcon( new BaseMultiResolutionImage( images ) ); // First image always the base image