简体   繁体   English

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

[英]Efficiently rendering an Occupancy Grid in Java Swing

First off, please accept my apologies if this question is basic, I mainly have knowledge of C# but am forced to use Java for this particular project! 首先,如果这个问题很简单,请接受我的道歉。我主要了解C#,但被迫为此特定项目使用Java!

I'm trying to implement a GUI to display an occupancy grid based on robot sensor data. 我正在尝试实现一个GUI,以显示基于机器人传感器数据的占用网格 The occupancy grid will be quite large, perhaps up to 1500x1500 grid squares representing real-world area of around 10cm2 per grid cell. 占用栅格将非常大,可能多达1500x1500栅格正方形,代表每个栅格单元约10cm2的实际面积。

Each grid square will simply store an Enumerable status, for example: 每个网格正方形将仅存储可枚举状态,例如:

  • Unknown 未知
  • Unoccupied 空置
  • Occupied 占据
  • Robot 机器人

I would simply like to find the best way to render this as a grid, using different colour squares to depict different grid cell status'. 我只是想找到一种最佳的方式来渲染为网格,使用不同的颜色方块来描述不同的网格单元状态。

I have implemented a naive, basic algorithm to draw squares and grid lines, however it performs VERY badly on larger occupancy grids. 我已经实现了幼稚的基本算法来绘制正方形和网格线,但是它在较大的占用网格上表现非常差。 Other code in the class redraws the window every 0.5s as new sensor data is collected, I suspect the reason for the very poor performance is the fact that i am rendering EVERY cell EVERY time. 当收集到新的传感器数据时,该类中的其他代码每0.5s重绘一次窗口,我怀疑性能非常差的原因是我每次都渲染每个单元格。 Is there an easy way i can selectively render these cells, should I wrap each cell in an observable class? 有一种简单的方法可以选择性地渲染这些单元格,是否应该将每个单元格包装在一个可观察的类中?

My current implementation: 我当前的实现:

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

Please help! 请帮忙! Thanks in advance. 提前致谢。

You need to respect the clip rectangle when painting (assuming your grid is in a JScrollPane) and use JComponent#repaint(Rectangle) appropriately. 绘画时需要注意剪辑矩形(假设网格位于JScrollPane中),并适当地使用JComponent#repaint(Rectangle)。

See this sample program (although it related to loading the value of a cell lazily, it also has the clip bounds painting): 请参见此示例程序(尽管它与延迟加载单元格的值有关,但它也具有剪贴画边界):

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

Rendering even a subset of 2,250,000 cells is not a trivial undertaking. 渲染225万个单元的子集也不是一件容易的事。 Two patterns you'll need are Model-View-Controller , discussed here , and flyweight , for which JTable may be useful. 您需要使用两种模式: Model-View-Controller (在此处讨论)和flyweight ,对于它们而言, JTable可能会有用。

Creating rectangles is probably too slow. 创建矩形可能太慢了。 Instead, why don't you create a bitmap image, each pixel being a cell of the grid, you can then scale it to whatever size you want. 相反,为什么不创建一个位图图像(每个像素都是网格的一个单元),然后可以将其缩放到所需的任何大小。

The following class takes a matrix of integers, and saves it to a bitmap file. 下列类采用整数矩阵,并将其保存到位图文件中。

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

}

With that class, you can simply do: 使用该课程,您可以简单地执行以下操作:

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

Generating the matrix of integers ( myOccupancyMatrix ) is easy. 生成整数矩阵( myOccupancyMatrix )很容易。 A simple trick to avoid the Switch statement is assigning the color values to your Occupancy enum: 一个避免Switch语句的简单技巧是将颜色值分配给您的Occupancy枚举:

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

Once you save it do disk, the BMP can be shown in an applet and scaled easily: 将其保存到磁盘后,BMP可以显示在小程序中并轻松缩放:

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

Hope this helps! 希望这可以帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM