1

大学のプロジェクトの一環として、画像に注釈を付けるためのJavaSwingプログラムに取り組んでいます。このプログラムの機能の1つは、画像の特定の領域の周りにポリゴンを描画して、キャプションでラベル付けできるようにすることです。

ポリゴンを描画するときは、クリックするたびに画像上に新しい緑色の頂点が描画され、線を描画してこの頂点を前の頂点にリンクします。ユーザーがマウスを動かしているときに描画されるプレビュー線もあり、次のクリックでポリゴンの形状に何が追加されるかを確認できます。

私が抱えている問題は、ユーザーが1つのポリゴンを描画すると、プログラム全体のパフォーマンスが大幅に低下することです。プレビューラインの描画は非常にぎくしゃくし、使いにくいところまで来ています。

プログラムのこの部分を担当するコードは、ファイルImagePanel.javaにあります。

package hci;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.JOptionPane;

import java.awt.Color;
import java.awt.BasicStroke;
import java.awt.Stroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.ArrayList;

import hci.utils.*;

public class ImagePanel extends JPanel implements MouseListener, MouseMotionListener {

    private static final long serialVersionUID = 1L;
    BufferedImage image = null;
    CaptionedPolygon currentPolygon = null;
    ArrayList<CaptionedPolygon> polygonsList = null;
    Point mousePos;
    public static final int FIRST_NODE_SIZE = 15;

    public ImagePanel() {
        currentPolygon = new CaptionedPolygon();
        polygonsList = new ArrayList<CaptionedPolygon>();
        mousePos = new Point(0,0);

        this.setVisible(true);

        Dimension panelSize = new Dimension(800, 600);
        this.setSize(panelSize);
        this.setMinimumSize(panelSize);
        this.setPreferredSize(panelSize);
        this.setMaximumSize(panelSize);

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    public ImagePanel(String imageName) throws Exception{
        this();
        image = ImageIO.read(new File(imageName));
        if (image.getWidth() > 800 || image.getHeight() > 600) {
            int newWidth = image.getWidth() > 800 ? 800 : (image.getWidth() * 600)/image.getHeight();
            int newHeight = image.getHeight() > 600 ? 600 : (image.getHeight() * 800)/image.getWidth();
            System.out.println("SCALING TO " + newWidth + "x" + newHeight );
            Image scaledImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST);
            image = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
            image.getGraphics().drawImage(scaledImage, 0, 0, this);
        }
    }

    public void ShowImage(Graphics g) {

        if (image != null) {
            g.drawImage(
                    image, 0, 0, null);
        }
    }

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

        //display image
        ShowImage(g);
        drawPreviewLine(g);

        //display all the completed polygons
        for(CaptionedPolygon polygon : polygonsList) {
            fillPolygon(polygon, g);
            drawPolygon(polygon, g);
            finishPolygon(polygon, g);
        }

        //display current polygon
        drawPolygon(currentPolygon, g);


    }

    public void drawPreviewLine(Graphics g){
        if (currentPolygon.points.size() > 0 && mousePos != null){
            Point currentPoint = currentPolygon.points.get(currentPolygon.points.size() - 1);
            g.setColor(Color.GREEN);
            g.drawLine(currentPoint.getX(), currentPoint.getY(), mousePos.getX(), mousePos.getY());
        }
    }

    public void fillPolygon(CaptionedPolygon polygon, Graphics g){
        Color fillColor = new Color((float)0.0,(float)1.0,(float)0.0, (float)0.3);
        Polygon polyToDraw =  new Polygon();
        for (Point point : polygon.points){
            polyToDraw.addPoint(point.getX(), point.getY());
        }
        g.setColor(fillColor);
        g.fillPolygon(polyToDraw);
    }

    public void drawPolygon(CaptionedPolygon polygon, Graphics g) {
        for(int i = 0; i < polygon.points.size(); i++) {
            int sizeModifier = 0;
            Graphics2D g2 = (Graphics2D)g;
            g2.setColor(Color.GREEN);
            g2.setStroke(new BasicStroke(1));
            Point currentVertex = polygon.points.get(i);


            if(currentPolygon.equals(polygon) && i == 0){ //First point of the current polygon

                //Enlarge circle drawn if mouse hovers over point
                if(pointWithinCircle(mousePos.getX(), mousePos.getY(), currentVertex.getX(), currentVertex.getY(), (FIRST_NODE_SIZE + 2)/2)){
                    sizeModifier = 3;
                }

                int nodeSize = FIRST_NODE_SIZE + sizeModifier;

                g2.setColor(Color.WHITE);
                g2.fillOval(currentVertex.getX() - nodeSize/2 , currentVertex.getY() - nodeSize/2, nodeSize, nodeSize);
                g2.setStroke(new BasicStroke(2));
                g2.setColor(Color.GREEN);
                g2.drawOval(currentVertex.getX() - nodeSize/2 , currentVertex.getY() - nodeSize/2, nodeSize, nodeSize);
            }
            else if (i != 0){ //Some arbitary middle point
                Point prevVertex = polygon.points.get(i - 1);
                g2.drawLine(prevVertex.getX(), prevVertex.getY(), currentVertex.getX(), currentVertex.getY());
                g2.fillOval(currentVertex.getX() - 5, currentVertex.getY() - 5, 10, 10);
            }
            else{ //First point of some non current polygon
                g2.fillOval(currentVertex.getX() - 5, currentVertex.getY() - 5, 10, 10);
            }
        }
    }

    public void finishPolygon(CaptionedPolygon polygon, Graphics g) {
        //if there are less than 3 vertices than nothing to be completed
        if (polygon.points.size() >= 3) {
            Point firstVertex = polygon.points.get(0);
            Point lastVertex = polygon.points.get(polygon.points.size() - 1);

            g.setColor(Color.GREEN);
            g.drawLine(firstVertex.getX(), firstVertex.getY(), lastVertex.getX(), lastVertex.getY());
        }
    }

    public void addNewPolygon() {
        //finish the current polygon if any
        if (currentPolygon.points.size() > 0 ) {
            currentPolygon.caption = JOptionPane.showInputDialog(this, "Please enter a caption for this area") ;
            polygonsList.add(currentPolygon);
        }

        currentPolygon = new CaptionedPolygon();
        repaint();
    }

    public boolean pointWithinCircle(int targetX, int targetY, int circleCentX, int circleCentY, double circleRadius){
        Point2D.Double mousePoint = new Point2D.Double(targetX,targetY);
        Point2D.Double firstNodePoint = new Point2D.Double(circleCentX, circleCentY);
        return (mousePoint.distance(firstNodePoint) <= circleRadius);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
    }

    @Override
    public void mouseExited(MouseEvent arg0) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();

        //check if the cursor is within image area
        if (x > image.getWidth() || y > image.getHeight()) {
            return;
        }

        //Clicking the left button will either add a new vertex or finish off a polygon
        if (e.getButton() == MouseEvent.BUTTON1) {
            if (currentPolygon.points.size() > 0 ){

                if(pointWithinCircle(x, y, currentPolygon.points.get(0).getX(), currentPolygon.points.get(0).getY(), FIRST_NODE_SIZE + 2)){
                    addNewPolygon();
                }
                else{
                    currentPolygon.points.add(new Point(x,y));
                    System.out.println(x + " " + y);
                    repaint();
                }
            }
            else{
                currentPolygon.points.add(new Point(x,y));
                System.out.println(x + " " + y);
                repaint();
            }
        } 
    }

    @Override
    public void mouseReleased(MouseEvent arg0) {
    }

    public void mouseDragged(MouseEvent e){
    }

    public void mouseMoved(MouseEvent e){
        mousePos.setX(e.getX());
        mousePos.setY(e.getY());
        repaint();
    }

}

私は今、いくつかの異なるオペレーティングシステムとラップトップでこのプログラムを実行しようとしましたが、それらすべてで速度低下が目立ちます。これは、コードを実行しているだけでなく、コードに問題があることを示しています。

私の問題は、repaint()メソッドを呼び出す回数が多すぎることに関係していると感じています。Javaのswingおよびグラフィックライブラリを使用してこれらのような描画機能を実装するための最良の方法について、オンラインで多くの優れたリソースを実際に見たことがないので、私がめちゃくちゃにしている一般的な方法に関するアドバイスと、この問題の直接的な解決策が望ましいでしょう。

4

1 に答える 1

4

フレームバッファに直接描画しているようです。何かを描画するたびに、JVMは画面上の画像を更新するためにシステムコールを実行する必要があるため、これは非常に低速です。

JVMメモリ内の単一のフレームですべての描画操作を実行し、画像全体の準備ができたときにのみシステムに出力すると、レンダリング速度が大幅に向上します。(これは、背景画像imageですでに行っていることの一種です。実際、作成済みのグラフィックスオブジェクトを再利用できますが、使用しないでください)

したがって、描画するキャンバスを作成する必要があります。

BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);

Graphics次に、キャンバスへの描画に使用するオブジェクトを取得します。

Graphics cg = canvas.getGraphics();

ですべての描画操作を実行してcgから、paintComponent(Graphics g)関数で、canvas;を使用して1回の呼び出しでコンポーネントに描画するだけです。

g.drawImage(canvas, 0, 0, null);

さらに優れたパフォーマンスを得るには、のVolatileImage代わりに描画する必要がありますBufferedImage。しかし、これBufferedImageははるかに使いやすく、目的に応じて問題なく機能します。

于 2012-10-10T17:30:27.030 に答える