2

時間の経過とともに幅が大きくなる画像を描画したいアプリケーションがあります。具体的には、アプリケーションはマイクをリッスンし、6 ミリ秒ごとにスペクトログラムを計算します。更新されたスペクトログラムを描画したいと考えていますjava.awt.image.BufferedImage。画像の固定幅。画像の幅がアプリオリにわからないストリーミングアプリケーションでこれを行う最良の方法は何ですか?

1 つの可能性は、右側に 1 ピクセルを追加して新しい BufferedImage を作成し、データをコピーすることですが、1 秒間に何百回も行うのは効率が悪いようです。または、比較的大きな幅から始めて、いっぱいになるまで右側を空白のままにし、いっぱいになったら幅を 2 ​​倍にすることもできます。これは、ArrayList がそのサイズを償却する方法と同様です。データを毎秒数回コピーするだけで済みます。 、または数秒に1回程度。より良いオプションはありますか?

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

1)描画する場所にGUIがありますか、それとも2)画像ファイル(またはストリーム)を実際に出力する必要がありますか?

1)見たものだけを描きます。JComponentを拡張します。通常、JPanelを使用し、paintComponentをオーバーライドして、表示されているものをペイントします。それをより効率的にするために、「タイル」を作成します-例えばのリスト。一定幅のBufferedImageを使用して、受信データからそれらを作成し、それらのみをGUIに描画します。

2)似たようなものですが、比較的幅の狭い1つの画像を使用して、それに新しいデータを描画できます。いっぱいになったら、これまでに作成した「左」の画像(最初は空)に「追加」します。このようにして、小さい画像と大きい画像を頻繁に変更しますが、それほど頻繁には変更しません。右の画像全体を塗りつぶす前に終了する場合は、塗りつぶした部分だけを左で結合します。

より多くのメモリを使用する代わりに、右側の画像のサイズを徐々に大きくして(* X、たとえば*1.5または*2)、結合する画像の数を減らすことができます。

これらの「画像」をbyte(またはint)配列(2Dを表す1D配列ですが、より効率的な変更のために行ではなく列で保存します)として保存できます。このようにして、必要なことがわかっている場合は、少し効率的に保存できます。一部の色が結果画像に含まれないため、ピクセルあたりのビット数が異常に多くなります。

画像が大きくなりすぎる場合は、画像をディスクに保存して左側の画像をクリアし、後でそれらを結合するか、別々に使用します。

于 2013-01-08T16:20:22.447 に答える
1

元の画像を拡大縮小して、目的のサイズにするだけです。

ImagegetScaledInstance(int width, int height, int hints)メソッドを持っています

于 2013-01-08T15:58:15.057 に答える