2008-08-22

Java的一种图像处理及优化方法

  本来是在研究Graphics的图形处理的,不过找的的资料说的是图像的,但是我们还是可以从中找到方向:
  下面的代码是一个简单的小程序,基本就是两张图片,一大一小,用户可以在大图片上拖动小图片:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
/**
* A pin-the-tail-on-th-donkye demo
* @author ulysess
*/
public class pin extends java.applet.Applet{
  Image frontone,backone;
  int new_x=400;
  int new_y=646;

  public void init(){
    frontone = getImage(getDocumentBase(),"sets.png");
    backone = getImage(getDocumentBase(),"eva.jpg");

    addMouseMotionListener(new MouseMotionListener(){
      public void mouseDragged(MouseEvent e){
        System.out.println("drag");
        new_x=e.getX();
        new_y=e.getY();
        repaint();
      }

      public void mouseMoved(MouseEvent e){}
    } );
  }
  public void paint (Graphics g) {
    g.setColor(Color.black);
    g.drawImage(backone,5, 5, this);
    g.drawImage(frontone, new_x-25, new_y-25, this);
  }

}


  基本上,只需要这些就能够实现效果了,不过可悲的是,拖动图片的时候会闪烁的异常厉害。也就是没有实际使用意义。
  下面讨论下其中的过程:因为applet是一个重量级组件,所以当拖动鼠标时调用的repaint()会调用Component.update(Graphics g)这个方法。
它的实现如下:

public void update(Graphics g){
  g.setColor(getBackground());
  g.fillRect(0,0,width,height);
  g.setColor(getForeground());
  paint(g);
}

  这里可以看出,当使用reapint()时,update重绘整个面板,然后有再次调用了paint(),这样就造成了重复,我们可以重载update方法来消除这个重复,但是结果仍然不好。
  因此我们就需要使用双缓冲或称离屏处理(offscreen imaging)技术来优化显示效果。

双重缓冲:
  基本上,双缓冲就是让程序使用两个graphics对象,绘制时在内存中绘制其中一个作为缓冲用的对象,在绘制完成后再显示到屏幕上,这样能很快的更新图像,显然输出结果比在屏幕上计算结果快的多。而所需要做的只是简单的改动代码(蓝色部分):


import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
/**
* A pin-the-tail-on-th-donkye demo
* @author ulysess
*/
public class pin extends java.applet.Applet{
  Image frontone,backone;
  Image myOffScreenImage;
  Graphics myOffScreenGraphics;
  int new_x=400;
  int new_y=646;

  public void init(){
    frontone = getImage(getDocumentBase(),"sets.png");
    backone = getImage(getDocumentBase(),"eva.jpg");
    myOffScreenImage=createImage(getSize().width,getSize().height);
    myOffScreenGraphics = myOffScreenImage.getGraphics();
    addMouseMotionListener(new MouseMotionListener(){
      public void mouseDragged(MouseEvent e){
        System.out.println("drag");
        new_x=e.getX();
        new_y=e.getY();
        repaint();
      }

      public void mouseMoved(MouseEvent e){}
    } );
  }
  public void paint (Graphics g) {
    g.setColor(Color.black);
    g.drawImage(backone,5, 5, this);
    g.drawImage(frontone, new_x-25, new_y-25, this);
  }
  public void update(Graphics g)
  {
    paint(myOffScreenGraphics);
    g.drawImage(myOffScreenImage,0,0,this);
  }

}

  很显然,只增加了一个用于创建后台图信的image图像对象和一个后台Graphics图形对象,
关键的地方在最后重载的update方法,在repaint时,现绘制myOffScreenGraphics对象,之后再通过g.drawImage把这个对象对应的图像画到屏幕上。仅仅如此处理,结果就让人惊奇了,一点都不会闪烁了。

剪切矩形:
  还有一种方法称为剪切矩形法。主要的原理就是使用一个剪切矩形区域告诉程序,只有这个区域中的内容发生变化,只需要在这个矩形内绘图即可。其他地方不需要改变。
为了演示效果,我们把前面个的双缓冲给去掉。
  当使用setClip(int x,int y,int width,int height)设定了剪切矩形的时候,之后paint处理的范围也就仅仅在这个矩形之内,而不会去刷新矩形外的内容。即使paint的实现中有填充整个窗口的设定。
  下面来看看代码,蓝色部分为剪切矩形的实现,红色部分为处理的改进(全部都是相对于最初的代码的变化):

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
/**
* A pin-the-tail-on-th-donkye demo
* @author ulysess
*/
public class pin extends java.applet.Applet{
  Image frontone,backone;
  int new_x=300,old_x=0;
  int new_y=446,old_y=0;

  public void init(){
    frontone = getImage(getDocumentBase(),"sets.png");
    backone = getImage(getDocumentBase(),"eva.jpg");

    addMouseMotionListener(new MouseMotionListener(){
      public void mouseDragged(MouseEvent e){
        System.out.println("drag");
        new_x=e.getX();
        new_y=e.getY();
        repaint();
      }

      public void mouseMoved(MouseEvent e){}
    } );
  }
  public void paint (Graphics g) {
    g.setColor(Color.black);
    g.drawImage(backone,5, 5, this);
    g.drawImage(frontone, new_x-25, new_y-25, this);
  }
  
  public void update(Graphics g)
  {
    int cut=0;
    g.setClip(new_x-Math.abs(old_x-new_x), new_y-Math.abs(old_y-new_y) ,325+2*Math.abs(old_x-new_x) ,251+2*Math.abs(old_y-new_y));
    old_x=new_x;
    old_y=new_y;
    paint(g);
  }
}

  所以,实际上,要使用剪切矩形只需要重载update方法,在paint之前setClip为需要的矩形区域即可,当时,因为用户移动的速度不一致,所以如果你的矩形设置不好,会产生严重的图形碎块,这个时候怎么办呢?红色的部分就是优化的方法,增加了old_变量,保存上次的点值,根据点的变化来确定矩形的大小。325和251是我这里用的图片的长宽而已。经过这样设置以后,你就会看到实际只有拖动的部分在刷新,而其他部分是没有刷新的。不过拖动的部分还是闪烁的十分厉害,这样,结合前面的双重缓冲就可以让这个程序的效率比之前大大提升了。

  最后,在重载update之后,会发现第一次打开程序的时候,页面背景图是不会显示的,必须最小化再最大化,或者用其他窗口覆盖一下才会出现,这个问题很奇怪,我测试了也还是的不出结论,我们不可能在update里面加一个drawImage加载背景图。因为这样一旦有动作就要加载图片,浪费太多资源。经过最后的测试发现,可能是程序初始化的时候虽然我们没有任何动作,但是程序会自动repaint()两次,只要在这个过程中让update去drawImage背景图片就没有问题了。
  不过个人还是不大喜欢这样。希望以后能在其他教程上找到原因。

/*******************************************
*原创文章,转载请注明出处
*******************************************/

没有评论: