App Programming/JAVA2007. 7. 10. 15:31

Chapter

 18

  Java2D Graphics


그래픽을 구현하는 클래스로 Graphics 클래스와 이를 상속하는 Graphics2D 클래스가 있다. Graphics2D 클래스는 JDK1.2 이후에 추가된 것으로 2D(평면) 그래픽 환경을 강력히 지원하는 고수준의 API이다. 이번 장에서는 Java2D 그래픽을 이해하고 애니메이션이나 게임과 같은 응용프로그램에 활용하는 방법을 생각해 보자.

 

 

컴포넌트에 그리기

컴포넌트의 paint 메소드는 자신을 그릴 때 호출되는 메소드이다. 이 메소드는 컴포넌트가 다시 그려질 필요가 있을 때 자동으로 호출된다. 예를 들어 컴포넌트의 일부가 어떤 것에 의해 가려진 후, 다시 보여지면 가려졌던 부분을 다시 그릴 필요가 있을 것이다. 그때 이 메소드가 호출된다. 이 메소드의 헤더는 다음과 같다.

 

public void paint(Graphics g)

 

paint 메소드의 인수로 Graphics 객체(일반적으로 Graphic context라고 함)가 넘어온다. 컴포넌트의 일부를 다시 그릴 필요가 있으면 Graphics 객체를 이용하여 컴포넌트를 그린다. 프로그래머는 paint 메소드를 오버라이드해서 컴포넌트에 원하는 그림이나 도형을 그릴 수 있다.

 

Paint1.java

 

import java.awt.*;

public class Paint1 extends Frame{

  public Paint1(String title){               // 생성자

    super(title);

  }

  public void paint(Graphics g){

    g.drawLine(10,30,50,50);              // 선을 그린다.

    g.drawRect(60,30,50,50);             // 사각형을 그린다.

    g.drawString("Hello!",120,50);         // 문자열을 그린다.

  }

  public static void main(String[] args){

    Paint1 f=new Paint1("paint");

    f.setSize(200,100);

    f.setVisible(true);                     // 프레임이 보여질 때 paint가 호출된다.

  }

}



[그림 18-1] paint 메소드

 

paint 메소드는 필요에 의해 자동으로 호출된다. 하지만 화면이 가려지지 않더라도 화면을 갱신해야할 경우가 생길 것이다. 그 때는 repaint 메소드들 호출하면 된다. repaint 메소드가 호출되면 빠르게 update 메소드가 호출되고 update 메소드는 컴포넌트를 원래의 모습대로 그린 후에 paint를 호출한다.

 

repaint() -> update() -> paint()

 

paint 메소드를 오버라이드하지 않고도 컴포넌트에 그릴 수 있는 방법이 있다. Component 클래스의 getGraphics 메소드를 이용하는 것이다. 이 메소드는 컴포넌트의 Graphics 객체의 레퍼런스를 반환한다. 주의할 것은 컴포넌트가 보여질 수 없는 상태에서 getGraphics를 호출하면 null을 반환한다는 점이다.

 

public Graphics getGraphics()      // 컴포넌트의 Graphics 객체를 반환한다.

 

다음 예제를 실행하면 Paint1과 같은 모양의 프레임이 보인다. 하지만 paint 메소드를 오브라이드하지 않았기 때문에 프레임의 일부가 가려지면 가려진 화면은 지워진다.



Paint2.java

 

import java.awt.*;

public class Paint2{

  public static void main(String[] args){

    Frame f=new Frame("paint");

    f.setSize(200,100);

    f.setVisible(true);                  // 프레임을 보이게 한다.

    Graphics g=f.getGraphics();    // 프레임의 Graphics 객체를 얻어온다.

    g.drawLine(10,30,50,50);

    g.drawRect(60,30,50,50);

    g.drawString("Hello!",120,50);

  }

}





도형 그리기

Graphics 클래스는 여러 가지의 도형을 그릴 수 있는 메소드를 제공한다. Graphics 클래스는 대부분의 메소드가 추상이지만 편의상 abstract 키워드는 생략할 것이다.

 

Graphics 클래스의 유용한 메소드 1

 

public void drawLine(int x1, int y1, int x2, int y2)

좌표 (x1, y1)에서 (x2, y2)까지 직선을 긋는다.

public void drawRect(int x, int y, int width, int height)

좌표 (x, y)로부터 너비가 width이고 높이가 height인 사각형을 그린다.

public void drawRoundRect(int x, int y, int width, int height,

                                                    int arcWidth, int arcHeight)

(x, y)로부터 너비가 width이고 높이가 height인 모서리가 둥근 사각형을 그린다.

arcWidth 와 arcHeight는 모서리의 크기를 정한다.

public void drawOval(int x, int y, int width, int height)

(x, y)로부터 너비가 width이고 높이가 height인 사각형안에 들어가는 타원을 그린다.

width와 height가 같으면 원이 된다.

public void drawArc(int x, int y, int width, int height,

                                                  int startAngle, int arcAngle)

(x, y)로부터 너비가 width이고 높이가 height인 사각형안에 들어가는 원호를 그린다.

startAngle은 원호의 시작 각(도)이고, arcAngle은 원호로 형성되는 부채꼴의 각이다.

public void drawPolygon(int xPoints[], int yPoints[], int nPoints)

다각형을 그린다. 배열 xPoints와 yPoints는 꼭지점의 x와 y의 집합이고, nPoints는 다각형의 꼭지점 개수로 배열의 크기와 일치한다.

public void drawPolygon(Polygon p)

다각형 p를 그린다. Polygon은 다각형을 구현하는 클래스이다.

public void drawPolyline(int xPoints[], int yPoints[], int nPoints)

연결된 직선들을 그린다. 배열 xPoints와 yPoints는 연결점의 x와 y의 집합이고, nPoints는 연결점의 개수이다.

public void clearRect(int x, int y, int width, int height);

주어진 범위에 있는 이미지를 지운다.

 

다음 예제는 프레임에 각종 다각형을 그리는 것이다.

 

Draw1.java

 

import java.awt.*;

public class Draw1 extends Frame{

  public Draw1(String title){                    // 생성자

    super(title);

  }

  public void paint(Graphics g){

    g.drawLine(10,30,50,50);                  // 직선을 그린다.

    g.drawRect(60,30,50,50);                  // 사각형을 그린다.

    g.drawRoundRect(120,30,50,50,20,20);    // 둥근 사각형을 그린다.

    g.drawOval(10,100,70,50);                 // 타원을 그린다.

    g.drawArc(100,100,50,50,90,180);          // 원호를 그린다.

    int[] x=new int[]{200,240,200,220,240};

    int[] y=new int[]{80,80,120,60,120};

    g.drawPolygon(x,y,5);                     // 다각형을 그린다.

  }

  public static void main(String[] args){

    Frame f=new Draw1("도형 그리기");

    f.setSize(300,200);

    f.setVisible(true);

  }

}



[그림 18-2] 도형 그리기

 

안이 색으로 채워진 도형을 그릴 때는 다음과 같은 메소드를 사용한다.

 

 

Graphics 클래스의 유용한 메소드 2

 

public void fillRect(int x, int y, int width, int height)

채워진 사각형을 그린다.

public void fillOval(int x, int y, int width, int height)

채워진 타원을 그린다.

 

위의 메소드외에도 fillRoundRect, fillPolygon 등이 있다.

 

Draw2.java

 

import java.awt.*;

public class Draw2 extends Frame{

  public Draw2(String title){

    super(title);

  }

  public void paint(Graphics g){

    g.fillRect(60,30,50,50);

    g.fillRoundRect(120,30,50,50,20,20);

    g.fillOval(10,100,70,50);

  }

  public static void main(String[] args){

    Frame f=new Draw2("도형 그리기");

    f.setSize(300,200);

    f.setVisible(true);

  }

}



[그림 18-3] 채워진 도형 그리기

 

 

 

마우스로 곡선 그리기

마우스의 버튼을 누른 채로 움직이면 마우스 포인터를 따라 곡선이 그려지도록 해보자. 움직이기 전의 마우스 좌표(ox, oy)에서 움직인 후의 마우스 좌표(x, y)까지 직선을 긋는 방법을 이용한다.

 

Draw3.java

 

import java.awt.*;

import java.awt.event.*;

public class Draw3 extends Frame{

  Graphics g;                     // 프레임의 Graphics 객체를 위한 변수

  int x, y, ox, oy;         // 움직인 후의 좌표(x, y)와 움직이기 전의 좌표(ox, oy)

  public Draw3(String title){      // 생성자

    super(title);

    setSize(200,200);

    setVisible(true);

    g=this.getGraphics();       // 프레임의 Graphics 객체를 얻는다.

    g.setColor(Color.red);        // 그리기 색을 빨간 색으로 정한다.

 

    // 마우스 움직임 이벤트 처리

    addMouseMotionListener(new MouseMotionAdapter(){

        public void mouseDragged(MouseEvent e){        // 마우스가 움직이면

          x=e.getX(); y=e.getY();              // 마우스의 현재 위치를 알아온다.

 

          // 전 위치부터 현재 위치까지 직선을 긋는다.

          g.drawLine(ox, oy, x, y);

       

          ox=x; oy=y;                  // x와 y를 ox와 oy에 대입한다.

        }

    });

 

    // 마우스 이벤트 처리

    addMouseListener(new MouseAdapter(){

        // 마우스를 누르면 호출된다.

        public void mousePressed(MouseEvent e){

           ox=e.getX(); oy=e.getY();             // 마우스의 위치를 기억한다.

        }

    });

  }

  public static void main(String[] args){

    Frame f=new Draw3("도형 그리기");

  }

}



[그림 18-4] 곡선 그리기

 

 

 

이미지 그리기

로컬 컴퓨터에 있는 이미지뿐만 아니라 네트워크 상에 있는 이미지도 쉽게 불러올 수 있다. 이미지를 처리하는 과정은 크게 이미지를 불러오는 작업과 이미지를 그리는 작업으로 생각할 수 있다. 그런데 문제는 이미지를 그리는 작업은 Graphics 객체가 담당한다지만 이미지를 불러오는 작업은 Toolkit 객체가 담당한다는 것이다. 이는 이미지를 불러오는 작업과 이미지를 그리는 작업이 비동기적임 의미한다. 따라서 이미지가 완전히 다운로드 되었는지, 혹은 다운로드하고 있는 중인지하는 이미지의 상태를 추적하는 작업을 필요하다. URL 객체는 이미지의 위치를 나타낼 수 있고, MediaTracker 객체는 이미지의 상태를 추적할 수 있다.

 

이미지처리에 필요한 클래스와 그 역할을 정리하면 다음과 같다.

 

 

클래스

역할

Graphics

이미지를 그린다.

Image

이미지(그림)를 구현한다.

URL

이미지의 위치를 구현한다.

Toolkit

이미지를 불러온다.

MediaTracker

이미지의 상태를 추적한다.

 

 

 

Graphics 클래스

Graphics 클래스는 이미지를 확대 또는 축소하여 그리거나 이미지의 부분 영역을 그릴 수 있는 메소드, drawImage를 제공한다. drawImage 메소드는 ImageObserver라는 객체를 인수로 받는데 ImageObserver는 이미지의 정보를 전달받아서 이미지를 업데이트시키는 역할을 한다. 이미지가 완전히 불려지지 않았을 때 drawImage를 호출하면 ImageObserver 객체에게 그 정보가 전달되고 ImageObserver는 이미지를 관찰하고 있다가 이미지가 완전히 불려지면 이미지를 업데이트시킨다. 컴포넌트 클래스가 이 인터페이스를 구현하고 있다. 따라서 이미지가 그려질 컴포넌트를 ImageObserver로 두면 된다.

 

Graphics 클래스는 다음과 같이 이미지를 그리는 메소드를 제공한다.

 

 

Graphics 클래스의 유용한 메소드

 

public abstract boolean drawImage(Image img, int x, int y,

                                      ImageObserver observer);

이미지 img를 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y, Color bgcolor,

                                      ImageObserver observer);

배경색을 bgcolor로 채운 후에 이미지 img를 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y, int width,

                                      int height, ImageObserver observer);

이미지 img의 너비를 width로, 높이를 height로 확대하거나 축소하여 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int x, int y,

                                    int width, int height,

                                    Color bgcolor, ImageObserver observer);

배경색을 bgcolor로 채우고 이미지 img의 너비를 width로 높이를 height로 확대하거나 축소한 후 (x, y)에 그린다.

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2,

                                    int dy2, int sx1, int sy1, int sx2, int sy2,

                                    ImageObserver observer);

이미지 img의 (sx1, sy1, sx2, sy2)영역을 (dx1, dy1, dx2, dy2)영역에 그린다.

두 영역의 크기가 같지 않는 경우 확대되거나 축소된다.

public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2,

                                    int dy2, int sx1, int sy1, int sx2, int sy2,

                                    Color bgcolor, ImageObserver observer);

배경색을 bgcolor로 채우고 이미지 img의 (sx1, sy1, sx2, sy2)영역을 (dx1, dy1, dx2, dy2)영역에 그린다.

 

실제로 그리는 작업은 조금 후에 알아보고 Image 클래스와 URL 클래스를 살펴보자.

 

 

 

Image 클래스

Image 클래스는 그래픽 이미지를 표현하는 추상 클래스이다. 따라서 new 키워드를 사용하여 이미지 객체를 생성할 수는 없다. 대신에 Toolkit 객체로부터 이미지를 받아오면 된다.

 

Toolkit toolkit=Toolkit.getDefaultToolkit();      // 기본 툴킷 객체를 얻는다.

Image img=toolkit.getImage(URL객체);         // 이미지를 얻는다.

 

또는 Component 클래스의 createImage 메소드를 이용하여 새로운 이미지를 만들 수도 있다.

 

// 가로 크기가 100이고 세로 크기가 200인 이미지를 생성한다.

Image img = 컴포넌트.createImage(100, 200);

 

이렇게 만든 이미지는 더블 버퍼링에 사용될 수 있다. Toolkit 클래스와 더블 버퍼링 기법은 조금 후에 알아보고 Image 클래스의 유용한 메소드를 살펴보자.

 

 

Image 클래스의 유용한 메소드

 

public abstract Graphics getGraphics();

이미지의 Graphics 객체를 반환한다. 이미지를 수정할 때 사용된다.

public abstract int getWidth(ImageObserver observer);

이미지의 가로 크기를 반환한다.

public abstract int getHeight(ImageObserver observer);

이미지의 세로 크기를 반환한다.

 

 

 

URL 클래스

URL 클래스는 월드와이드웹(www)상의 URL(Uniform Resource Locator)을 표현하는 클래스이다. URL은 웹 상에 있는 특정 파일의 위치를 명시하기 위한 것으로 서비스의 종류, 서버의 위치(도메인), 파일 이름으로 구성된다.

 

URL의 구성

서비스의 종류(프로토콜)

http, ftp

서버의 위치(도메인)

java.sun.com

파일이름

index.html, image.gif

 

다음은 sun의 자바 홈페이지에 있는 어떤 그림에 대한 URL 객체를 생성하는 예이다.

 

URL url=new URL("http://java.sun.com/images/v4_java_logo.gif");

 

어떤 클래스에 대한 상대 경로를 이용하여 URL 객체를 얻을 수도 있다.

 

// 객체의 런타임 클래스 객체를 얻는다.

Class cl = 객체.getClass();

 

// 클래스와 같은 위치에 있는 파일에 대한 URL 객체를 얻는다.

URL url = cl.getResource(파일이름);

 

 

Url1.java

 

import java.awt.*;

import java.net.*;                      // URL을 import한다.

public class Url1{

  public static void main(String[] args){

    Url1 ob=new Url1();

    Class cl= ob.getClass();                 // ob에 대한 클래스 객체를 얻는다.

    URL url=cl.getResource("Url1.java");    // "Url1.java"의 Url 객체를 만든다.

    System.out.println(cl.toString());

    System.out.println(url.toString());

  }

}



출력 결과

 

class Url1

file:/C:/java2/source/Url1.java




URL 클래스는 네트워크 프로그래밍 장에서 다시 살펴보자.

 

 

 

Toolkit 클래스

이 클래스는 이미지를 불러오거나 화면에 대한 정보를 제공한다.

 

 

Toolkit 클래스의 유용한 메소드

 

public static synchronized Toolkit getDefaultToolkit()

기본 툴킷 객체를 반환한다.

public abstract Image getImage(String filename);

filename에 해당되는 이미지를 반환한다.

public abstract Image getImage(URL url);

url에 해당되는 이미지를 반환한다.

public abstract boolean prepareImage(Image image, int width, int height,

                                       ImageObserver observer);

너비가 width이고 높이가 height인 이미지를 그릴 수 있도록 준비한다.

width와 height에 -1을 대입하면 이미지의 원래 크기로 준비한다.

public abstract Dimension getScreenSize();

스크린의 사이즈를 반환한다.

 

getImage 메소드는 이미지의 위치(URL)만 생성할 뿐이지 이미지를 불러오는 것은 아니다. 그래픽 객체의 drawImage 메소드를 호출하면 비로소 이미지를 불러와서 그리게 된다. 이미지를 불러오는 시간이 소요되기 때문에 drawImage를 호출했을 때, 바로 그려지지 않는다. 그래서 이미지를 불러와 그릴 수 있는 상태로 만들어주는 prepareImage 메소드를 따로 만들어 두었다. 이 메소드를 호출하면 이미지를 불러오는 작업을 시작하고 이미지가 그려질 수 있는 상태가 되면 true를 반환한다. 그러나 이미지의 크기가 크거나 인터넷상에 있어서 불러오는 시간이 많이 소요되는 경우에는 prepareImage를 호출하여도 바로 준비시켜주지 못하고 false를 반환하는 경우가 있다. 이때는 다시 prepareImage를 호출하여 이미지가 준비된 상태인지 알아보아야 하는 불편함이 있다.

 

Frame f=new Frame();

Graphics g=f.getGraphics();                // 프레임의 그래픽 객체를 얻는다.

Toolkit toolkit=f.getToolkit();                // 프레임의 툴킷 객체를 얻는다.

Image img=toolkit.getImage(URL객체);     // 이미지를 얻는다.

toolkit.prepareImage(img, -1, -1, f);     // img를 그릴 수 있게 준비시킨다.

g.drawImage(img, 10,10, f);              // img를 그린다.

 

다음은 인터넷상에 있는 그림을 불러와 프레임에 그리는 예제이다. 그림이 존재하지 않아서 그려지지 않는다면 다른 URL을 사용해 보자.

 

image1.java

 

import java.awt.*;

import java.net.*;

public class Image1 extends Frame{

  Image img;

  Toolkit tkit;

  public Image1(String title){                  // 생성자

    super(title);

    tkit=getToolkit();                // this(프레임)의 Toolkit 객체를 얻는다.

    try{

      URL url=new URL("http://java.sun.com/images/v4_java_logo.gif");

 

      img = tkit.getImage(url);               // url에 대한 이미지 객체를 만든다.

      tkit.prepareImage(img,-1,-1,this);     // 이미지를 준비시킨다.

    }catch(MalformedURLException e){      // URL이 이상할 경우에 발생한다.

      System.out.println("URL이 올바르지 않습니다.");

    }

  }

  public void paint(Graphics g){

     g.drawImage(img,80,80,this);             // 이미지를 그린다.

  }

  public static void main(String[] args){

    Frame f=new Image1("이미지 그리기");

    f.setSize(200,200);

    f.setVisible(true);

  }

}



[그림 18-5] 이미지 그리기

 

 

 

MediaTracker 클래스

미디어 트래커는 많은 이미지의 상태를 점검하는데 편리하게 사용된다. 미디어 트래커는 다운로드할 이미지를 추가하거나 제거할 수 있는 리스트를 가지고 있다. 리스트에 이미지를 추가할 때는 이미지와 함께 아이디(ID)도 함께 추가한다. ID는 중복될 수 있고 같은 ID의 이미지들은 묶어서 처리할 수 있다.

 

 

MediaTracker 클래스의 유용한 생성자

 

public MediaTracker(Component comp)

주어진 컴포넌트(comp)에 그려질 이미지들을 추적하는 미디어 트래커를 만든다.

 

 

MediaTracker 클래스의 유용한 메소드

 

public void addImage(Image image, int id)

public void addImage(Image image, int id, int w, int h)

id를 가진 image를 리스트에 추가하고, w(너비)와 h(높이)로 이미지의 배율을 조정한다. id는 중복될 수 있다.

public void removeImage(Image image)

public void removeImage(Image image, int id)

public void removeImage(Image image, int id, int width, int height)

이미지를 목록에서 제거한다.

public boolean checkAll()

모든 이미지가 로딩(loading)되었으면 true를, 아니면 false를 반환한다.

public boolean checkID(int id)

public boolean checkID(int id, boolean load)

id를 가진 이미지를 체크한다. load에 true를 대입하면 로딩되지 않은 이미지를 로딩하기 시작한다.

public synchronized Object[] getErrorsAny()

public synchronized Object[] getErrorsID(int id)

에러가 발생한 이미지들을 모두 반환하거나 주어진 id의 이미지들을 반환하다.

public synchronized boolean isErrorAny()

public synchronized boolean isErrorID(int id)

로딩하는 동안 이미지에 에러가 있으면 true를, 없으면 false를 반환한다.

public void waitForAll() throws InterruptedException

public boolean waitForAll(long ms) throws InterruptedException

모든 이미지가 로딩될 때까지 무작정 기다리거나 ms(milli second)까지 기다린다.

도중에 에러가 발생하면 모두 로딩된 것으로 간주하므로 isErrorAny로 에러가 있는지 검사해보아야 한다.

public void waitForID(int id) throws InterruptedException

public boolean waitForID(int id, long ms) throws InterruptedException

주어진 id를 가진 이미지가 로딩될 때까지 기다리거나 ms까지 기다린다. 도중에 에러가 발생하면 모두 로딩된 것으로 간주한다.



다음 예제는 이미지를 모두 불러온 후에 프레임에 그리는 예제이다. 불러오지 못한 이미지가 있으면 에러 메시지를 출력하고 프로그램을 종료할 것이다. 그러면 지정한 URL에 해당하는 이미지가 없을 수도 있으므로 URL을 바꾸어 실행해 보자.

 

Image2.java

 

import java.awt.*;

import java.net.*;

public class Image2 extends Frame{

  Image[] img;                         // 이미지를 위한 배열

  Toolkit tkit;

  public Image2(String title){           // 생성자

    super(title);

    tkit=getToolkit();                   // this의 툴킷 객체를 얻는다.

    img=new Image[2];                // 2개의 이미지

    URL url;

    try{

      url=new URL("http://java.sun.com/docs/books/tutorial/figures/2d/2D-1.gif");

      img[0]=tkit.getImage(url);          // 이미지 객체를 생성한다.

 

      url=new URL("http://java.sun.com/docs/books/tutorial/figures/2d/2D-2.gif");

      img[1]=tkit.getImage(url);         // 이미지 객체를 생성한다.

 

      MediaTracker mTracker =new MediaTracker(this);  // 미디어 트래커 객체

 

      // 미디어 트래커에 이미지를 추가한다.

      mTracker.addImage(img[0],0);                   // ID = 0

      mTracker.addImage(img[1],1);                      // ID = 1

 

      System.out.println("이미지 로딩중...");

      mTracker.waitForAll();             // 이미지가 완전히 로딩되도록 기다린다.

 

      if(mTracker.isErrorAny()){                       // 이미지에 에러가 있으면

        System.out.println("이미지 다운로드 오류");      // 메시지를 출력한다.

        System.exit(1);                               // 프로그램을 종료한다.

      }

 

      System.out.println("이미지 로딩 완료");

    }catch(Exception e){

      System.out.println(e);

    }

  }

  public void paint(Graphics g){

    g.drawImage(img[0],20,30,this);   // 이미지를 그린다.

    g.drawImage(img[1],20,160,this);

  }

  public static void main(String[] args){

    Frame f=new Image2("이미지 그리기");

    f.setSize(450,250);

    f.setVisible(true);

  }

}



[그림 18-6] MediaTracker 활용

 

 

 

Graphics2D

Graphics2D는 Graphics 클래스를 상속하는 클래스이다. 이 클래스는 2D 그래픽 환경을 강력히 지원하는 진보된 그래픽 클래스이다. AWT 컴포넌트의 paint(Graphics g) 메소드의 인수로 넘어오는 Graphics 객체는 Graphics2D 객체로 형 변환이 가능하다.

 

public void paint(Graphics g){

  Graphics2D g2=(Graphics2D)g;    // Graphics2D 객체로 형 변환

}

 

사실 컴포넌트의 그래픽 객체는 Graphics 객체가 아니라 Graphics2D 객체이기 때문에 형 변환이 가능한 것이다. Graphics2D는 JDK1.2 이후에 추가된 API로 이전의 기본적인 그래픽 환경을 강력한 2D 그래픽 환경으로 개선시켰다. 그러므로 Graphics 객체를 Graphic2D 객체로 형 변환만 하면 2D 그래픽 환경을 사용할 수 있다.

 

 

 

기하학적 도형

java.awt.geom 패키지를 보면 기하학적인 도형을 나타내는 클래스들이 있다. 모두 Shape(도형) 인터페이스를 구현한다. Shape 인터페이스를 구현하는 클래스의 종류로 Line2D, Rectangle2D, Ellipse2D, GeneralPath 등이 있다. 2D 환경에서는 좌표계가 확대, 축소 또는 회전이 되기 때문에 이런 클래스들은 정수가 아닌 double 또는 float으로 이루어진 좌표를 사용한다.

 

 

Shape 인터페이스의 유용한 메소드

 

public boolean contains(double x, double y);

좌표 (x, y)가 도형 내부에 있는 점이면 true를, 아니면 false를 반환한다.

public boolean contains(double x, double y, double w, double h);

영역 (x, y, w, h)이 도형 내부에 있으면 true를, 아니면 false를 반환한다.

public Rectangle getBounds();

public Rectangle2D getBounds2D();

도형을 감싸는 사각형 영역을 반환한다.

public boolean intersects(double x, double y, double w, double h);

public boolean intersects(Rectangle2D r);

해당 영역이 도형과 겹치면 true를, 아니면 false를 반환한다.

 

Graphics2D 객체의 draw와 fill 메소드를 이용하면 도형을 그릴 수 있다.

 

 

Graphics2D 클래스의 유용한 메소드

 

public abstract void draw(Shape s);

도형의 외각을 그린다.

public abstract void fill(Shape s);

채워진 도형을 그린다.




Line2D

이 클래스는 평면상의 직선을 나타내는 추상 클래스이다. 자식 클래스로 double 좌표를 사용하는 Line2D.Double과 float 좌표를 사용하는 Line2D.Float이 있다. 생성자의 인수로 두 점의 좌표 (x1, y1), (x2, y2)을 필요로 한다.

 

import java.awt.*;

import java.awt.geom.*;

...

public void paint(Graphics g){

  Graphics2D g2=(Graphics2D)g;        // Graphics2D 객체로 형 변환한다.

  Line2D line1=new Line2D.Double(10.0, 30.0, 100.0, 100.0);  // Line2D 객체

  Line2D line2=new Line2D.Float(100.0f, 30.0f, 10.0f, 100.0f);

 

  g2.draw(line1);  // 도형을 그린다.

  g2.fill(line2);

}

 

 

 

Rectangle2D

이 클래스는 평면상의 사각형을 나타내는 추상 클래스이다. 자식 클래스로 Line2D와  마찬가지로 Rectangle2D.Double과 Rectangle2D.Float이 있다. 생성자의 인수로 왼쪽 상단의 꼭지점의 좌표(x, y)와 너비, 높이가 필요하다.

 

Rectangle2D rect1=new Rectangle2D.Double(10.0, 30.0, 100.0, 100.0);

Rectangle2D rect2=new Rectangle2D.Float(150.0f, 30.0f, 100.0f, 100.0f);

g2.draw(rect1);

g2.fill(rect2);

 

 

 

Ellipse2D

이 클래스는 평면상의 타원을 구현하는 추상 클래스이다. 생성자의 인수로 타원을 포함하는 사각형의 왼쪽 상단의 좌표(x, y)와 너비, 높이가 필요하다. 너비와 높이를 같게 하면 원이 된다.

 

Ellipse2D ellipse1=new Ellipse2D.Double(10.0, 30.0, 100.0, 100.0);

Ellipse2D ellipse2=new Ellipse2D.Float(150.0f, 30.0f, 100.0f, 100.0f);

g2.draw(ellipse1);

g2.fill(ellipse2);

 

다음 예제는 Line2D와 Rectangle2D, Ellipse2D를 이용하여 직선과 사각형, 타원(원)을 그리는 예제이다.

 

Shape1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Shape1 extends Frame{

  public Shape1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    Line2D line1=new Line2D.Double(50.0, 50.0, 250.0, 50.0);

    Rectangle2D rect1=new Rectangle2D.Double(50.0, 100.0, 100.0, 100.0);

    Ellipse2D ellipse1=new Ellipse2D.Double(200.0, 100.0, 100.0, 100.0);

    g2.draw(line1);

    g2.draw(rect1);

    g2.fill(ellipse1);

  }

  public static void main(String[] args){

    Frame f=new Shape1("평면 도형");

    f.setSize(350,250);

    f.setVisible(true);

  }

}



[그림 18-7] 2D 도형

 

 

 

GeneralPath

GeneralPath는 직선 또는 곡선으로 연결된 경로에 의해서 형성되는 도형을 구현한 클래스이다.

 

GeneralPath 클래스의 유용한 메소드

 

public synchronized void moveTo(float x, float y)

현재의 점을 (x, y)로 이동한다.

public synchronized void lineTo(float x, float y)

현재의 점에서 (x, y)까지 직선을 긋는다.

public void append(Shape s, boolean connect)

새로운 도형 s를 추가한다. connect가 true이면 이전의 도형과 연결한다.

public synchronized void closePath()

처음 점과 끝점을 연결하는 직선을 그어 경로를 닫는다.

 

단순한 예제이므로 이해하기 쉬울 것으로 생각된다.

 

Shape2.java

 

import java.awt.*;

import java.awt.geom.*;

public class Shape2 extends Frame{

  public Shape2(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    GeneralPath gp=new GeneralPath();  // GeneralPath 객체를 만든다.

    gp.moveTo(150,50);                  // (150, 50)으로 현재의 점을 옮긴다.

    gp.lineTo(150,250);                   // (150, 250)까지 직선을 긋는다.

    gp.lineTo(250,150);                   // (250, 150)까지 직선을 긋는다.

    gp.lineTo(50,150);                    // (50, 150)까지 직선을 긋는다.

    gp.closePath();                 // 처음 점과 끝점을 연결하여 경로를 닫는다.

   

    g2.draw(gp);                           // GeneralPath 객체를 그린다.

  }

  public static void main(String[] args){

    Frame f=new Shape2("GeneralPath");

    f.setSize(300,300);

    f.setVisible(true);

  }

}



[그림 18-8] GeneralPath

 

 

 

렌더링 속성

2D 그래픽 환경에서 도형이나 이미지를 그릴 때 선의 두께와 스타일, 칠하기 패턴과 색의 연산, 좌표의 변환 등을 적용할 수 있다. 이런 것들을 렌더링 속성(Rendering Attributes)이라고 하고 Stroke, Paint, Composite, Transform, Clip, RenderingHints 등이 있다.

 

 

Stroke 속성

스트로크(Stroke)란 도형의 외각선 모양을 결정하는 속성이다. 스트로크를 구현하는 클래스로 BasicStroke가 있다. BasicStroke 객체는 선의 두께와 스타일, 끝점(end cap)의 모양, 꼭지점(end join: 선과 선이 만나는 부분)의 모양을 설정한다.

 

 

BasicStroke 클래스의 유용한 상수

 

public final static int CAP_BUTT = 0;

선이 끝나는 곳의 모양을 사각형으로 설정한다.

public final static int CAP_ROUND = 1;

선이 끝나는 곳의 모양을 둥글게 설정한다.

public final static int CAP_SQUARE = 2;

모양은 CAP_BUTT와 같으나 선두께의 반만큼 나오게 설정한다.

public final static int JOIN_MITER = 0;

꼭지점의 모양을 날카롭게 설정한다.

public final static int JOIN_ROUND = 1;

꼭지점의 모양을 둥글게 설정한다.

public final static int JOIN_BEVEL = 2;

꼭지점의 날카로운 부분을 자른다.

 

 

BasicStroke 클래스의 유용한 생성자

 

public BasicStroke(float width, int cap, int join, float miterlimit,

                     float dash[], float dash_phase)

width(선의 두께), cap(끝점의 모양), join(꼭지점의 모양),

miterlimit(꼭지점 길이의 한계: 1이상 값), dash(점선의 패턴 길이 모양),

dash_phase(점선간의 공백 거리)가 설정된 스트로크 객체를 만든다.

public BasicStroke(float width, int cap, int join, float miterlimit)

BasicStroke(width, cap, join, miterlimit, null, 0.0f)와 같다.

dash가 null이고 dash_phase가 0이면 실선을 의미한다.

public BasicStroke(float width, int cap, int join)

BasicStroke(width, cap, join, 10.0f, null, 0.0f)와 같다.

public BasicStroke(float width)

BasicStroke(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f)와 같다.

public BasicStroke()

BasicStroke(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f)와 같다.

 

BasicStroke를 그래픽 환경에 적용하려면 Graphics2D 객체의 setStroke 메소드를 호출한다.

 

g2.setStroke(new BasicStroke(30));

 

다음은 Stroke를 설정하여 도형을 그리는 예제이다.

 

Stroke1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Stroke1 extends Frame{

  public Stroke1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

   

    // CAP_ROUND를 사용한 직선 그리기

    g2.setStroke(new BasicStroke(30,BasicStroke.CAP_ROUND,0));

    g2.draw(new Line2D.Double(50,50,200,50));

   

    // JOIN_ROUND로 사각형 그리기

    g2.setStroke(new BasicStroke(30,0,BasicStroke.JOIN_ROUND));

    g2.draw(new Rectangle2D.Double(50,100,50,50));

   

    // JOIN_BEVEL으로 사각형 그리기

    g2.setStroke(new BasicStroke(30,0,BasicStroke.JOIN_BEVEL));

    g2.draw(new Rectangle2D.Double(150,100,50,50));

   

    // JOIN_MITER로 점선 그리기

    float[] dash=new float[]{10,5,5,5};

    g2.setStroke(new BasicStroke(5,0,BasicStroke.JOIN_MITER,1.0f,dash, 0));

    g2.draw(new Rectangle2D.Double(50,200,150,50));

   

  }

  public static void main(String[] args){

    Frame f=new Stroke1("스트로크");

    f.setSize(250,300);

    f.setVisible(true);

  }  

}



[그림 18-9] Stroke 속성

 

 

Paint 속성

이 속성은 도형의 내부를 채울 때 적용되는 속성으로 단일 색 속성, 그라데이션 속성, 타일 속성이 있다.

 

Color

단일 색으로 도형 내부를 채운다.

GradientPaint

선형적으로 변하는 색으로 내부를 채운다. 그라데이션 효과

TexturePaint

작은 이미지를 반복해서 채운다. 타일 효과

 

Paint 속성을 적용하려면 Graphics2D 객체의 setPaint 메소드들 사용한다. 단일 색으로 채우려면 다음 코드와 같이 할 수 있다.

 

g2.setPaint( Color.red );        // 빨간 색으로 채우는 Paint를 적용한다.

 


다음으로 그라데이션 효과를 위한 GradientPaint 클래스를 살펴보자.

 

GradientPaint 클래스의 유용한 생성자

 

public GradientPaint(float x1, float y1, Color color1,

                      float x2, float y2, Color color2 )

시작점(x1, y1)의 색(color1)에서 끝점(x2, y2)의 색(color2)으로 변하는 Paint 객체를 만든다.

public GradientPaint(float x1, float y1, Color color1,

                      float x2, float y2, Color color2, boolean cyclic)

시작점(x1, y1)의 색(color1)에서 끝점(x2, y2)의 색(color2)으로 변하는 Paint 객체를 만든다. cyclic이 true이면 반복적으로 그라데이션 효과가 적용된다.

 

GradientPaint 객체를 생성하고 Graphics2D 객체에 적용하는 코드를 예로 들면 아래와 같다.

 

g2.setPaint( new GradientPaint(0, 0, Color.white, 50, 50, Color.blue, true) );

 

다음은 프레임의 생성자에서 네 개의 GradientPaint 객체를 생성한 후에 paint 메소드에서 GradientPaint 속성을 적용하여 사각형을 그리는 예제이다.

 

Gradient1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Gradient1 extends Frame{

  Paint gPaint1, gPaint2, gPaint3, gPaint4;   // Paint 속성을 위한 변수들

 

  public Gradient1(String title){              // 생성자

    super(title);

 

    // GradientPaint 객체를 생성한다.

    gPaint1=new GradientPaint(10, 50, Color.white, 20, 60, Color.blue, true);

    gPaint2=new GradientPaint(100, 50, Color.white, 100, 30, Color.blue, true);

    gPaint3=new GradientPaint(190, 50, Color.white, 270, 250, Color.blue, false);

    gPaint4=new GradientPaint(280, 50, Color.white, 280, 250, Color.blue, false);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

   

    // GradientPaint를 적용하여 사각형을 그린다.

    g2.setPaint(gPaint1);

    g2.fill(new Rectangle2D.Double(10,50,80,200));

    g2.setPaint(gPaint2);

    g2.fill(new Rectangle2D.Double(100,50,80,200));

    g2.setPaint(gPaint3);

    g2.fill(new Rectangle2D.Double(190,50,80,200));

    g2.setPaint(gPaint4);

    g2.fill(new Rectangle2D.Double(280,50,80,200));

  }  

  public static void main(String []args){

    Frame f=new Gradient1("그라데이션 효과");

    f.setSize(400,300);

    f.setVisible(true);

  }

}



[그림 18-10] 그라데이션 효과

 

 

마지막으로 TexturePaint는 작은 그림을 반복적으로 채우기 위한 클래스이다. 작은 그림으로 BufferedImage 객체를 사용하는데 BufferedImage 클래스는 이미지의 각 픽셀의 정보를 버퍼에 기억하고 있어 프로그래머는 그 버퍼에 직접 접근하여 이미지의 정보를 바꿀 수 있다. 따라서 BufferedImage를 사용하면 이미지를 다양하게 처리할 수 있다.

 

BufferedImage 클래스의 유용한 생성자

 

public BufferedImage(int width, int height, int imageType)

imageType으로 설정된 주어진 크기(width×height)의 BufferedImage 객체를 만든다.

 

image type은 하나의 픽셀이 차지하는 비트 수와 RGB의 정보를 나타내는 것으로 상수로 정의 되어있다. 예를 들어 TYPE_INT_RGB은 한 픽셀의 크기가 4바이트(Int)이고 RGB는 각각 8비트씩 차지하는 이미지 타입을 나타낸다.

 

TexturePaint 속성은 BufferedImage 객체와 더불어 이미지를 맵핑시키는 사각형 영역에 대한 정보를 필요로 한다. 이 영역을 앵커(anchor)라고 하고 Rectangle2D 객체로 표현한다. TexturePaint 속성은 다음 그림과 같이 앵커를 기준으로 모든 방향으로 BufferedImage을 채운다.

 

 

anchor

 


실제로 TexturePaint 클래스의 생성자는 다음과 같다.

 

TexturePaint 클래스의 유용한 생성자

 

public TexturePaint( BufferedImage txtr, Rectangle2D anchor )

txtr을 anchor에 맵핑하는 TexturePaint 객체를 만든다.

 

다음은 TexturePaint 속성으로 도형을 그리는 예제이다.

 

Texture1.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.image.*;

public class Texture1 extends Frame{

  Paint tPaint1, tPaint2, tPaint3, tPaint4;    // TexturePaint 속성을 위한 변수들

 

  public Texture1(String title){             // 생성자

    super(title);

 

    // 10×10 크기의 BufferedImage 객체를 만든다.

    BufferedImage buf=

               new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB);

 

    // BufferedImage 객체의 Graphics2D 객체를 얻는다.

    Graphics2D g2buf=(Graphics2D)buf.getGraphics();

   

    // BufferedImage 객체에 채워진 원을 그린다.

    g2buf.setPaint(new Color(100,100,255)); // Paint 설정

    g2buf.fill(new Ellipse2D.Float(0,0,10,10)); // 채워진 원

 

    // 네 개의 TexturePaint 객체를 만든다.

    tPaint1=new TexturePaint(buf, new Rectangle.Float(10,50,10,10));

    tPaint2=new TexturePaint(buf, new Rectangle.Float(100,50,15,15));

    tPaint3=new TexturePaint(buf, new Rectangle.Float(190,50,20,20));

    tPaint4=new TexturePaint(buf, new Rectangle.Float(280,50,25,25));

  }

  public void paint(Graphics g){

 

    Graphics2D g2=(Graphics2D)g;

 

    // TextPaint 객체를 적용하여 네 개의 사각형을 그린다.

    g2.setPaint(tPaint1);

    g2.fill(new Rectangle2D.Double(10,50,80,200));

    g2.setPaint(tPaint2);

    g2.fill(new Rectangle2D.Double(100,50,80,200));

    g2.setPaint(tPaint3);

    g2.fill(new Rectangle2D.Double(190,50,80,200));

    g2.setPaint(tPaint4);

    g2.fill(new Rectangle2D.Double(280,50,80,200));

  }  

  public static void main(String []args){

    Frame f=new Texture1("타일 효과");

    f.setSize(400,300);

    f.setVisible(true);

  }

}



[그림 18-11] 타일 효과

 

 

Composite 속성

합성(Composite)은 이미지와 이미지가 겹칠 때 색상을 조합하는 속성이다. Composite를 구현하는 클래스로 AlphaComposite가 있다.

 

AlphaComposite 객체를 생성하려면 static 멤버인 getInstance 메소드를 이용한다.

 

public static AlphaComposite getInstance(int rule, float alpha)

AlphaComposite 객체를 얻는다. rule은 색의 합성 규칙. alpha는 투명도( 0.0f에서 1.0f사이의 값을 가진다.)

 

합성 규칙으로는 CLEAR, SRC_IN, SRC_OVER 등이 있는데 대표적으로 SRC_OVER 규칙으로 합성하면 그리는 이미지가 반투명하게 그려진다. 투명도가 0.0f이면 이미지가 완전 투명하게 그려지며 1.0f이면 완전 불투명하게 그려진다.

 

Graphics2D 객체의 setComposite 메소드를 사용하면 합성 속성이 적용된다.

 

AlphaComposite alpha;

alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);

g2.setComposite(alpha);

 

다음 예제는 투명 효과를 테스트해 보는 것이다.

 

Composite1.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.image.*;

public class Composite1 extends Frame{

  public Composite1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

 

    // 빨간 원을 그린다.

    g2.setPaint(Color.red);

    g2.fill(new Ellipse2D.Float(50,50, 100,100));

 

    // 파란색 Paint 설정

    g2.setPaint(Color.blue);

 

   // 투명도를 0.0에서 0.01씩 증가시키며 원을 그린다.

    for(float i=0.0f; i<=1.0f;i+=0.01f){

      g2.setComposite(

             AlphaComposite.getInstance(AlphaComposite.SRC_OVER, i)

      );

      g2.fill(new Ellipse2D.Float(100,50,100,100));

      try{

        Thread.sleep(100);        // 변화를 관찰하기 위해 간격을 둔다.

      }catch(Exception e){}

    }

  }

  public static void main(String []args){

    Frame f=new Composite1("투명 효과");

    f.setSize(250,200);

    f.setVisible(true);

  }

}



[그림 18-12] 투명 효과

 

 

Transform 속성

java2D 환경은 좌표를 자유롭게 변환할 수 있는 강력한 인터페이스를 기본으로 제공한다.

 

java.awt.geom 패키지의 AffineTransform 클래스는 좌표의 평행 이동, 회전, 스케일링(확대/축소), 반전(flip), 변형(shearing)의 좌표 변환을 구현한다. 이러한 좌표 변환은 3 × 3 행렬로 표현되고 이 행렬과 좌표 (x, y)를 곱하여 변형된 좌표 (x', y')를 구한다.

 

[ x']   [  m00  m01  m02  ] [ x ]   [ m00x + m01y + m02 ]

[ y'] = [  m10  m11  m12  ] [ y ] = [ m10x + m11y + m12 ]

[ 1 ]   [   0    0    1   ] [ 1 ]   [         1         ]

 

좌표 변환에 사용되는 3 × 3 행렬을 변환 행렬이라고 하며 변환 행렬 각 원소의 기능은 다음과 같다.

 

변환 행렬의 원소

기능

m00

x 좌표의 확대/축소

m10

y 좌표의 변형

m01

x 좌표의 변형(shearing)

m11

y 좌표의 확대/축소

m02

x 좌표의 평행 이동

m12

y 좌표의 평행 이동

 

좌표를 원하는 형태로 자유자재로 변환하려면 선형 대수에 대한 기초 지식이 있어야 할 것이다. 하지만 기본적인 좌표 변환은 AffineTransform 클래스와 Graphics2D가 제공하기 때문에 편리하게 이용할 수 있다.

 

 

AffineTransform 클래스의 유용한 생성자

 

public AffineTransform()

기본 AffineTransform 객체를 만든다.

public AffineTransform(double m00, double m10,

                        double m01, double m11,

                        double m02, double m12)

인수들을 변환 행렬로 하는 AffineTransform 객체를 만든다.

public AffineTransform(float[] flatmatrix)

public AffineTransform(double[] flatmatrix)

flatmatrix의 각 원소를  m00, m10, m01, m11, m02, m12로 하는 AffineTransform 객체를 만든다.

 

 setTransform 메소드를 사용하면 AffinTransform 객체를 Graphics2D에 적용할 수 있다.

 

AffineTransform affine=new AffineTransform(...);

...

g2.setTransform(affine);

 


Graphics2D도 다음과 같은 좌표 변환 메소드를 직접 제공하고 있어서 쉽게 좌표를 변환할 수 있다.

 

Graphics2D 클래스의 유용한 메소드

 

public abstract void translate(double tx, double ty);

좌표를 x축 방향으로 tx, y축 방향으로 ty만큼 평행 이동한다.

public abstract void rotate(double theta);

원점을 기준으로 각 theta만큼 회전한다. 각도의 단위는 radian이다.

public abstract void scale(double sx, double sy);

좌표를 x축 방향으로 sx, y축 방향으로 sy만큼 확대하거나 축소한다.

public abstract void shear(double shx, double shy);

좌표  y축을 shx, x축을 shy만큼 찌그러트린다.

 


다음은 프레임의 원점을 가운데로 평행 이동시키고 x축과 y축을 Line2D로 그려보는 예제이다.

 

Transform1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Transform1 extends Frame{

  public Transform1(String title){

    super(title);

  }

  public void paint(Graphics g){

    Graphics2D g2=(Graphics2D)g;

    g2.translate(100, 100);                // 원점을 (100, 100)로 이동시킨다.

    g2.draw(new Line2D.Float(-100, 0, 100, 0));     // x축을 그린다.

    g2.draw(new Line2D.Float(0, -100, 0, 100));     // y축을 그린다.

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));      // (50, 50)에 원을 그린다.

  }

  public static void main(String []args){

    Frame f=new Transform1("평행 이동");

    f.setSize(200, 200);

    f.setVisible(true);

  }

}



[그림 18-13] 평행 이동

 

다음으로 원점을 중심으로 회전하는 변환에 대한 예제를 해보자. "시계 방향" 버튼을 누르면 시계 방향으로 좌표축이 15도 회전하고, "반 시계 방향" 버튼을 누르면 시계 반대 방향으로 좌표축이 15도 회전할 것이다.

 

Transform2.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.event.*;

public class Transform2 extends Frame implements ActionListener{

  int theta;

  Button clockwise=new Button("시계 방향");

  Button counterClockwise=new Button("반 시계 방향");

  Graphics2D g2;

  public Transform2(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());              // 레이아웃 변경

    add(clockwise);                           // 버튼 추가

    add(counterClockwise);

    clockwise.addActionListener(this);          // 이벤트 처리

    counterClockwise.addActionListener(this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==clockwise)                  // "시계 방향" 버튼을 누르면

      theta=15;                                   // 회전각을 15도로 한다.

    else if(ae.getSource()==counterClockwise)     // "반 시계 방향" 버튼을 누르면

      theta=-15;                                  // 회전각을 -15도로 한다.

    repaint();                                    // paint를 호출한다.

  }

  public void paint(Graphics g){

    if(g2==null){

      g2=(Graphics2D)getGraphics();

      g2.translate(150,150);                         // 좌표 축 이동

    }

    g2.rotate(Math.toRadians(theta));              // theta만큼 회전한다.

    g2.draw(new Line2D.Float(-100, 0, 100, 0));     // 축과 원을 그린다.

    g2.draw(new Line2D.Float(0, -100, 0, 100));

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));

  }  

  public static void main(String []args){

    Frame f=new Transform2("좌표축 회전");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}



[그림 18-14] 좌표축 회전

 

 

Graphics2D와 AffineTransform의 scale 메소드를 사용하면 좌표계를 확대, 축소, 또는 반전시킬 수 있다.

 

scale( 2 , 2 )

좌표계를 2배 확대한다.

scale( 0.5 ,0.5 )

좌표계를 1/2로 축소한다.

scale( 1, -1 )

좌표계를 x축을 중심으로 반전한다.

scale( -1, 1 )

좌표계를 y축을 중심으로 반전한다.

 

다음은 좌표축의 확대, 축소, 반전을 테스트해보는 것이다. 이전의 예제와 그 구성이 비슷하므로 이해하기 어렵지 않을 것이다.

 

Transform3.java

 

import java.awt.*;

import java.awt.geom.*;

import java.awt.event.*;

public class Transform3 extends Frame implements ActionListener{

  double scale=1;

  Button flipX=new Button("x축 반전");

  Button flipY=new Button("y축 반전");

  Button scaleUp=new Button("2배 확대");

  Button scaleDown=new Button("1/2로 축소");

  Graphics2D g2;

  public Transform3(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());

 

    add(flipX); add(flipY); add(scaleUp); add(scaleDown);

 

    flipX.addActionListener(this); flipY.addActionListener(this);

    scaleUp.addActionListener(this); scaleDown.addActionListener(this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==flipX)

      g2.scale(1, -1);      // 좌표계를 x축을 중심으로 반전한다

    else if(ae.getSource()==flipY)

      g2.scale(-1, 1);      // 좌표계를 y축을 중심으로 반전한다.

    else if(ae.getSource()==scaleUp)

      g2.scale(2, 2);       // 좌표계를 2배 확대한다.

    else if(ae.getSource()==scaleDown)

      g2.scale(0.5, 0.5);   // 좌표계를 1/2로 축소한다.

    repaint();

  }

  public void paint(Graphics g){

    if(g2==null){

      g2=(Graphics2D)getGraphics();

      g2.translate(150,150);

    }

   

    g2.draw(new Line2D.Float(-100, 0, 100, 0));

    g2.draw(new Line2D.Float(0, -100, 0, 100));

    g2.fill(new Ellipse2D.Float(50, 50, 20, 20));

  }  

  public static void main(String []args){

    Frame f=new Transform3("스케일링");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}



[그림 18-15] 스케일링

 

 

 

Clip 속성

클립(Clip)이란 그리기 작업이 수행될 영역을 말한다. 일단 클립 영역이 지정되면 클립 영역 이외의 영역에는 그려지지 않는다. 따라서 관심 있는 영역에만 그리기 작업을 하려면 클립 영역으로 정하면 된다. 사각형 모양의 영역뿐만 아니라 Shape를 구현하는 도형도 클립 영역이 될 수 있다.

 

 

Graphics 클래스의 유용한 메소드

 

public abstract Rectangle getClipBounds();

paint 메소드가 호출되었을 때 새로 그려질 영역을 반환한다.

public abstract void setClip(int x, int y, int width, int height);

사각형 [x, y, width, height]를 클립 영역으로 정한다.

public abstract void setClip(Shape clip);

도형 clip을 클립 영역으로 정한다.

public abstract Shape getClip();

클립 영역을 Shape형으로 반환한다.

public boolean hitClip(int x, int y, int width, int height)

[x, y, width, height]와 현재의 클립 영역이 겹치면 true를, 아니면 false를 반환한다.

 

다음은 사진의 이미지를 타원 모양의 클립 영역에 그리는 예제이다.

 

Clip1.java

 

import java.awt.*;

import java.awt.geom.*;

public class Clip1 extends Frame{

  Image img;

  public Clip1(String title){                  // 생성자

    super(title);

 

    // 이미지 객체를 생성한다.

    img=getToolkit().getImage(getClass().getResource("/images/baby.gif"));

  }

  public void paint(Graphics g){

    // 클립 영역을 지정하고 이미지를 그린다.

    g.setClip(new Ellipse2D.Float(50,30,180,260));

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

  }

  public static void main(String []args){

    Frame f=new Clip1("클리핑");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}



[그림 18-16] 클리핑

 

 

 

더블 버퍼링

더블 버퍼링은 도형이나 그림을 컴포넌트에 바로 그리지 않고 다른 곳의 버퍼에 도형이나 그림을 그린 다음에 버퍼에 그려진 이미지를 컴포넌트에 그리는 기법을 말한다. 애니메이션이나 게임과 같이 짧은 시간에 많은 이미지를 그리면 화면에 깜빡임이 생기거나 그리는 작업 과정이 보인다. 이런 경우에 더블 버퍼링을 이용하여 한번에 그리면 화면에 그리는 작업 과정이 보이지 않으며 화면의 깜빡임이 줄어든다.

 

[그림 18-17] 더블 버퍼링

 

여기서 버퍼란 Image 클래스의 객체로 구현된다. Image 객체를 생성한 후, 그 Image 객체의 그래픽 객체를 얻어와서 그리기 작업을 하면 된다. 이때 버퍼의 크기는 컴포넌트의 크기와 같아야 할 것이다.

 

Image buffer=컴포넌트.createImage(width,height);  // 버퍼를 만든다.

Graphics g=buffer.getGraphics();             // 버퍼의 그래픽 객체를 얻는다.

g.drawImage(...); ...                          // 버퍼에 이미지를 그린다.

...drawImage(buff, 0, 0, ...);                 // 버퍼를 컴포넌트에 그린다.

 

더블 버퍼링을 사용하면 이미지를 버퍼에 그리는 동안 컴포넌트에는 그리지 않기 때문에 컴포넌트에 바로 그리는 경우보다 속도 면에서 조금은 늦을 것이다. 따라서 그리는 작업을 최소화하여 반응속도를 개선하는 것이 좋다.

 

다음은 더블 버퍼링을 사용한 경우와 그렇지 않은 경우를 비교하기 위한 예제이다. 시스템에 따라 정도의 차이가 있으나 더블 버퍼링을 하지 않은 경우에 화면이 깜빡이는 것을 확인할 수 있다.

 

DBuffering1.java

 

import java.awt.*;

import java.awt.event.*;

public class DBuffering1 extends Frame implements ActionListener{

  Button b1= new Button("바로 그리기");

  Button b2= new Button("버퍼 사용하기");

  public DBuffering1(String title){               // 생성자

    super(title);

    setLayout(new FlowLayout());

    add(b1);                                    // 버튼을 추가한다.

    add(b2);

    b1.addActionListener(this);                   // 이벤트 처리를 한다.

    b2.addActionListener(this);

  }

 

  // 그래픽 객체 g에 color색으로 직선을 긋는 메소드

  private void drawLines(Graphics g, Color color){

    g.setColor(color);

    for(int i=0;i<getHeight();i++){                   // 선으로 채운다.

      g.drawLine(0,i,getWidth(),i);

    }

  }

  private void drawOnFrame(){              // 프레임에 바로 그리는 메소드

    Graphics g=getGraphics();

    g.clearRect(0,0,getWidth(),getHeight());    // 프레임에 그려진 이미지를 지운다.

    drawLines(g,Color.red);                   // 프레임에 빨간 색으로 선을 그린다.

  }

  private void drawOnBuffer(){        // 버퍼에 그려서 프레임에 복사하는 메소드

 

    // 프레임과 같은 크기의 버퍼를 생성한다.

    Image buffer=createImage(getWidth(), getHeight());

 

    Graphics g=buffer.getGraphics();         // 버퍼의 그래픽 객체를 얻는다.

    drawLines(g, Color.blue);                // 버퍼에 파란색으로 선을 그린다.

 

   // 버퍼에 그려진 이미지를 프레임에 그린다.

    getGraphics().drawImage(buffer,0,0,this);

  }

  public void actionPerformed(ActionEvent ae){

    if(ae.getSource()==b1)               // b1을 누르면

      drawOnFrame();                  // 프레임에 바로 그린다.

    else if(ae.getSource()==b2)          // b2를 누르면

      drawOnBuffer();                 // 버퍼에 그려서 프레임에 복사한다.

  }

  public static void main(String []args){

    Frame f=new DBuffering1("더블 버퍼링");

    f.setSize(300, 300);

    f.setVisible(true);

  }

}



[그림 18-18] 더블 버퍼링 테스트

 


java2D 그래픽은 나중에 Applet 게임과 네트워크 게임을 만들 때 응용되어진다.

 

 

 

 

연습 문제

 

 

1. 키보드의 방향키를 누르면 프레임에 그려진 작은 이미지가 움직이도록 해보자. 움직이는 방향에 따라 자동차의 방향도 바뀌어야 한다. 더블 버퍼링을 이용하면 화면의 떨림을 제거할 수 있다.

 

[그림 18-19] 이미지 움직이기

 

 

2. 몇 개의 장애물(도형)을 설치하고, 장애물이 있는 곳으로는 자동차가 이동하지 못하도록 해보자.  intersects(Rectangle2D r)를 이용할 수 있다.

 

[그림 18-20] 충돌 검사

 

 

3. 아래 그림과 같이 실행되도록 해보자.

 

[그림 18-21] 임의의 색깔로 원 그리기

 

 

4. 아래 그림과 같이 실행되도록 해보자.

 

[그림 18-22] 좌표축 이동과 회전

 




Posted by BAGE