什么导致Java JLabel图标的图像质量差?

9

JFrame 中,Java 的 JLabel 图标显示像素失真。使用不同的 png 图像(均为 32x32)也会出现这种情况。我没有对图像进行缩放,它们在程序中以 32x32 的大小显示,并且使用 getWidthgetHeight 验证了它们的大小。这些失真会在每次运行程序时出现在相同的位置,而不是随机出现。

以下是提供的示例代码的屏幕截图。
example screenshot (1)

您可以在此屏幕截图中看到一组 JLabel 图标,每个图标都受到不同程度的影响。
example screenshot (2)

当从侧面调整窗口大小时,由于图标随着窗口移动,失真会像一条垂直线一样移动到图标上。

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

public class FrameApp extends JFrame
{
    public static void main(String[] args) throws IOException
    {
        FrameApp frameApp = new FrameApp();
    }

    private FrameApp() throws IOException
    {
        BufferedImage image;

        URL url = new URL("http://i.stack.imgur.com/L5DGx.png");
        image = ImageIO.read(url);

        JLabel label = new JLabel(new ImageIcon(image));

        add(label);

        pack();
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }
}

编辑:
我正在使用JDK 11.0.3,Java SE Runtime Environment构建1.8.0_202,在Windows 8.1 64位上运行。


2
  1. 为了更快地获得帮助,请[编辑]以添加[MCVE]或简短,自包含,正确的示例。2) 获取示例图像的一种方法是热链接到在此问答中看到的图像。例如,此答案热链接到嵌入在此问题中的图像。
- Andrew Thompson
@AndrewThompson,我按照您的建议正确编辑了我的问题,但仍然没有得到答案,并且自那以后被投票降低了。这个问题是否有些不太合适?您认为我应该放弃寻找答案吗?谢谢。 - IamJohnGalt
你使用的Java版本是哪个?我尝试使用你的示例进行复制,但图像看起来完美无缺(openjdk 11.0.1)。 - dpr
@matt 我从IntelliJ的“帮助->关于”中获取了这些信息。以下是完整的内容: IntelliJ IDEA 2019.1.3(社区版)
构建#IC-191.7479.19,于2019年5月28日构建
JRE:1.8.0_202-release-1483-b58 amd64
JVM:由JetBrains s.r.o提供的OpenJDK 64位服务器VM
Windows 8.1 6.3
- IamJohnGalt
我曾经遇到过同样的问题,但是我在另一台屏幕上尝试了我的应用程序,在我的笔记本电脑上,图像显示正确,没有模糊。 - bashizip
显示剩余3条评论
3个回答

4
你可能认为你正在以32x32的大小显示图像,但是你贴瓷砖图像的示例表明并非如此。你有一个9x2的图标网格,应该是288x64像素,但在你的示例图像中,网格是302x66。
如果仔细检查你的贴瓷砖图像,你会发现单个图块被显示为34像素宽-看到从32像素到66像素延伸的品红色边框。(注意,一些图块显示为33像素宽;它似乎是33、34、34、33、34...)
为了将图块拉伸到更宽的宽度,某些列被加倍(红色边框),这就创建了你所看到的视觉故障。

marked up portion of provided sample image

你是否尝试过固定JLabel的大小,而不是让它根据内容自动调整大小?

干得好,注意到了这一点。我尝试显式调整JLabel的大小为所需的32x32,以及更大和更小的尺寸,但是并没有效果。即使将其调整为像28x28这样的较小尺寸,仍然存在相同的视觉故障,这似乎证明了你所说的由于图标扩展而引起的错误并不正确。但是,我也视觉检查了它,在28x28下,它仍然比它应该的要大(在这里看 https://i.imgur.com/xyrGpUJ.png)。对label.getWidth()的调用返回28,而对label.getIcon().getIconWidth()的调用返回32。有趣,但我仍然不知道该怎么看待它。 - IamJohnGalt
所以即使在您的新示例中,看起来双倍列也是在规律间隔发生的;从开始到开始20或21个像素。在先前的“32x32”图标示例中,它们为22或23。我不确定这告诉我们什么......设备像素和逻辑像素之间是否存在差异?(我在试探。) - DavidW
感谢您回复,David。在随着时间的推移促使OP调整和扩展问题后,我很失望它已经从主页面上消失了,并且一旦成为一个很好的问题就没有收到任何回复。添加赏金(我觉得我拥有比我需要或应得的更多声望)是一个毫不费力的决定。 - Andrew Thompson
1
@AndrewThompson 这确实是一个有趣的问题;很抱歉我没有看到它得到解决。不幸的是,我自己无法复现它,所以我只能指出我能看到的东西... - DavidW
并非每个问题都有可行的答案。(耸了耸肩)也许是由于“小精灵”的原因。 - Andrew Thompson

2

第一种选择:不要使用ImageIcon,可以尝试创建自己的图标类,使用graphics.drawImage(x,y,width,height,null)绘制图像并控制渲染质量(https://docs.oracle.com/javase/tutorial/2d/advanced/quality.html)。例如:

public class Icon32 extends ImageIcon {
    public Icon32(String f) {
      super(f);

      BufferedImage i= new BufferedImage(32, 32, 
           BufferedImage.TYPE_INT_RGB);


      Graphics2D g2d = (Graphics2D) i.getGraphics();
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

                g2d.setRenderingHint(
                    RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);

      g2d.drawImage(getImage(), 0, 0, 32, 32, null);
      setImage(i);
    }

    public int getIconHeight() {
      return 32;
    }

    public int getIconWidth() {
      return 32;
    }

    public void paintIcon(Component c, Graphics g, int x, int y) {
      g.drawImage(getImage(), x, y, c);
    }
  }

where the method:

getImage()

正在加载您的图像/图标...

第二个选项:如果您对结果不满意,可以尝试使用此库: https://github.com/mortennobel/java-image-scaling 它声称提供比Java运行时更好的图像缩放选项。

最初的回答: 如果您对结果不满意,您可以尝试使用Java运行时提供的其他图像缩放选项。


1

答案来自于此链接,用于生成高质量图像https://componenthouse.com/2008/02/08/high-quality-image-resize-with-java/

链接中相应的类为

public class ImageResize {

    public static void main(String[] args) {
        try {
            URL url = new URL("http://i.stack.imgur.com/L5DGx.png");
            BufferedImage image = ImageIO.read(url);
            ImageIO.write(resizeImage(image, 32, 32), "png", new File("D:/picture3.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static BufferedImage resize(BufferedImage image, int width, int height) {
        int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
        BufferedImage resizedImage = new BufferedImage(width, height, type);
        Graphics2D g = resizedImage.createGraphics();
        g.setComposite(AlphaComposite.Src);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.drawImage(image, 0, 0, width, height, null);
        g.dispose();
        return resizedImage;
    }

    private static BufferedImage resizeImage(BufferedImage image, int width, int height) {
        image = createCompatibleImage(image);
        image = resize(image, 100, 100);
        image = blurImage(image);
        return resize(image, width, height);
    }

    public static BufferedImage blurImage(BufferedImage image) {
        float ninth = 1.0f/9.0f;
        float[] blurKernel = {
                ninth, ninth, ninth,
                ninth, ninth, ninth,
                ninth, ninth, ninth
        };

        Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
        map.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        map.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
        map.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        RenderingHints hints = new RenderingHints(map);
        BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
        return op.filter(image, null);
    }

    private static BufferedImage createCompatibleImage(BufferedImage image) {
        GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage result = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
        Graphics2D g2 = result.createGraphics();
        g2.drawRenderedImage(image, null);
        g2.dispose();
        return result;
    }

}

1
你为什么觉得这个问题与调整大小有关?他们说图像是32x32,而且显示也是32x32。但是图像存在扭曲。当他们调整大小时,他们只是调整了JFrame的大小,图像仍然是32x32,但会导致扭曲移动。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接