끌림

이중 버퍼링(Double Buffering) 본문

Programming/Java

이중 버퍼링(Double Buffering)

소닉럽 2009. 6. 20. 10:59

이번장에서는 이미지의 애니메이션 처리를 하거나  많은 페인팅작업시 흔히 발생하는 화면 깜박임 현상을 최소화하고 보다 자연스럽고 부드러운 화면을 구현할수 있는 방법에 대해 살펴보겠습니다.  설명에 앞서, 더블 버퍼링 방법은 자바뿐아니라, GUI프로그램을 만드는 대다수 프로그래밍 언어(VC, VB등..) 에서 자주 사용되는 기법이므로, 개념을 익혀두면 유용하게 쓸수있는 중요한 팁중 하나 입니다.

 앞서, paint 메소드에 대해 설명하면서, update 메소드에 대해 잠시 언급했던것을 기억할것입니다. update 메소드는 해당 컴포넌트의 배경을 설정된 배경색으로 칠한후, paint메소드를 호출하는것이라고 설명하였는데, 이때 빈번하게 화면을 갱신해야 하는 경우, 매번 배경을 새로 칠함으로 인해 깜박임 현상이 발생하므로 아래와 같이 update메소드를 재정의(오버라이딩)해서 배경을 칠하지 않게 해주면 깜박임 현상을 다소 줄일수있다고 하였습니다.

public void update(Graphics g) {

    paint(g);

}

 이처럼, update 메소드를 치환하면 약간의 화면 깜박임 현상은 줄수 있습니다. 하지만, 보다 자연스럽고 매끄러운 화면을 구현하기 위해서는 더블 버퍼링(Double Buffering)기법이 꼭 필요하게 됩니다.

그럼, 더블 버퍼링이란?

먼저 메모리상의 그래픽 버퍼를 생성하여 화면에 그릴 내용을 먼저 그버퍼에 그린후,  버퍼에 모든 페인팅 작업이 끝나게 되면, 그내용을 한꺼번에 화면에 그려주는 방식을 말합니다.

 실제, 구현방법은

1) 우선 메모리상에 실제 화면사이즈 만큼의 이미지 버퍼를 생성합니다.

Image memoryimage = createImage(width, height);

2) 생성한 이미지 버퍼에 대한 Graphics객체를 얻어옵니다.

Graphics mgc = memoryimage.getGraphics();

3) 이제, 리턴된 Graphics객체를 통해 원하는 페인팅 작업을 모두 수행합니다.

mgc.drawString("memory image-buffer", 10, 10);

4) 메모리 그래픽버퍼에 대한  페인팅작업이 모두 완료되면, 실제 화면의 Graphics 객체의 drawImage메소드를 사용하여 한번에 메모리 그래픽 버퍼에 저장된 내용을 화면에 출력합니다.

public void paint(Graphics g){

  g.drawImage(memoryimage, 0, 0, this);

}

 실제, 더블버퍼링을 사용한 간단한 예제를 소개합니다. 이 예제는 자동차 이미지가 좌쪽에서 우측으로 이동하는 애니메이션을 구현한 프로그램 입니다. 아래소스중, boolean형 usedbuffer 변수는 프로그램 내에서 더블 버퍼링을 사용할지를 결정하는 값으로 usedbuffer값을 true 로하면 더블버퍼링 처리를 하고, false로 바꾸면 더블버퍼링을 사용하지 않습니다. 

 //UsingDoubleBuffering.java

import java.awt.*;
public class UsingDoubleBuffering extends Frame implements Runnable {
    private Image offScreenImage;
    private Graphics offScreen;
    private Image img;
    private Thread th;
    private int speed_x, speed_y;
    private int x, y;
    private boolean usedbuffer = true;

    public UsingDoubleBuffering() {
        super("UsingDoubleBuffering");

        initlodation();
        MediaTracker tracker = new MediaTracker(this);
        img = Toolkit.getDefaultToolkit().getImage("car1.gif");
        tracker.addImage(img, 0);
        try {
            // MediaTracker을 사용하여 이미지가 완전히 로딩될때까지 대기
            tracker.waitForAll();
        } catch (InterruptedException e) {}

        //Thread생성
        th = new Thread(this);
        th.start();
    }

    public void run() {
        while (th != null) {
            repaint();
            try {
                th.sleep(40);
            } catch (InterruptedException e) {}
        }
    }

    void paintingjob(Graphics g, int w, int h) {
        g.clearRect(0, 0, w, h);
        x += speed_x;
        y += speed_y;
        if (x >= w) {
            initlodation();
        }
        g.drawImage(img, x, y, this);
        if (usedbuffer) {
            g.drawString("Use Double-Buffering", 100, h / 2);
        } else {
            g.drawString("Not Use Double-Buffering", 100, h / 2);
        }
    }

    void initlodation() {
        x = 0;
        y = 120;
        speed_x = 5;
        speed_y = 0;
    }

    public void update(Graphics g) {
        paint(g);
    }

    public void paint(Graphics g) {
        int w = this.getSize().width;
        int h = this.getSize().height;
        //메모리 버퍼를 생성하고, 버퍼에대한 Graphics객체를 얻어옴
        if (offScreen == null && usedbuffer) {
            try {
                offScreenImage = createImage(w, h);
                offScreen = offScreenImage.getGraphics();
            } catch (Exception e) {
                offScreen = null;
            }
        }
        //더블버퍼링을 사용하여 출력
        if (offScreen != null) {
            paintingjob(offScreen, w, h);
            g.drawImage(offScreenImage, 0, 0, this);
        }
        //일반적인 방식으로 출력
        else {
            paintingjob(g, w, h);
        }
    }

    public static void main(String[] args) {
        UsingDoubleBuffering frm = new UsingDoubleBuffering();
        frm.setSize(300, 200);
        frm.setResizable(false);
        frm.setVisible(true);
    }
}

더블버퍼링을 사용하여 UsingDoubleBuffering  프로그램을 실행하면, 자동차가 부드럽게 애니메이션 되는것을 볼수 있습니다. 반대로, 더블버퍼링을 사용하지 않는 경우는 화면이 가끔씩 깜박임을 체감할수 있습니다. 참고로, 소스에서 usedbuffer 값을 변경하므로써 더블버퍼링의 사용유무를 결정할수있습니다.

 그럼, UsingDoubleBuffering 프로그램내의 주요소스에 대한 살펴 보겠습니다.

 a)

 th = new Thread(this);
쓰레드를 생성합니다.

 b)

 while (th != null) {
            repaint();
            try {
                th.sleep(40);
            } catch (InterruptedException e) {}
  }

40밀리 세컨드 간격으로 화면을 재갱신 합니다.

c)

        int w = this.getSize().width;
        int h = this.getSize().height;
        if (offScreen == null && usedbuffer) {
            try {
                offScreenImage = createImage(w, h);
                offScreen = offScreenImage.getGraphics();
            } catch (Exception e) {
                offScreen = null;
            }
        }

 offScreenImage = createImage(w, h);

컴포넌트의 사이즈(w,h) 와 동일한 사이즈를 가지는 메모리상의 이미지 버퍼를 생성합니다.

offScreen = offScreenImage.getGraphics();
생성한 이미지 버퍼에 대한 Graphics객체를 리턴받습니다.

d)

        g.clearRect(0, 0, w, h);
        x += speed_x;
        y += speed_y;
        if (x >= w) {
            initlodation();
        }
        g.drawImage(img, x, y, this);

clearRect 메소드를 호출하여, (0,0)좌표에서 시작하여 폭이w, 높이가 h인 영역을 현재 컴포넌트의 배경색으로 칠하여 영역을 깨끗하게 지웁니다.

drawImage 메소드를 사용하여 img을 x, y위치에 출력합니다.

e)

 if (offScreen != null) {
            paintingjob(offScreen, w, h);
            g.drawImage(offScreenImage, 0, 0, this);
     }

offScreen가 null이 아닌경우 즉, 더블버퍼링을 사용한경우는 생성한 메모리 버퍼에 모든 페인팅작업을 완료후 메모리 버퍼 내용을 한번에 화면상에 출력합니다.

 

 

Comments