作为大学项目的一部分,我正在开发一个用于注释图像的Java摇摆程序。这个程序的特点之一是能够在图像的某些区域周围绘制多边形,然后可以用标题标记。
绘制多边形时,每次单击都会在图像上绘制一个新的绿色顶点,并通过绘制一条线将该顶点链接到前一个顶点。当用户移动鼠标时,还会绘制一条预览线,以便他们可以看到下一次单击将为多边形的形状添加什么。
我遇到的问题是,一旦用户绘制了一个多边形,整个程序性能就会明显下降。预览线的绘制变得令人难以置信地抖动,以至于难以使用。
负责这部分程序的代码在文件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的摇摆和图形库实现绘图功能的最佳方法的好资源,所以关于我搞砸的一般实践以及这个问题的直接解决方案的建议是可取的。
它看起来像你直接绘制到帧缓冲区。这是非常慢的,因为每次你绘制任何东西,JVM都必须进行系统调用来更新屏幕上的图像。
如果您在内存中的单个帧上执行所有绘图操作,并且仅在整个图像准备就绪时才输出到系统,您将看到更好的渲染速度。(这是您已经在使用backback-imageimage
所做的事情,实际上您可以重新使用您已经创建的图形对象,但不要使用)
所以,你需要创建一个画布来绘制;
BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
然后获取用于绘制到画布的Graphics
对象;
Graphics cg = canvas.getGraphics();
在cg
上完成所有的绘图操作,然后在你的画布组件(Graphics g)
函数中,只需使用一次调用将画布
绘制到组件上;
g.drawImage(canvas, 0, 0, null);
为了获得更好的性能,您应该绘制到VolatileImage
而不是BufferedImage
。但是BufferedImage
更易于使用,并且可以很好地满足您的目的。