【问题标题】:Draw an image of growing size in Java在 Java 中绘制一个不断增长的图像
【发布时间】:2012-12-22 12:48:12
【问题描述】:

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

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

【问题讨论】:

    标签: java image swing awt


    【解决方案1】:

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

    这是一个演示代码,说明了我的意思:

    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();
                }
            });
        }
    }
    

    结果如下:

    【讨论】:

      【解决方案2】:

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

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

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

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

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

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

      【讨论】:

      • 我不需要将图像输出到文件中,只需将其显示在屏幕上即可。我正在使用 JScrollPane 来显示整个图像,并且暂时不用担心它会变得太大而无法放入内存。不过,我喜欢瓷砖的想法。例如,如果我有一系列以 BufferedImages 作为图标的 JLabel,并且我将它们彼此相邻放在一个容器中,它们会正确排列(例如,没有间隙)吗?
      • 再想一想,我需要回到之前的专栏并在频谱图上绘制标记。使用一张图片,这很容易,但如果标记必须跟踪列表中它必须引用的哪张图片,那就有点头疼了。但是,我仍然喜欢您回答中的新想法,如果我没有更好的想法,我会接受。
      • 排队:原则上它应该可以工作,但正如我所说,我会在paintComponent中绘制这些图像,并在绘制它们之后,在它们上面绘制标记。这样你就可以更多地负责绘画。然后有两种方法可以将手动绘制的图像移动,仿射变换或绘制到不同的坐标。
      【解决方案3】:

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

      Image 有getScaledInstance(int width, int height, int hints) 方法

      【讨论】:

      • 我希望图像的内容大小相同,只是为了有额外的列来绘制更多的数据。getScaledInstance 处理吗?我看了看,看起来我可以提供的任何提示都不能满足我的需要。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-26
      • 2014-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 2020-12-26
      相关资源
      最近更新 更多