2

我有一个应用程序,我想在其中绘制一个宽度随时间增长的图像。具体来说,应用程序监听麦克风并每 6 毫秒计算一次频谱图,我想在它进来时绘制更新的频谱图。我一直在使用java.awt.image.BufferedImage绘制频谱图,但仅适用于预先录制的文件,所以我有图像的固定宽度。对于流应用程序执行此操作的最佳方法是什么,我事先不知道图像的宽度?

一种可能性是只创建一个新的 BufferedImage 并在右侧增加一个像素并将数据复制过来,但每秒执行数百次似乎效率低下。或者我可以从一个相对较大的宽度开始并将右侧保持空白直到它填满,然后将宽度加倍,类似于 ArrayList 如何摊销其大小 - 我只需每秒复制几次数据,或每隔几秒一次,或以此类推。有更好的选择吗?

4

3 回答 3

3

我将使用您建议的内容和仅绘制整个图像的子图像并返回适当的首选大小的覆盖组件的组合。

这是一个演示代码,它显示了我的意思:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TestRecording {

    public static class MyPanel extends JPanel {

        private BufferedImage buffer = new BufferedImage(3000, 100, BufferedImage.TYPE_INT_RGB);

        private int width = 0;

        private int lastY = 50;

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

            if (width > 0) {
                BufferedImage sub = buffer.getSubimage(0, 0, width, buffer.getHeight());
                g.drawImage(sub, 0, Math.max(0, (getHeight() - buffer.getHeight()) / 2), this);
            }

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(width, 100);
        }

        protected void drawSomething() {
            // Here need to handle growing image
            Graphics g = buffer.getGraphics();
            g.setColor(Color.GREEN);
            int y = new Random().nextInt(buffer.getHeight());
            g.drawLine(width, lastY, width + 1, y);
            lastY = y;
            width += 1;
            Rectangle r = new Rectangle();
            r.x = getWidth();
                    // Lame hack to auto-scroll to the end
            scrollRectToVisible(r);
            revalidate();
            repaint();
        }
    }

    protected void initUI() {
        JFrame frame = new JFrame(TestRecording.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final MyPanel p = new MyPanel();
        JScrollPane scrollpane = new JScrollPane(p);
        frame.add(scrollpane);
        frame.setSize(400, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        Timer t = new Timer(20, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                p.drawSomething();
            }
        });
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestRecording().initUI();
            }
        });
    }
}

结果如下:

结果

于 2013-01-08T16:54:17.093 回答
1

可能只是缩放原始图像以获得所需的大小。

ImagegetScaledInstance(int width, int height, int hints)方法

于 2013-01-08T15:58:15.057 回答
1

您是否有 1)在哪里绘制它的 gui,或者 2)您是否需要真正输出图像文件(或流)?

如果 1) 只画所见。扩展 JComponent,我通常使用 JPanel 并覆盖 paintComponent 并绘制可见的内容。为了提高效率,请创建“图块” - 例如列表。恒定宽度的 BufferedImage 并从传入数据创建它们并仅将它们绘制到 gui。

2)类似的东西,但你可以使用一个宽度相对较低的图像并为其绘制新数据。满时,将其“附加”到目前创建的“左”图像(最初为空)。这样你经常修改小图像和不那么频繁的大图像。如果在填充整个右侧图像之前结束,则仅将填充的部分与左侧连接。

您可以尝试逐渐增加右图像的大小(*X,例如 *1.5 或 *2)以减少加入图像的数量,但会以使用更多内存为代价。

您可以将这些“图像”存储为字节(或 int)数组(表示 2D 的一维数组,但按列存储,而不是按行存储以进行更有效的修改),这样如果您知道需要,您可以更有效地存储它每像素一些不寻常的位数,因为某些颜色永远不会出现在结果图像中。

如果图像太大,请将其保存到磁盘并清除左侧图像,然后将它们组合在一起或单独使用。

于 2013-01-08T16:20:22.447 回答