在Java Swing中从原始图像生成HiDPI ImageIcon

在Java Swing中从原始图像生成HiDPI ImageIcon,java,swing,hidpi,Java,Swing,Hidpi,我有一个应用程序,它将使用JLabel和一个图标大小为32x32的ImageIcon 我现在想使用一个64x64图像,加载它并将其大小调整为32x32(如果低DPI),否则将其用作高DPI图像 调整大小很容易,例如,此技巧有效: ImageIcon icon = ... Image lowRes = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH); return new ImageIcon(lowRes); 但是,我没

我有一个应用程序,它将使用JLabel和一个图标大小为32x32的ImageIcon

我现在想使用一个64x64图像,加载它并将其大小调整为32x32(如果低DPI),否则将其用作高DPI图像

调整大小很容易,例如,此技巧有效:

ImageIcon icon = ...
Image lowRes = icon.getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH);
return new ImageIcon(lowRes);
但是,我没有找到一种方法将ImageIcon设置为高DPI图像

我尝试过玩多分辨率图像,但没有成功

编辑:尝试以简单的方式使用多分辨率图像:

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$4.run(RepaintManager.java:876)
    at java.desktop/javax.swing.RepaintManager$4.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和GetScaleInstance on ImageIcon创建的图像属于ToolkitImage类型。这些不是Swing所期望的BuffereImage

一个可行的解决方案是拍摄图像,然后将其转换为两个BuffereImage实例。以下是破解上述代码的丑陋解决方案:

  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图标并为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

“我尝试过使用多分辨率图像,但没有成功。”向我们展示代码。请解释一下当你尝试它时发生了什么。我没有找到一种方法将ImageIcon设置为高DPI图像-我不知道这意味着什么。您也可以调整图像的大小。但是质量会受到影响。@VGR我只是用高分辨率+低分辨率版本的图像创建了一个BaseMultiResolutionImage,然后将其输入到ImageIcon。我从中得到一个运行时错误。@camickr在UIKit中,举个例子,一个图像既有一个缩放因子,也有一个大小。因此,您可以创建一个缩放因子为2、大小为32x32的图像。该图像的实际像素大小为64x64。因此,您可以拍摄该图像并将其复制到64x64/比例因子1或16x16/比例因子4。请将运行时错误的完整堆栈跟踪添加到您的问题中。这是ImageIO.readMyApplication.class.getResourceres/icons/toolbar/+iconName所做的一种相当迂回的方式。我得出了相同的结论。巧合的是,我今天实现了一些非常类似的东西,除了加载一个SVG文件,然后以多种分辨率渲染,而不是使用更大的光栅图像。我没有点击无效的图像变量,因为使用ApacheBatik光栅化SVG会导致多个ToolkitImage实例,每个实例都由BuffereImage支持。我确实需要的一个怪癖是等待图像完成加载,否则getWidth可能会返回-1到Swing,从而导致它在错误的位置渲染。将发布代码。哦,这是一个非常有帮助的答案-谢谢10^6!想法:也许你可以在while循环中包含一个Thread.yield?