在 Java Swing 中高效渲染占用网格

发布于 2025-01-05 00:32:35 字数 2174 浏览 1 评论 0原文

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

我正在尝试实现一个 GUI 来显示基于机器人传感器数据的 占用网格。占用网格将非常大,可能高达 1500x1500 网格方块,代表每个网格单元约 10cm2 的实际面积。

每个网格方块将简单地存储一个可枚举状态,例如:

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

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

我已经实现了一种简单的基本算法来绘制正方形和网格线,但是它在较大的占用网格上表现非常糟糕。类中的其他代码在收集新的传感器数据时每 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();
}

请帮忙!提前致谢。

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!

I'm trying to implement a GUI to display an occupancy grid based on robot sensor data. The occupancy grid will be quite large, perhaps up to 1500x1500 grid squares representing real-world area of around 10cm2 per grid cell.

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. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

み零 2025-01-12 00:32:35

绘画时您需要尊重剪辑矩形(假设您的网格位于 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;
    }
}

You need to respect the clip rectangle when painting (assuming your grid is in a JScrollPane) and use JComponent#repaint(Rectangle) appropriately.

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;
    }
}
夏の忆 2025-01-12 00:32:35

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

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

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 语句的一个简单技巧是将颜色值分配给 Occupancy 枚举:

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

将其保存到磁盘后,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);
    }
}

希望这会有所帮助!

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. A simple trick to avoid the Switch statement is assigning the color values to your Occupancy enum:

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:

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!

古镇旧梦 2025-01-12 00:32:35

即使渲染 2,250,000 个单元的子集也不是一件容易的事。您需要的两种模式是模型-视图-控制器,讨论了此处,以及flyweight,其中 JTable< /code>可能有用。

Rendering even a subset of 2,250,000 cells is not a trivial undertaking. Two patterns you'll need are Model-View-Controller, discussed here, and flyweight, for which JTable may be useful.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文