在Java Swing中有效地渲染占用网格

时间:2012-02-12 19:33:26

标签: java swing graphics robotics

首先,请接受我的道歉,如果这个问题是基本的,我主要了解C#,但我被迫在这个特定项目中使用Java!

我正在尝试使用GUI来显示基于机器人传感器数据的occupancy grid。占用网格将非常大,可能高达1500x1500网格方块,代表每网格单元约10平方厘米的真实世界区域。

每个网格方块只会存储一个Enumerable状态,例如:

  • 未知
  • 未占
  • 占用
  • 机器人

我只想找到将其渲染为网格的最佳方法,使用不同的颜色方块来描绘不同的网格单元状态。

我已经实现了一个简单的基本算法来绘制正方形和网格线,但是它在较大的占用网格上执行非常糟糕。当收集新的传感器数据时,类中的其他代码每0.5秒重绘一次窗口,我怀疑性能非常差的原因是我每次都在渲染每个单元格。如果我将每个单元格包装在一个可观察的类中,是否有一种简单的方法可以选择性地渲染这些单元格?

我目前的实施:

@Override
public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;

    int width = getSize().width;
    int height = getSize().height;

    int rowHeight = height / (rows);
    int colWidth = width / (columns);

    //Draw Squares
    for (int row = 0; row < rows; row++) {
        for (int col = 0; col < columns; col++) {
            switch (this.grid[row][col]) {
                case Unexplored:
                    g.setColor(Color.LIGHT_GRAY);
                    break;
                case Empty:
                    g.setColor(Color.WHITE);
                    break;
                case Occupied:
                    g.setColor(Color.BLACK);
                    break;
                case Robot:
                    g.setColor(Color.RED);
                    break;
            }

            g.drawRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth,     rowHeight);
            g.fillRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth,     rowHeight);
        }
    }

    int k;
    if (outline) {
        g.setColor(Color.black);
        for (k = 0; k < rows; k++) {
            g.drawLine(0, k * rowHeight, width, k * rowHeight);
        }

        for (k = 0; k < columns; k++) {
            g.drawLine(k * colWidth, 0, k * colWidth, height);
        }
    }

}


 private void setRefresh() {
    Action updateUI = new AbstractAction() {
        boolean shouldDraw = false;

        public void actionPerformed(ActionEvent e) {
            repaint();
        }
    };

    new Timer(updateRate, updateUI).start();
}

请帮忙!提前谢谢。

4 个答案:

答案 0 :(得分:2)

答案 1 :(得分:2)

绘制时需要尊重剪辑矩形(假设您的网格位于JScrollPane中)并适当地使用JComponent #repaint(Rectangle)。

请参阅此示例程序(虽然它与懒惰地加载单元格的值有关,但它也有剪辑边界绘制):

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.*;


public class TilePainter extends JPanel implements Scrollable {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("Tiles");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(new TilePainter()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    private final int TILE_SIZE = 50;
    private final int TILE_COUNT = 1000;
    private final int visibleTiles = 10;
    private final boolean[][] loaded;
    private final boolean[][] loading;
    private final Random random;

    public TilePainter() {
        setPreferredSize(new Dimension(
                TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
        loaded = new boolean[TILE_COUNT][TILE_COUNT];
        loading = new boolean[TILE_COUNT][TILE_COUNT];
        random = new Random();
    }

    public boolean getTile(final int x, final int y) {
        boolean canPaint = loaded[x][y];
        if(!canPaint && !loading[x][y]) {
            loading[x][y] = true;
            Timer timer = new Timer(random.nextInt(500),
                    new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    loaded[x][y] = true;
                    repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
                }
            });
            timer.setRepeats(false);
            timer.start();
        }
        return canPaint;
    }

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

        Rectangle clip = g.getClipBounds();
        int startX = clip.x - (clip.x % TILE_SIZE);
        int startY = clip.y - (clip.y % TILE_SIZE);
        for(int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
            for(int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
                if(getTile(x / TILE_SIZE, y / TILE_SIZE)) {
                    g.setColor(Color.GREEN);
                }
                else {
                    g.setColor(Color.RED);
                }
                g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
            }
        }
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
    }

    @Override
    public int getScrollableBlockIncrement(
            Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE * Math.max(1, visibleTiles - 1);
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    @Override
    public int getScrollableUnitIncrement(
            Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE;
    }
}

答案 2 :(得分:1)

甚至渲染2,250,000个细胞的子集也不是一项微不足道的工作。您需要的两种模式是Model-View-Controller,讨论hereflyweightJTable可能会有用。

答案 3 :(得分:1)

创建矩形可能太慢了。相反,为什么不创建位图图像,每个像素都是网格的单元格,然后可以将其缩放到您想要的任何大小。

以下类采用整数矩阵,并将其保存到位图文件中。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class BMP {
    private final static int BMP_CODE = 19778;

    byte [] bytes;

    public void saveBMP(String filename, int [][] rgbValues){
        try {
            FileOutputStream fos = new FileOutputStream(new File(filename));

            bytes = new byte[54 + 3*rgbValues.length*rgbValues[0].length + getPadding(rgbValues[0].length)*rgbValues.length];

            saveFileHeader();
            saveInfoHeader(rgbValues.length, rgbValues[0].length);
            saveRgbQuad();
            saveBitmapData(rgbValues);

            fos.write(bytes);

            fos.close();

        } catch (FileNotFoundException e) {

        } catch (IOException e) {

        }

    }

    private void saveFileHeader() {
        byte[]a=intToByteCouple(BMP_CODE);
        bytes[0]=a[1];
        bytes[1]=a[0];

        a=intToFourBytes(bytes.length);
        bytes[5]=a[0];
        bytes[4]=a[1];
        bytes[3]=a[2];
        bytes[2]=a[3];

        //data offset
        bytes[10]=54;
    }

    private void saveInfoHeader(int height, int width) {
        bytes[14]=40;

        byte[]a=intToFourBytes(width);
        bytes[22]=a[3];
        bytes[23]=a[2];
        bytes[24]=a[1];
        bytes[25]=a[0];

        a=intToFourBytes(height);
        bytes[18]=a[3];
        bytes[19]=a[2];
        bytes[20]=a[1];
        bytes[21]=a[0];

        bytes[26]=1;

        bytes[28]=24;
    }

    private void saveRgbQuad() {

    }

    private void saveBitmapData(int[][]rgbValues) {
        int i;

        for(i=0;i<rgbValues.length;i++){
            writeLine(i, rgbValues);
        }

    }

    private void writeLine(int row, int [][] rgbValues) {
        final int offset=54;
        final int rowLength=rgbValues[row].length;
        final int padding = getPadding(rgbValues[0].length);
        int i;

        for(i=0;i<rowLength;i++){
            int rgb=rgbValues[row][i];
            int temp=offset + 3*(i+rowLength*row) + row*padding;

            bytes[temp]    = (byte) (rgb>>16);
            bytes[temp +1] = (byte) (rgb>>8);
            bytes[temp +2] = (byte) rgb;
        }
        i--;
        int temp=offset + 3*(i+rowLength*row) + row*padding+3;

        for(int j=0;j<padding;j++)
            bytes[temp +j]=0;

    }

    private byte[] intToByteCouple(int x){
        byte [] array = new byte[2];

        array[1]=(byte)  x;
        array[0]=(byte) (x>>8);

        return array;
    }

    private byte[] intToFourBytes(int x){
        byte [] array = new byte[4];

        array[3]=(byte)  x;
        array[2]=(byte) (x>>8);
        array[1]=(byte) (x>>16);
        array[0]=(byte) (x>>24);

        return array;
    }

    private int getPadding(int rowLength){

        int padding = (3*rowLength)%4;
        if(padding!=0)
            padding=4-padding;


        return padding;
    }

}

通过该课程,你可以做到:

new BMP().saveBMP(fieName, myOccupancyMatrix);

生成整数矩阵(myOccupancyMatrix)很简单。避免Switch语句的一个简单技巧是将颜色值分配给占用枚举:

public enum Occupancy {
        Unexplored(0x333333), Empty(0xFFFFFF), Occupied(0x000000), Robot(0xFF0000);
}

一旦你保存了磁盘,BMP就可以显示在applet中并轻松扩展:

public class Form1 extends JApplet {
    public void paint(Graphics g) {
        Image i = ImageIO.read(new URL(getCodeBase(), "fileName.bmp"));
        g.drawImage(i,0,0,WIDTH,HEIGHT,Color.White,null);
    }
}

希望这有帮助!