상세 컨텐츠

본문 제목

[Java] 자바 더블 버퍼링 이미지 처리 관련

JAVA

by AlrepondTech 2018. 8. 20. 18:12

본문

반응형

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://movefast.tistory.com/6

 

 

더블 버퍼링(Double Buffering)은 이중 버퍼링이라 불리기도 하며, 그래픽 객체에 이미지를 그릴 때 사용되는 기법이다.

 

 

Q) 왜 사용하는가 ? 

 

A) API를 시작하다보면 비트맵 이미지를 사용하게 된다. 그 때 이미지들이 전환되면서 영상처럼 부드럽게 움직일 거라 생각하지만 실제로 이미지들이 움직일 때마다 화면이 깜빡이는 현상이 눈에 들어온다. 쉽게 말하자면 아래와 같은 상황인 것이다.

 

▶ 게임 캐릭터이미지를 구현할 때 이미지를 움직이게 하고 싶다.

그러나 캐릭터가 띄엄띄엄 움직임과 동시에 깜빡거리는 화면

때문에 게임할 맛이 안난다.

 

 

그 이유는 컴퓨터가 이미지를 지웠다가 새 이미지를 다시 그리고 하는 방식을 반복하기 때문이다.

 

즉, 이미지를 그리는 데 시간이 소요되므로 이미지의 출력이 잦을수록 깜빡거리는 현상이 심해진다.

 

이에 대한 해결방안으로 버퍼 역할을 해줄 메모리 장치 컨텍스트(보이지 않는 화면)를 하나 더 사용하여 그곳에 이미지를 그리고, 기존화면을 유지하다가 이미지가 완성되면 실제 화면 장치 컨텍스트로 한꺼번에 베껴 그리는 것이다. 

 

아래는 이를 그림으로 표현한 것이다.

 

 

 

 

 

 

Q) 어떻게 사용하는가 ?

 

A) 자바를 예로 들자면,

 

//FIELDS

Image buffImage;

Graphics buffg;

 

//CONSTRUCTOR

(클래스명) {

repaint(); // 호출 시 repaint() -> update(g) -> paint(g) 순서로 메소드가 호출된다.

}

 

//METHODS

public void paint(Graphics g) {

if(buffg == null) {

buffImage = createImage(이미지 넓이, 이미지 높이); //버퍼링용 이미지 생성

 

if(buffImage == null) System.out.println("더블 버퍼링용 오프 스크린 생성 실패");

else buffg = buffImage.getGraphics(); 

//이미지를 생성해도 그래픽 객체를 얻어야 이미지 위에 그리고자하는 것을 그릴 수 있다.

}

update(g); // 이렇게 하면 반복적인 메소드 실행이 가능하다.

}

 

public void update(Graphics g) {

 

buffg.drawImage(그릴 이미지, 0, 0, this);

 

g.drawImage(buffImage, 0, 0, this); //실제 화면(g)으로 오프스크(buffg)에 그려진 이미지(buffImage)를 옮김.

}

 

 

=================================

=================================

=================================

 

 

 

출처: http://10gleza.tistory.com/entry/JAVA-%EC%95%A0%ED%94%8C%EB%A6%BF-%EC%9D%B4%EC%A4%91%EB%B2%84%ED%8D%BC%EB%A7%81

 

게임은 기본적으로 매 프레임마다 업데이트가 필요합니다.

또한 매 프레임마다 게임의 모든 화면을 지우고 다시 그려야 합니다.

이 과정에서 이중 버퍼링을 하지 않으면, 화면이 깜빡이는 현상이 발생하게 됩니다.

 

이중 버퍼링은 화면을 2개를 만듭니다.

그리고 이 2개 중 1개는 플레이어의 모니터에 표시가 되고,

남은 1개는 메모리상에 존재합니다.

 

메모리상에 존재하는 곳에 다음 프레임에 보일 화면을 먼저 그리고, 다 그려지면 두 화면을 교체합니다.

원래 모니터에 표시되었던 화면이 메모리로 돌아오면, 지우고 또 다음 프레임을 그리게 됩니다.

 

이 과정을 반복하는 행위를 이 중 버퍼링이라고 합니다.

 

애플릿은 이 이중버퍼링을 기본으로 지원하지 않으며

프레임 갱신이 지속적으로 되지 않습니다.

 

따라서, 애플릿에 쓰레드를 하나 생성하여 지속적으로 프레임 갱신을 되게 하고,

이 중 버퍼링을 추가하는 작업을 해야합니다.

 

다음은 이중 버퍼링 코드입니다.

 

package com.survival.game;

 

import java.applet.Applet;

import java.awt.*;

import java.awt.event.KeyEvent;

import java.awt.event.KeyListener;

 

public class GameMain extends Applet implements Runnable{

 

    Thread Game;

    Image    offScreen;

    Graphics bufferGraphics;

 

    final int WINDOW_WIDTH = 800;

    final int WINDOW_HEIGHT = 600;

     

    public void init()

    {

        setSize(WINDOW_WIDTH, WINDOW_HEIGHT);

 

        offScreen = createImage(WINDOW_WIDTH, WINDOW_HEIGHT);

        bufferGraphics = offScreen.getGraphics();

 

    }

    public void start()

    {

        if(Game == null)

        {

            Game = new Thread(this);

            Game.start();

        }

    }

     

    public void paint(Graphics g)

    {

        bufferGraphics.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

        // 렌더링 시작

         

        // 렌더링 종료

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

    }

     

    public void update(Graphics g)

    {

        paint(g);

    }

     

    // 게임 업데이트 함수

    public void update()

    {

        // 매 프레임마다 호출되는 공간입니다.

    }

     

    public void run() {

        // TODO Auto-generated method stub

        while(true)

            try {

                update();

                this.repaint();

                Game.sleep(33);

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

    }

 

     

    public void stop()

    {

        if(Game != null && Game.isAlive())

        {

            Game = null;

        }

    }

}

 

 

WINDOW_WIDTH 변수나 WINDOW_HEIGHT 변수를 수정하면, 창의 크기를 수정할 수 있습니다.

만약 저 범위 밖에 그릴 경우에는, 그린 모습이 보이지 않거나 지워지지 않을 수 있습니다.

 

 

프레임 제한은 60번 라인의 Game.sleep(33); 입니다.

프로그램은 1초에 약 1000번 루프를 돌기 때문에 1000을 30으로 나누었을때 근사값이 33이기 때문에 33값이 들어가 있습니다.

만약 16 정도의 값으로 바꾼다면, 60프레임이 나올 것이라 생각합니다.



 

 

=================================

=================================

=================================

 

 

출처: http://quadflask.tistory.com/290

 

 

컴푸터에서 그래픽을 표시하기 위해서는 일정 시간마다 화면을 뿌려줘야 합니다. 움직임이 있을때는 더욱 그렇죠.

 

그런데, 이 화면을 갱신하다보면, 자주 있는 일이 화면이 깜박이는 현상(flickering)입니다.

 

이것은, 컴퓨터가 화면을 뿌리는것과, 화면이 그려주는것이 서로 동시에 이루어 져서 사실, 좌측 상단에서부터 차례로 그리는것인데, 인간의 눈으로 보면, 이것이 너무 빨리 되기 때문에, 아직 안그려진부분이 순식간에 채워져서 깜박이는것처럼 보이는것입니다. // 이에대한 생각은 틀릴 수 도 있지만, 제가 경험해본 바로는 이렇습니다.

 

처음에, C로 콘솔 테트리스를 짤때도 이것이 문제가 되었습니다. 매초마다 화면을 갱신하다 보니, 전체를 그리려면, 시간이 좀 걸렸고, 몇몇부분에서는(특히, 이전에 그려져있었던 장소에 또 다시한번 그리는바람에) 깜박임 현상이 생겼습니다. 그때는, 또 그려야 하는 부분을 검출해서, 해당 부분을 그리지 않고, 변동사항이 생긴 부분만 그렸습니다.

그래서 어느정도 이 현상을 해결했었죠.

 

요즘에 자바로 만드는데, 역시 이 깜박임 현상이 문제가 됩니다........

 

하지만, 자바는 우릴 버리지 않았습니다.ㅋ(응?)

 

기존 awt에서 없던 기능인 더블버퍼링을 손쉽게 구현해주는 기능이 swing에 생겼기 때문이죠!

 

본래, 더블 버퍼링을 구현하려면, 일단, 임시 버퍼에 모두 그리고 나서, 이 임시버퍼를 다시 본래화면표시용 버퍼에 그리는 방식으로 했었습니다. 이렇게 하면, 일단 paint()의 내부 로직이 더럽게 됩니다....

 

swing에서는 이 문제를 아주 쉽게 해결할 수 있도록, JFrame에서 지원해줍니다.

 

{

.

.

.

setResizable(false);

 

setVisible(true);

 

 

createBufferStrategy(2);

}

 

(set... 한 부분은 중요하지 않습니다. 단지, createBufferStrategy() 매서드의 호출 순서를 보여줄 뿐,,)

 

바로 저것이.... 더블 버퍼(이중 버퍼)를 만드는 매서드입니다.... 참 쉽죠잉.......-_-

 

그리고나서, 버퍼에 그림을 그려야하는데;;; 어디다 그리느냐?

 

 

 

Graphics2D g = (Graphics2D) jframe.getBufferStrategy().getDrawGraphics();

 

g.setColor(font_debug);

// 무언가 그린다.

g.dispose();

 

getMasterWindow().getBufferStrategy().show();

 

 

 

이상입니다..

 

JFrame 인스턴스로부터 BufferStrategy 를 가져오고, 이 버퍼전략(...)으로부터 그래픽인터페이스를 받아오면됩니다....

 

마지막으로, 화면에 출력하기 위해, show() 하면 끝......

 

더블버퍼 참 쉽죠잉;;;-_-

 



출처: http://quadflask.tistory.com/290 [Flash & Flask]

 

 

 

=================================

=================================

=================================

 

 

 

반응형

 

728x90

 

 

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=veratis&logNo=20116050894&categoryNo=15&viewDate=&currentPage=1&listtype=0

 

Java AWT 더블버퍼링

 

게임이나 움직임이 많은 화면을 만들때 이미지가 깜빡거리는 현상을 볼 수 있다.

 

쓰레드가 repaint()를 호출하면 repaint()는 update(Graphics g)를 호출하고 update는 paint(Graphics g)를 호출한다.

 

update는 화면을 지우고 paint를 호출하는데 이때 깜빡거림이 생긴다.

 

그렇다고 update를 오버라이딩해서 화면을 지우지않고 paint를 호출하도록 한다면

 

이미지가 움직이면서 이전에 있던 위치에 이미지가 그대로 남게되버린다.

 

 

더블버퍼링을 사용하면 가상의공간을 만들어서 가상의공간에 이미지를 그리고 그 가상의이미지를 화면에 출력하게된다.

 

더블버퍼링을 사용하면서 update를 오버라이딩 해주면 이미지가 깜빡거리는 현상을 제거 할 수 있다.

 

 

import java.awt.*;
import java.awt.event.*;

public class BufferTest extends Frame implements MouseMotionListener
{
  Toolkit tk = Toolkit.getDefaultToolkit();  // 이미지를 띄우기 위해 Toolkit 인스턴스를 생성
  Image img_buffer = null;                  // 가상이미지 저장
  Graphics buffer = null;                   // 가상이미지 객체 저장
  
  int x;                                    // 이미지 x좌표

  public BufferTest(String sTitle){        // 초기화
    super(sTitle);
    addMouseMotionListener(this);
    setSize(500,100);
    setVisible(true);
    
    x = 0;
  }
  
  public void paint(Graphics g) {
    img_buffer = createImage(500,100);  // img_buffer에 size(500,500)의 Image 객체 생성
    buffer = img_buffer.getGraphics();  // img_buffer의 Graphics 객체를 얻어와 buffer 에 저장
    buffer.drawImage(tk.getImage("img_gray.jpg"),x,50,this);
    // img_buffer의 객체가 저장된 buffer를 사용해서 (x,50)에 이미지를 그린다 ( 가상공간 )

    g.drawImage(img_buffer,0,0,this);  // img_buffer를 그려준다. (0,0)좌표에 (500,100) 가상이미지 복사
  }
  
  public void update(Graphics g){    // update(Graphics g)는 화면을 지우고 paint(Graphics g)를 호출하는데
    paint(g);                          // 화면을 지우지 않고 paint를 호출하도록 update를 오버라이딩함.
  }
  
  public void mouseDragged(MouseEvent e) {}
  public void mouseMoved(MouseEvent e) {  // 마우스가 움직일때마다 repaint();
    x+=1;
    repaint();
  }
  
  public static void main(String[] args){
    new BufferTest("DoubleBuffer");
  }
}

 

 

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=dlrbtjd86&logNo=119958624

 

 

더블 버퍼링은 이미지를 화면에 바로 그리는 것이 아니라, 메모리(버퍼)에 먼저 그리고 화면에 나중에 그리는 방법이다.

 시스템 리소스는 더 필요하지만.

 

 

더블 버퍼링은 화면의 깜빡임을 줄이고, 자연스러운 애니메이션을 위해서 많이 사용된다.

  1. Image 객체를 만들어서 이곳에 그림을 그린다.
  2. update() 함수는 오버라이드해서 paint() 함수를 호출하도록한다.
  3. paint() 함수에서는 1번에서 생성한 이미지를 그린다.

 

 

/* Drawing in applets is almost always done with double-buffering.

 

This means that drawing is first done to an offscreen image, and when all

 

is done, the offscreen image is drawn on the screen.

 

This reduces the nasty flickering applets otherwise have.Above you see:

 

No Double-buffering : It flickers because everything is draw straight to the screen.

 

Bad double-buffering: Same as this below but without the update() method

 

Double Buffering: Example of this source.

 

*/

import java.applet.*; 
import java.awt.event.*; 
import java.awt.*;

public class DoubleBuffering extends Applet implements MouseMotionListener 

     

// The object we will use to write with instead of the standard screen graphics

 
     Graphics bufferGraphics; 

     // The image that will contain everything that has been drawn on

 

     // bufferGraphics.

 
     Image offscreen; 
     

// To get the width and height of the applet.

 
     Dimension dim; 
     int curX, curY;

     public void init()  
     { 
          

// We'll ask the width and height by this

 
          dim = getSize(); 
         

 // We'll redraw the applet eacht time the mouse has moved.

 
          addMouseMotionListener(this); 
          setBackground(Color.black); 

          // Create an offscreen image to draw on

 
         

 // Make it the size of the applet, this is just perfect larger

 

          // size could slow it down unnecessary.

 
          offscreen = createImage(dim.width,dim.height); 
         

 // by doing this everything that is drawn by bufferGraphics

 

          // will be written on the offscreen image.

 
          bufferGraphics = offscreen.getGraphics(); 
     }

      public void paint(Graphics g)  
     { 
          

// Wipe off everything that has been drawn before

 

          // Otherwise previous drawings would also be displayed.

 
          bufferGraphics.clearRect(0,0,dim.width,dim.width); 
          bufferGraphics.setColor(Color.red); 
          bufferGraphics.drawString("Bad Double-buffered",10,10); 
         

 // draw the rect at the current mouse position

 

          // to the offscreen image

 
          bufferGraphics.fillRect(curX,curY,20,20); 
          

// draw the offscreen image to the screen like a normal image.

 

          // Since offscreen is the screen width we start at 0,0.

 
          g.drawImage(offscreen,0,0,this); 
     }

     

// Always required for good double-buffering.

 

     // This will cause the applet not to first wipe off

 

     // previous drawings but to immediately repaint.

 

     // the wiping off also causes flickering.

 

     // Update is called automatically when repaint() is called.

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

     

// Save the current mouse position to paint a rectangle there.

 

     // and request a repaint()

 
     public void mouseMoved(MouseEvent evt)  
     { 
          curX = evt.getX(); 
          curY = evt.getY(); 
          repaint(); 
     } 
 

     

// The necessary methods.

 
     public void mouseDragged(MouseEvent evt)  
     { 
     }

 

 }

/*

 

This is all about double-buffering. It's easy to use and recommended to use always.

 

There is one dangerous pitfall here, when you create an offscreen image that's very large

 

the applet might run slow because it takes a lot of resources and effort.

 

I would not recommend offscreen images larger than 500*500 when redrawn at 30FPS.

 

(see Threads)

 

*/

 

*굳이 어플렛을 쓸 이유는 없음.. 단지 예제
예제의 결과물 보기 => http://www.realapplets.com/tutorial/DoubleBuffering.html

 

 

 

 

 

 

=================================

=================================

=================================

 

 

출처: http://dongz.tistory.com/entry/%EB%8D%94%EB%B8%94%EB%B2%84%ED%8D%BC%EB%A7%81

 

 

더블 버퍼링은 이미지를 화면에 바로 그리는 것이 아니라, 메모리(버퍼)에 먼저 그리고 화면에 나중에 그리는 방법이다. 더블 버퍼링은 화면의 깜빡임을 줄이고, 자연스러운 애니메이션을 위해서 많이 사용된다. 더블 버퍼링을 하기 위해서는 다음과 같은 절차를 거쳐야 한다.

  1. Image 객체를 만들어서 이곳에 그림을 그린다.
  2. update() 함수는 오버라이드해서 paint() 함수를 호출하도록한다.
  3. paint() 함수에서는 1번에서 생성한 이미지를 그린다.
예: Clock.java import java.awt.*;  import java.awt.image.*;  import java.applet.*;  import java.util.*;    public class Clock extends Applet implements Runnable {          Date     day;          Thread   runner;          Image    img;          Graphics gc;          Font     font;          int      w, h, d;          boolean  stop = false;           public void start() {                  day = new Date();                  font = new Font("TimesRoman", Font.BOLD, 24);                  FontMetrics metrics = getFontMetrics(font);                  d = metrics.getDescent();                  w = metrics.stringWidth(day.toString())+10;                 h = metrics.getHeight()+10;                  img = createImage(w , h);                 gc = img.getGraphics();                  gc.setFont(font);                  runner = new Thread(this);                  runner.start();          }            public void stop() {                 if(runner != null) {                         stop = true;                         runner = null;                 }         }           public void run() {                  while(!stop) {                          day = new Date();                          gc.setColor(Color.lightGray);                          gc.fillRect(0, 0, w + 10, h+10);                          gc.setColor(Color.black);                          gc.drawString(day.toString(),5, h -d);                          repaint();                          try {                                  Thread.sleep(1000);                          } catch(Exception ex) {                                  System.out.println("Exception:" + ex.toString());                          }                  }          }            public void update(Graphics g) {                  paint(g);          }            public void paint(Graphics g) {                  g.drawImage(img, 10, 50, this);          }  }   

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://donggeuri0320.tistory.com/entry/Java-%EB%8D%94%EB%B8%94%EB%B2%84%ED%8D%BC%EB%A7%81%EC%93%B0%EB%A0%88%EB%93%9C-%EC%98%88%EC%A0%9C-%ED%97%AC%EB%A1%9C%EC%9A%B0-%EB%93%80%ED%81%AC

 

 

프로그램 : 스페이스 바를 누르면 멈춘다. 다시 한번 스페이스 바를 누르면 움직인다.

 

images.zip
다운로드

 

HelloDukeMain.java
다운로드

 

 

 

좀 더 응용 해보기!

1. 멈추었던 듀크의 동작 순서를 기억 한 다음, 새로 시작할 때 그 다음 동작부터 시작하기 ( 움직임이 끊기지 않도록 )

2. 듀크 여러마리 만들기

 



출처: http://donggeuri0320.tistory.com/entry/Java-더블버퍼링쓰레드-예제-헬로우-듀크 [생각을 SHOW 하라]

 

 

=================================

=================================

=================================

 

 

 

*기타관련링크

http://q-in.tistory.com/entry/RunnerGame-%EC%BD%98%EC%86%94-%EB%8D%94%EB%B8%94%EB%B2%84%ED%8D%BC%EB%A7%81

 

 

=================================

=================================

=================================

 

 

 

반응형


관련글 더보기

댓글 영역