【问题标题】:Graphics.drawImage() consumes a lot of memory drawing an int[] imageGraphics.drawImage() 绘制 int[] 图像会消耗大量内存
【发布时间】:2017-05-22 06:51:29
【问题描述】:

我有一个扩展 JComponent 的 ImageViewComponent。该组件被添加到 JPanel 中,该 JPanel 被添加到 JFrame。从另一个类中,我定期从另一个类更新 ImageViewComponent 的 int[] image 字段。 问题是这个过程会占用大量内存。它甚至消耗了如此多的内存(根据 JProfiler,几秒钟后 +/- 130 MB,最终超过 1GB),以至于整个程序在垃圾收集期间经历了“滞后峰值”(程序中的滞后同时发生内存被清除)。

这是ImageViewComponent的代码:

public class ImageViewComponent extends JComponent {

    private int image_width, image_height, updateInterval, updateCounter;
    private int[] imageArray;
    private BufferedImage currentDisplayedImage;
    private Image scaledDisplayedImage;

    /**
     * @param width          The width of this component
     * @param height         The height of this component
     * @param ui             The higher, the less frequent the image will be updated
     */
    public ImageViewComponent(int width, int height, int ui) {
        setPreferredSize(new Dimension(width, height));
        this.updateInterval = ui;
        this.updateCounter = 0;
        this.currentDisplayedImage = null;
        this.scaledDisplayedImage = null;
    }

    public void setImage(int[] image, int width, int height) {
        this.imageArray = image;
        this.image_width = width;
        this.image_height = height;
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);

        if (image_width == 0 || image_height == 0)
            return;
        else if (updateCounter != updateInterval && currentDisplayedImage != null) {
            g.drawImage(scaledDisplayedImage, 0, 0, this);
            updateCounter++;
            return;
        }

        this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB);
        this.currentDisplayedImage.setRGB(0, 0, image_width, image_height, this.imageArray, 0, image_width);

        this.scaledDisplayedImage = this.currentDisplayedImage.getScaledInstance(this.getPreferredSize().width,
                this.getPreferredSize().height, BufferedImage.SCALE_DEFAULT);

        g.drawImage(scaledDisplayedImage, 0, 0, this);

        // reset update counter
        updateCounter = 0;
    }

}

JProfiler 声明其活动内存的 70% 的程序在此类中分配,50% 在 Graphics.drawImage 中,而 20% 在 BufferedImage 初始化中。

我尝试通过将行 this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB) 放入“setImage”中来修复它,并让它只用一个布尔标志设置一次,但这会使绘制的图像偶尔在短时间内完全变黑,也不它是否解决了内存问题。 我也尝试了this 的建议,也没有用。

如何解决这个内存问题?

【问题讨论】:

  • 避免在paintComponent 方法中完成所有工作。使用ComponentListener 来检测组件大小的变化,然后更新缩放的实例。我还将仅在setImage 方法中创建新的BufferedImage,调用repaint 来触发绘画通道
  • @MadProgrammer 组件本身的大小不变,图像大小也不变。我重新调整它是因为int[] image 的尺寸比组件大。 setImage 方法经常被调用,每次使用完全不同的图像(它从以大约 60fps 运行的模拟器获取图像)。我将在setImage 中创建BufferedImage,并使用布尔标志只运行一次,就像我之前尝试的那样。还有什么我应该考虑的吗?
  • 提供一个可运行的例子
  • 我还要验证它是导致内存泄漏的组件,而不是生成int[]

标签: java image swing memory graphics


【解决方案1】:

代码有几个问题。一些涉及性能,另一些涉及样式或最佳实践,还有一些(至少可能)涉及内存消耗。

  • 性能:getScaledInstance 方法非常缓慢。请参阅https://stackoverflow.com/a/32278737/3182664 和其他人了解更好的选择
  • 风格:是imageWidth,不是image_width
  • 最佳实践:对于JComponent,您通常覆盖paintComponent 而不是paint
  • 内存消耗:这是重点...:

正如 MadProgrammer 已经指出的那样:尽可能少做事。这个updateCounter 的作用和目的并不完全清楚。我认为减少更新图像频率的责任应该在使用您的组件的类中 - 特别是在调用updateImage的类中(应该简单地少做)。在paint 方法中维护这个不是很可靠。

在您当前的代码中,currentDisplayedImage 似乎(尽管它的名字)既没有显示也没有以任何其他方式使用。但是,保留它可能是个好主意:需要用 int[] 数据填充它,并作为可能必须创建的缩放图像的源。

您的类的一种可能实现如下所示:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;

public class ImageViewComponent extends JComponent {

    private int updateInterval, updateCounter;
    private BufferedImage fullImage;
    private BufferedImage displayedImage;

    /**
     * @param width          The width of this component
     * @param height         The height of this component
     * @param ui             The higher, the less frequent the image will be updated
     */
    public ImageViewComponent(int width, int height, int ui) {
        setPreferredSize(new Dimension(width, height));
        this.updateInterval = ui;
        this.updateCounter = 0;
        this.fullImage = null;
        this.displayedImage = 
            new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    }

    public void setImage(int[] image, int width, int height) {

        // Note: The updateInvervall/updateCounter stuff COULD
        // probably also be done here...
        if (fullImage == null ||
            fullImage.getWidth() != width ||
            fullImage.getHeight() != height)
        {
            fullImage = new BufferedImage(
                width, height, BufferedImage.TYPE_INT_RGB);
        }
        fullImage.setRGB(0, 0, width, height, image, 0, width);
        scaleImage(fullImage, displayedImage);
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(displayedImage, 0, 0, this);
    }


    private static BufferedImage scaleImage(
        BufferedImage input, BufferedImage output)
    {
        double scaleX = (double) output.getWidth() / input.getWidth();
        double scaleY = (double) output.getHeight() / input.getHeight();
        AffineTransform affineTransform = 
            AffineTransform.getScaleInstance(scaleX, scaleY);
        AffineTransformOp affineTransformOp = 
            new AffineTransformOp(affineTransform, null);
        return affineTransformOp.filter(input, output);
    }    

}

请注意,出于上述原因,这执行此“updateInterval”处理。

附带说明:也许您甚至不必缩放图像。如果您的意图是让图像始终以组件的大小显示,那么您可以简单地做

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);

    // Draw the FULL image, which, regardless of its size (!) 
    // is here painted to just fill this component:
    g.drawImage(fullImage, 0, 0, getWidth(), getHeight(), null);
}

通常,绘制这样的缩放图像非常快。但取决于许多因素,像您所做的那样,将图像的缩放步骤和绘制步骤分开,也可能是一个合理的选择。

【讨论】:

  • 将您的建议与调用方法中的invokeLater 块中的setImage 结合使用可解决内存问题。仅供参考,您帖子的最后一部分(将缩放和绘画分开)似乎没有太大区别,如果有的话。谢谢!
猜你喜欢
  • 2013-05-07
  • 2017-12-21
  • 2022-01-16
  • 2014-01-04
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多