서버에 JNDI등록 후 Context 인스턴스로 lookup시에 발생

크 게 두 가지 원인 존재함

-서버에 해당이름의 리소스가 없는 경우
주로 설정한 이름과 소스상에서 바인딩하는 이름이 상이해서 발생
설정값과 소스를 확인해서 일치시켜서 해결

-서버에 해당이름의 리소스를 생성하기 위한 라이브러리들이 클래스패스에 없는 경우
Tomcat서버에 아파치 DBCP를 JNDI 리소스로 등록해서 DataSource를 생성하는 경우 설정에 아무 문제가 없더라도 위와 같은 에러가 발생

Tomcat에서 dbcp DataSource를 생성하기 위해서는 Context 인스턴스로부터  NamingContext인스턴스를 생성하고 해당 JNDI이름을 통해  DataSource를 생성하는 과정에서
commons dbcp
commons collections
commons pool

위의 3개 라이브러리가 필요함

서버 설정상이나 서버 시작시에 JNDI로 인한 인스턴스 생성과정을 추적하여 관련 라이브러리의 존재여부를 확인하지 않음. 해당 라이브러리들을 서버의 classpath에 등록시키면 해결

프로세스(Processes)

프로세스는 리눅스와 Windows에서 모두 실행 프로그램을 뜻한다. Windows에서는 .exe 확장자가 있는 파일 이름이 실행파일이다. 프로세스는 소스 파일을 컴파일 하고 실행 파일을 생산함으로서 만들어진다. 컴파일 단계는 Windows와 리눅스 모두 비슷하다:

 


프로그램 컴파일하기
    WINDOWS:   cl -O2 create-pt2.cpp
    LINUX  :   gcc -O2 create-pt2.cpp -lpthread -o create-pt2

위 두개의 컴파일 결과는 create-pt2.exe (Windows)와, create-pt2 (리눅스)라는 실행파일이다.

프로세스는 오픈 파일 핸들을 상속 받을 수 있다. Windows에서, 핸들이 작은 정수가 아니기 때문에 정확한 값은 알 수 없다. Windows에서 기존의 오픈 파일을 위해 핸들을 정확한 값으로 초기화 하는 것은 문서화된 프로시져가 아니다. 리눅스에서 파일 디스크립터(descriptor)를 20으로 설정하고 사용하는 것은 매우 쉽다.

일단 프로세스가 만들어지면 시작할 때 명령행에서 프로그램 이름을 타이핑 하면 된다. 리눅스와 Windows 모두 같다:


프로그램 실행하기
    create-pt2

Windows 프로그램의 경우, .exe 서픽스를 타이핑 할 필요가 없다. 프로그램이 실행권한이 있고 합법적인 Windows 바이너리 형식의 프로그램이라면 Windows는 모든 프로그램 이름을 실행 파일로서 인식한다. 따라서 나는 마이크로소프트 컴파일러의 아웃풋을 리눅스와 마찬가지로 create-pt2 라고 이름을 붙였다.


리눅스 프로세스 만들기

리눅스는 매개변수가 없는 단일 시스템 호출을 사용하여 새로운 프로세스를 만든다. fork 시스템 호출로 정확한 부모 프로세스 카피(copy)를 만들고 부모에게는 자식 프로세스 ID를, 자식에게는 0(zero)을 리턴한다:


리눅스 프로세스 생성
        #include <sys/types.h>
        #include <unistd.h>

        pid_t child_pid;

        child_pid = fork();

        if(child_pid == -1) {
            ERROR;
        }
        else if(child_pid == 0) {
            do_child();
        }
        else {
            do_parent();
        }

Fork는 새로운 프로세스를 만들지만 어떻게 이것이 새로운 프로그램을 실행하는가? 표준 수행 방법은 다음과 같다:


리눅스 상에서 새로운 프로그램 실행하기
        child_pid = fork();
        if(child_pid == -1) ERROR

        if(child_pid == 0) { // Child executes a new program image
            execl("/bin/view", "view", "/etc/hosts");
            ERROR;
        }
        else {               // Parent waits for child to finish (exit).
            int status;

            while(wait(&status) != child_pid);
        }

프로그램을 만들고 실행하는 코드는 깊은 의미를 가지고 있다. 프로그램은 main 으로 부터 리턴하거나 시스템 호출을 사용하여 종료한다.


Windows 프로세스 만들기

Windows 프로세스는 CreateProcess() API를 사용하여 만들어진다. 리눅스의 단순한 fork 호출과는 달리, CreateProcess API는 시도하기에 앞서 공부를 해두는 것이 필요하다.


Windows에서 프로세스를 만드는 API
BOOL CreateProcess(
    LPCTSTR lpApplicationName,                 // name of executable module
    LPTSTR lpCommandLine,                      // command line string
    LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
    LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
    BOOL bInheritHandles,                      // handle inheritance option
    DWORD dwCreationFlags,                     // creation flags
    LPVOID lpEnvironment,                      // new environment block
    LPCTSTR lpCurrentDirectory,                // current directory name
    LPSTARTUPINFO lpStartupInfo,               // startup information
    LPPROCESS_INFORMATION lpProcessInformation // process information
);

CreateProcess API는 리눅스의 execl() 시스템 호출과 비교되어야 공정하다. execl() 는 프로그램 이름과 인자는 API에 있는 스트링으로 지정되어야 하는 반면, CreateProcess()는 많은 옵션들을 제공한다.

create-pt2.cpp 프로그램은 프로세스 또는 쓰레드를 만들 수 있다. 프로세스를 만드는 코드는 다음과 같다:


Windows에서 프로세스 만들기
    extern char *applname;
    extern char *progname;
    HANDLE t1;
    char buf[1024];
    BOOL b;
    #define errno   GetLastError()

    sprintf(buf, "%s-child", applname);
    buf[sizeof(buf)-1] = 0;

    STARTUPINFO         startInfo;
    PROCESS_INFORMATION pidInfo;

    //
    //    child process
    //
    startInfo.cb          = sizeof(STARTUPINFO);
    startInfo.lpReserved  = NULL;
    startInfo.lpTitle     = buf;
    startInfo.lpDesktop   = NULL;
    startInfo.dwX         = 0;
    startInfo.dwY         = 0;
    startInfo.dwXSize     = 0;
    startInfo.dwYSize     = 0;
    startInfo.dwXCountChars    = 0;
    startInfo.dwYCountChars    = 0;
    startInfo.dwFlags     = STARTF_USESTDHANDLES;
    startInfo.wShowWindow = 0; //SW_SHOWDEFAULT;
    startInfo.lpReserved2 = NULL;
    startInfo.cbReserved2 = 0;
    startInfo.hStdInput   = GetStdHandle(STD_INPUT_HANDLE);
    startInfo.hStdOutput  = GetStdHandle(STD_OUTPUT_HANDLE);
    startInfo.hStdError   = GetStdHandle(STD_ERROR_HANDLE);

    b = CreateProcess(
            progname,
            buf,
            NULL,
            NULL,
            TRUE,
            0,//CREATE_NEW_CONSOLE,
            NULL,
            NULL,
            &startInfo,
            &pidInfo);

    if(!b) {
        printf("Creation of child process failed: err=%d\n", errno);
        printf("applname=<%s>, buf=<%s>\n",applname,buf);
        return;
    }
    t1 = pidInfo.hProcess;

보다시피 필요한 코드의 양이 많다. 이와 유사한 리눅스의 wait() 호출을 보자:


Windows에서 프로세스 기다리기
    if(WaitForSingleObject(t1,INFINITE) == WAIT_FAILED) {
        printf("WaitForSingleObject FAILED: err=%d\n", GetLastError());
        return;
    }

Windows 프로그램은 종료 방식이 여러가지 이다. 두 가지는 리눅스와 유사하다. main 에서 리턴하거나 표준 C 라이브러리에서 exit() 루틴을 호출할 수 있다. 세 번째 옵션은 ExitProcess() API를 호출하는 것이다. 여기에서는 exit 루틴을 사용한다.


쓰레드(Threads)

쓰레드는 실행 콘텍스트(context)이다. 처음에 각 프로세스는 하나의 실행 콘텍스트를 갖는다. 이 실행 콘텍스트를 쓰레드라고 한다. 프로세스가 다른 실행 콘텍스트를 필요로 한다면 간단히 다른 프로세스를 만들 수 있다. 하지만 프로세스 생성은 프로세서 사이클과 메모리 사용의 관점에서 볼 때 사치스러운 일이다. 쓰레드는 다중 실행 콘텍스트를 만드는데 경량의(lightweight) 메커니즘을 제공하도록 되어있다. Windows와 리눅스는 한 쌍의 실행 환경을 제공할 목적으로 오퍼레이팅 시스템에서 쓰레드를 스케쥴링한다.

프로세스와 쓰레드의 가장 뚜렷한 차이는 프로세스의 모든 쓰레드가 같은 메모리 공간과 시스템 정의의 "도구(facility)"를 공유한다는 점이다. 오픈 파일 핸들(파일 디스크립터), 공유 메모리, 프로세스 동기화 프리머티브(process synchronization primitives), 현재 디렉토리 등이 "도구"이다. 글로벌 메모리가 공유되고 새로운 메모리가 할당되지 않기 때문에 쓰레드를 만드는 것은 프로세스를 만드는 것보다 간단하고 빠르다.


리눅스 쓰레드 만들기

리눅스는 POSIX 쓰레드를 지원한다. 리눅스 쓰레드는 다음의 코드로 만들어진다:


리눅스에서 쓰레드 만들기
        #    define DEC    ( void *(*)(void*) )

        if(pthread_create(&t1, NULL, DEC  threadwork, NULL)) {
            printf("pthread_create A failed: err=%d\n", errno);
            return;
        }

여기에서, t1은 쓰레드 ID를 받기위해 사용되는 매개변수이다. 두 번째 인자는 많은 스케쥴링 옵션을 지원하는 쓰레드 속성 인자이다. 우리는 여기서 디폴트 세팅을 사용할 것이다. 세 번째 인자는 쓰레드를 생성할 때 실행 될 서브루틴(subroutine) 이다. 네 번째 인자는 서브루틴으로 전달된 포인터이다. 이것은 쓰레드나 새로 만들어진 쓰레드에 필요한 것을 위해 저장한 메모리를 지시할 수 있다.

리눅스 쓰레드는 서브루틴에서 리턴하거나 pthread_exit() 루틴을 호출하여 종료한다. 쓰레드가 가지고 있는 리소스는 부모 쓰레드가 pthread_join() 루틴을 호출할 때 회복된다. pthread_join()는 프로세스용 wait() 함수와 비슷하다. pthread_join()는 쓰레드에 의해 할당된 힙 메모리(heap memory)를 회복하지 않는다. 전역으로(globally) 할당된 메모리가 프로그램 또는 쓰레드에 의해 자유로워 져야 한다.


Windows 쓰레드 만들기

Windows 쓰레드는 Windows 프로세스를 만드는 것보다 훨씬 간단하다. 쓰레드를 만드는 데에는 리눅스에서 사용한 매개변수를 비롯하여 보안 디스크립터와 초기 스택 크기도 포함된다.


Windows에서 쓰레드 만들기
        t1 = CreateThread(NULL,4096,(THRDFN)threadwork,"L",NULL,&threadid);
        if(t1 == NULL) {
            printf("CreateThread FAILED: err=%d\n", errno);
            return;
        }

첫 번째 인자는 보안 디스크립터 로서 쓰레드 핸들이 상속 될 수 있을 지의 여부를 결정한다. NULL 은 이것이 상속될 수 없다는 것을 의미한다. 두 번째 인자는 스택 사이즈이다. 세 번째와 네 번째 인자는 서브루틴과 패스된 매개변수이다. 다섯 번째 인자는 플래그 인자이다. 쓰레드는 플래그가 CREATE_SUSPENDED로 설정될 때 중지 상태에서 만들어진다. 여섯 번째 인자는 결과 쓰레드 ID를 시스템에 저장한 위치를 가리킨다.

Windows 쓰레드는 호출된 서브루틴에서 리턴하거나 ExitThread() API를 호출하여 종료한다. 부모는 WaitForSingleObject(threadid)를 호출하여 쓰레드를 정리하거나 WaitForMultipleObjects() API를 사용하여 다중 이벤트를 기다릴 수 있다.


퍼포먼스 결과

프로세스와 쓰레드의 생성과 리눅스와 Windows에서 그들의 유사성을 설명하기 위해 create-pt2.cpp를 작성했다. "시간 측정 (timed-test)" 방식을 사용한다.

Linux 2.4.2 커널( Red Hat 7.2), Windows 2000 Advanced Server, Windows XP Professional에서 프로그램을 실행했다. 세 개의 오퍼레이팅 시스템은 같은 Thinkpad 600X (320 MB메모리)에서 실행되었다. 그림1과 2는 오퍼레이팅 시스템의 퍼포먼스를 나타낸다.


그림 1. 쓰레드 생성 속도
Figure 1. Thread creation speed

그림 2. 프로세스 생성 속도
Figure 2. Process creation speed

쓰레드와 프로세스를 만들 때 리눅스가 Windows(2000, XP)보다 상당히 빠르다는 것을 알 수 있다.

create-pt2.cpp 는 주어진 시간 간격에서 가능한 오랜 시간동안 쓰레드(프로세스)를 만들고 소멸시키는 단순한 프로그램이다. 다음과 같이 테스트를 실행했다:


테스트 스크립트
    create-pt 2         # create threads for 2 seconds
    create-pt 4
    create-pt 8
    create-pt 16

    create-pt -p 2      # create processes for 2 seconds
    create-pt -p 4
    create-pt -p 8
    create-pt -p 16

두 개의 Windows 결과는 프로그램 실행이 길어질수록 퍼포먼스가 악화되었음을 나타냈다. 그림 3은 이 문제를 보여주고 있다.


그림 3. 쓰레드 생성 속도
Figure 3. Thread creation speed decay

Windows가 쓰레드를 생성할 때 동시에 핸들도 생성한다. 이 핸들은 쓰레드를 만들었던 프로세스 상에서 열려있다. 프로세스의 경우, Windows는 두 개의 핸들을 만든다. 하나는 프로세스 용이고 다른 하나는 그 프로세스의 실행 쓰레드용이다. 이 핸들들은 나중에 OpenProcess()이나 OpenThread() API를 이용하여 얻을 수 있기 때문에 불필요한 것들이다. 그럼에도 불구하고 그들이 닫히지 않는다면 프로그램은 계속해서 핸들을 얻을 것이고 시스템 전역의 리소스를 소비하는 결과가 된다.

create-pt2.cpp 의 첫 번째 버전은 생성된 쓰레드와 프로세스를 위해 핸들을 닫지 않았다. 쇠퇴하는 퍼포먼스는 메모리 유출을 증명하고 있다. Task Manager 를 사용하거나 프로세스 탭에 나타내기 위해 칼럼으로서 "핸들" 을 선택함으로서 create-pt2.exe 프로그램이 가진 핸들의 수는 증가한다. 핸들의 유출은 Windows 프로그램의 퍼포먼스가 실행 시간이 길어질수록 쇠퇴하는 이유가 된다.

문제를 고쳤을 때 Task Manager는 create-pt2a.cpp가 고정된 수의 핸들을 가지고 있다는 것을 나타냈다. 퍼포먼스 결과는 테스트 실행이 길어져도 더 이상 쇠퇴하지 않았다.

Windows XP는 쓰레드 생성 속도에 있어서 Windows 2000를 능가했다. 하지만 리눅스 속도의 60% 에 그치는 수준이다. 프로그램이나 측정 기술에 이상이 없는 한, Windows XP의 프로세스 생성은 Windows 2000보다 현저하게 느리고, Windows 2000의 프로세스 생성은 리눅스 보다 느리다.


요약

프로세스와 쓰레드에 대해 간단히 설명했고 내가 작성한 프로그램에서 두 가지의 프로그래밍을 설명했다. 프로세스와 쓰레드의 퍼포먼스를 측정한 결과 리눅스가 Windows 2000 이나 Windows XP 보다 빨랐다. 여러분도 테스트 스크립트 (create-pt2a-sh.sh) 와 create-pt2a.cpp용 소스 코드를 다운로드 하여 직접 실행해보기 바란다.

Windows 프로세스와 쓰레드 생성속도를 높이는 코드를 만드는 방법을 알고있다면 discussion forum에서 의견을 나눠주기 바란다.


객체 직렬화란?
- 간단히 말하면 객체를 스트림으로 보낼 수 있는 형태의 데이터로 바꿔즈는 것을 말한다.
- 직렬화가 가능한 클래스라는 것을 JVM에게 알려주기 위해서는 Serializble이라는 인터페이스를 구현한다.


코드예제)

// 클래스 -> 파일로 직렬화

Test test = new Test();

ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream("Test.ser"));

outStream.writeObject(test);

outStream.close();

//파일 -> 클래스

ObjectInputStream in = new ObjectInputStream(new FileInputStream("Test.ser"));

Test test = (Test)in.readObject();


split 함수는 정규식 사용

public String [] split (String regex)

regex가 정규식을 뜻함

그리고 정규식에서 "." 문자열은 임의의 문자를 뜻한다

정규식에서 "."을 그대로 사용하기 위해서는 -> "[ . ]" 이렇게 사용

ex) String[] array = str.split ( "[ . ]" );

그래픽을 구현하는 클래스로 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 게임과 네트워크 게임을 만들 때 응용되어진다.

 

 

 

[출처] 2D Graphics|작성자 원준아빠



 

Immutable이란..

Immutable이란 생성후 변경 불가한 객체입니다. 그래서 immutable에는 set 메소드가 없습니다. 멤버 변수를 변경할 수 없습니다. return type이 void인 메소드도 없습니다. 주로 void 메소드는 뭔가를 하고(하지 않을 수도 있고.. ) 멤버변수를 변경하는 역할을 하는 것이기 때문에 쓸 일이 거의 없습니다. (물론, 콘솔에 뭔가를 찍는 것과 같은 예외적인 void는 있을 수 있습니다.)
Immutable을 쓰면, 멀티 쓰레드 환경에서 좀 더 신뢰할 수 있는 코드를 만들어 내기가 쉽습니다. 멀티 쓰레드 프로그램을 짜보셨다면 아시겠지만, 멀테 쓰레드 환경에서는 에러보다 비정상적 작동의 경우가 많습니다. 에러도 아니기 때문에 찾아내기도 어렵습니다. 게다가 항상 생기는 것도 아니고 백번에 한번 천번에 한번 식으로 문제가 생겨 정말 머리 아픈 경우가 한 두번이 아닙니다.
Immutable을 쓰게 되면, 이런 요소들을 많이 줄일 수 있습니다.

대표적인 Immutable 클래스

String, Boolean, Integer, Float, Long 등등이 있습니다. 여기서 주의할 점은 변경불가라는 것은 heap 영역에서의 변경불가라는 뜻입니다. String a="a"; a="b"; 와 같이 재할당은 가능합니다. 이는 a가 reference하고 있는 heap 영역의 객체가 바뀌는 것이지 heap영역에 있는 값이 바뀌는 것이 아닙니다.

String vs StringBuffer

String과 StringBuffer에는 비슷한 메쏘드들이 많이 있어서 비슷해 보이지만, 결정적인 차이가 있습니다. String은 Immutable 입니다. StringBuffer는 아닙니다. StringBuffer가 String에 비해서 훨씬 빠르다는 얘기를 들어보셨나요? 그건 객체를 새로 생성할 필요가 없기 때문입니다.

String에는 없고 StringBuffer에만 있는 대표적인 메소드는 append, delete 등일 겁니다. 멤버 변수를 변화시켜 값을 바꿀 수 있는 거죠. 그런데 잘 보며 이들의 리턴은 StringBuffer 타입입니다. 어차피 얘네도 객체를 새로 만들어낸다면, String과 별 차이가 없어보입니다. 그러나 객체를 새로 만들어 내는 것이 아닙니다. 
아래 코드를 실행시켜보세요.
StringBuffer b = new StringBuffer();
StringBuffer a = b.append("test");
System.out.println(a == b);
true가 나옵니다. 객체를 새로 만드는 것이 아니라 return this 로 되어있습니다. 굳이 리턴을 하는 이유는 아래와 같은 코딩을 가능하게 해주려는 것 같습니다.

StringBuffer test = new StringBuffer();
test.append("a").append("b").append("c").append("d");

와 같은 식으로 여러줄 코딩을 한 줄로 할 수 있게 해주려는 것 같습니다.. 아님 말구요.-_-;

Immutable의 유용성과 위험성

멀티쓰레드 환경에서 하나의 객체에 접근을 하는데 각각의 쓰레드끼리는 영향을 받으면 안 되는 경우가 있습니다. 그럴 때 한 번 만들어진 객체의 값이 변하지 않는다는 게 보장이 되면 편하겠죠.

String a  = "";
while(어떤 조건문){
    a += "머시기";
    if(딴 조건문){
        break;
    }
}

위와 같은 코드는 쓰면 안 됩니다. a+= "머시기" 구문이 문젭니다. 객체를 계속 생성해 냅니다. Immutable은 값이 계속 변경될 수 있는 곳에 쓰면 메모리 왕창 잡아먹습니다. 값이 완전히 정리된 후에 한 큐에 immutable로 만들어 내셔야 합니다. (물론, 가비지 컬레션이 돌면서 정리를 하기 때문에 치명적으로 위험한 수준의 행동은 아닙니다.)

java.util.Collections 에 unmodifiable머시기 하는 메쏘드들이 있습니다. 얘네들을 이용하면, Set, List, Map 등을 immutable로 사용할 수 있습니다. 다만, add, put과 같은 메쏘드를 호출할 경우에는 UnsupportedOperationException 가 발생합니다. 예외 상황을 고려해야 합니다.

Immutable은 보통 final 클래스로 정의합니다.

처음에 자바를 할 때 String이란 객체에 ltrim이란 메쏘드를 추가하고 싶었습니다. (왼쪽만 trim하는 메쏘듭니다.) 상속을 받아 새로운 클래스를 만들어 해결하려고 했습니다. 헛, 그런데 final로 정의가 되어있어서 상속을 받을 수가 없더군요.
final로 정의가 되지 않으면, 상속을 받은 클래스가 Immutable을 깨버릴 수가 있기 때문입니다.

잘못된 Immutable의 구현

package immutable;

import java.text.SimpleDateFormat;
import java.util.Date;

public final class WrongImmutable {
    private final Date date;
    private final SimpleDateFormat dateFormat;
    public WrongImmutable(Date date){
        this.date = date;
        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
    public String getMessage(){
        return dateFormat.format(date);
    }
}

위의 소스를 보세요. date라는 변수가 final로 정의 되어있으며, setter가 없어 오직 생성자에서만 값을 지정할 수 있는 것 같아보입니다. 그러나 아래와 같은 테스트 클래스를 실행시켜보시면, 위의 코드가 잘못되었음을 알 수 있습니다.

package immutable;

import java.util.Date;

public class WrongImmutableTest {
    public static void main(String[] args) {
        Date testDate = new Date();
        WrongImmutable wrongImmutable = new WrongImmutable(testDate);
        testDate.setTime(testDate.getTime() + 10000000);
        System.out.println(wrongImmutable.getMessage());
    }
}

WrongImmutable의 생성자에 인자로 넣은 Date를 외부에서 값을 변경시키면 WrongImmutable의 멤버 변수의 값이 변경이 되고 맙니다. Immutable에서는 멤버 변수가 외부로 공개되어 변경이 가능하면 안 됩니다.
그럼 처음에 원했던 대로 정상적인 Immutable이 되도록 하려면 인자로 받은 Date 객체를 그대로 사용하는 것이 아니라, 어떤 식으로든 복사를 해서 써야 합니다. 생성자에서 멤버 변수의 값을 할당하는 부분을

this.date = new Date(date.getTime());
와 같이 바꿔야 합니다.

자바에 진짜 Immutable이란 건 없다!

java의 reflection을 이용하면 변경 가능합니다. 다음 코드를 실행시켜 보세요.

import java.lang.reflect.Field;

public class EditUsingReflection {
    public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
        String s = "string!";
        edit(s);
        System.out.println(s);
    }
    public static void edit(String s) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
        Field stringValue = String.class.getDeclaredField("value");
        stringValue.setAccessible(true);
        stringValue.set(s, s.toUpperCase().toCharArray());
    }
}

 

[출처] Immutable에 대해서..|작성자 와니



이클립스 폴더의 실행파일의 위치에
eclipse.ini 파일이 있음.

그것을 열어보시면

-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256M
-vmargs
-Dosgi.requiredJavaVersion=1.5
-Xms40m
-Xmx512m   
이부분에서
맨 마지막의 -Xms40m와 -Xmx512m을 각각 -Xms128과 -Xmx256m으로 고치기.

-Xms와 -Xmx는 JVM이 차지하는 최소메모리와 최대메모리를 설정하는 옵셥임.
최대메모리가 너무 높아 다른 프로그램의 메모리 영역과 충돌하여 발생하는 것임.



 


Scalable Vector Graphics (SVG) 1.1 [W3C Recommendation]은 2차원 그래픽을 기술하는 언어이다.
일부 래스터 그래픽 기능도 사용할 수 있지만, 기본적으로는 벡터 그래픽 언어이다. SVG는 실질적이고
유연한 그래픽 포맷을 XML에 제공한다는 목표를 갖고 있으며, 이 부분에서 성공을 거두었다.
SVG의 기능에는 중첩된 변형, 클리핑 경로, 알파 마스크, 래스터 필터 효과, 템플릿 객체 등이 포함되어 있고,
확장성도 우수하다. SVG는 애니메이션, 줌과 플래닝 뷰, 다양한 그래픽 명령어, 그룹핑, 스크립팅, 하이퍼링크,
구조화 된 메타데이터, CSS, 특화된 DOM 수퍼세트, 기타 XML 문서로의 임베딩을 지원한다. SVG의 일부
디자인은 벡터 경로가 개별 애트리뷰트 내에서 공간 한정적인 숫자 리스트로 표현된다는 점에서 논란이
되고 있지만, 메인 SVG는 가장 광범위하게 XML 애플리케이션들을 포용하고 있다. 이 스팩은 여러 언어들로
번역되었다.

SVG 1.1은 XHTML 1.1+과 비슷한 방식으로 모듈화 되었다. Mobile SVG Profiles: SVG Tiny and SVG Basic
[W3C Recommendation]에서는 모바일 폰과 PDA에 적합한 감소된 SVG 모듈을 정의하고 있다. SVG 1.2 [개발중]는 SVG를 완벽한 애플리케이션 플랫폼으로 만드는 새로운 툴과 그래픽 포맷을 추가했다.


import java.awt.event.*; // 각 컴포넌트에 대한 이벤트를 작성하기 위한 패키지
import javax.swing.*; // 스윙 컴포넌트를 사용하기위한 패키지
import java.net.*; // 네트워크 클래스를 사용하기 위한 패키지
import java.io.*; // 입출력 스트림을 사용하기 위한 패키지

 

public class MailSender extends JFrame implements ActionListener{

    // 보낸이, 받는이, 제목을 입력 받기 위한 텍스트 상자

    //private JTextField from, to, suject;

    private JTextField from, to, subject;

    // 메일 본문 작성으로 위한 텍스트 상자
    private JTextArea data;

    // 메일보내기 버튼, 모든 텍스트상자 지우기버튼, 종료 버튼
    private JButton send, clear, exit;

    // SMTP서버에 접속하기 위한 서버주소와 도메인을 담는 변수
    private String server, domain;

   

    // MailSender 생성자(객체 생성시 자동으로 호출)

 //public void MailSender(String server, String domain) { 생성자는 반환형이 없음

    public MailSender(String server, String domain) {

         // 부모 클래스의 생성자를 호출해 창의 제목을 설정
        //Super("send Mail");

        super("send Mail");

        // 생성자로 넘어온 SMTP서버의 주소와 도메인을 각 변수에 할당
        this.server = server;
        this.domain = domain;
  

        // 각 컴포넌트를 생성해 이벤트연결
        from = new JTextField();
        to = new JTextField();
        subject = new JTextField();
        data = new JTextArea();
        send = new JButton("SEND");
        send.addActionListener(this);
        clear = new JButton("CLEAR");
        clear.addActionListener(this);
        exit = new JButton("EXIT");
        exit.addActionListener(this);

 

        // 각 컴포넌트를 패널에 위치 시킴

        JPanel c = new JPanel(new PositionLayout());
        c.add("5%, 2%, 20%, 9%", new JLabel("FROM: "));
        c.add("25%, 2%, 95%, 9%", from);
        c.add("5%, 11%, 20%, 18%", new JLabel("To: "));
        c.add("25%, 11%, 95%, 18%", to);
     //c.add("5%, 20%, 20%, 27%", new JLable("SUBJECT: "));

        c.add("5%, 20%, 20%, 27%", new JLabel("SUBJECT: "));
     //C.add("25%, 20%, 95%, 27%", subject);

        c.add("25%, 20%, 95%, 27%", subject);
        c.add("5%, 29%, 20%, 34%", new JLabel("DATA: "));
        c.add("5%, 35%, 95%, 85%", new JScrollPane(data));
        c.add("8%, 88%, 28%, 95%", send);
        c.add("40%, 88%, 60%, 95%", clear);
        c.add("72%, 88%, 92%, 95%", exit);
 

         // 컴포넌트들을 담고 있는 패널을 프레임에 추가 시킴
      //SetContentPane(c);

        setContentPane(c);

        // 기본 닫힘명령 설정
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // 프레임의 사이즈 설정
        setSize(400, 450);

        // 프레임을 띄움.
        setVisible(true);
    }

    // 이벤트 핸들러
    public void actionPerformed(ActionEvent e){

        // 이벤트를 발생한 객체를 가져옴
        Object c = e.getSource();

        // 서버로 부터 수신된 응답 메시지를 담기위한 변수
        String incoming;

 

        // exit버튼을 눌렀을경우 프레임을 닫고 프로그램 종료

        if(c==exit) {
         //setvisible(false);

            setVisible(false);
            System.exit(0);

        // clear버튼을 눌렀을경우 모든 텍스트 상자 초기화
        }else if(c==clear) {
            from.setText("");
            to.setText("");
            subject.setText("");
            data.setText("");

        // send버튼을 눌렀을 경우 SMTP서버로 접속 후 메일을 보냄.
        }else if(c==send) {
            try{

                // SMTP서버에 접속 하기위한 소켓 생성(SMTP서버 Def.Port:25)
                Socket s = new Socket(server, 25);

                // 서버로 송신하기 위한 출력 스트림을 얻어온다.
                PrintStream out = new PrintStream(s.getOutputStream(), true);

                // 서버로 부터 수신된 데이터를 받기위한 입력 스트림을 얻어온다.
             //BufferedReader in = new BufferedReader(new InputStreamReader(s.GetInputStream()));

                BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

 

                // 서버로 연결 요청 메시지를 송신한다.
                out.println("HELO"+domain);

                // 서버로 부터의 응답메시지를 수신 한다.
                incoming = in.readLine();

 

                // 서버로 메일 보낸이에 대한 정보를 송신 한다.
                out.println("MAIL FROM: < "+from.getText()+" >");

                // 서버로 부터의 응답메시지를 수신 한다.
                incoming = in.readLine();

               

                // 서버로 메일 받는이에 대한 정보를 송신 한다.
                out.println("RCPT TO:< "+to.getText()+" >");

                // 서버로 부터의 응답메시지를 수신 한다.
                incoming = in.readLine();

               

                // 서버로 데이터를 보낸다는 메시지를 송신한다.
                out.println("DATA");

                // 서버로 부터의 응답메시지를 수신 한다.
                incoming = in.readLine();

           

                // 서버로 메일 제목을 송신 한다.
                out.println("Subject: "+subject.getText());

                // 서버로 부터의 응답메시지를 수신 한다.
                incoming = in.readLine();

     

                // 본문 내용의 끝에 캐리지리턴값과 라인피드값을 합친 내용을 서버에 전송
                String msg = data.getText();
               out.println(msg+"\r\n.\r\n");

               // 서버로 부터의 응답메시지를 수신 한다.
               incoming = in.readLine();

               // 서버로 종료한다는 메시지를 송신 한다.
               out.println("QUIT");

             

               // 각 텍스트 상자들의 값을 초기화 한다.

               to.setText("");
               subject.setText("");
               data.setText("");

           // try{}구문에서 예외 발생시 예외 처리 부분..
           }catch(Exception ex){
               ex.printStackTrace();

           }
        }

    }

    public static void main(String args[]) {

        // 프로그램을 실행시 아규먼트 2개가 넘어오지 않았을경우
     //if(args.length(2)){  length는 메소드가 아님

        if(args.length != 2){

            // SMTP서버의 주소와 도메인을 객체에 생성자를 통해 객체 생성
            new MailSender("localhost", "mokpo.ac.kr");
        }else{ // 프로그램을 실행시 아규먼트가 넘어 왔다면

            // SMTP서버의 주소와 도메인을 객체에 생성자를 통해 객체 생성
         //new MailSender(args[0], arg[1]);

            new MailSender(args[0], args[1]);
        }
    }
}


/*
* @(#)PositionLayout.java       98/02/18
*      - written by 최종명(CHOI JONG MYUNG)
*
* Copyright by Choi Jong Myung, CS Dept. of Soongsil Univ., Seoul, Korea.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Choi Jong Myung.  You shall not disclose such Confidential
* Information and shall use it only non-commercial purpose.
*/

import java.awt.*;
import java.util.*;

public class PositionLayout implements LayoutManager {
    Vector  positions = new Vector(10);
    Vector  components = new Vector(10);
    private  boolean   leftRatio;
    private  boolean   topRatio;
    private  boolean   rightRatio;
    private  boolean   bottomRatio;

    public void addLayoutComponent(String p, Component c) {
        positions.addElement(p);
        components.addElement(c);
    }


    public void removeLayoutComponent(Component c) {

        int i = components.indexOf(c);
        if(i != -1) {
            positions.removeElementAt(i);
            components.removeElementAt(i);
        }
    }


    public Dimension preferredLayoutSize(Container parent) {
        Dimension dim = new Dimension(0,0);
        int ncomponents = parent.getComponentCount();
        Insets  insets = parent.getInsets();
        dim.width += insets.left + insets.right;
        dim.height += insets.top + insets.bottom;

        int maxW = dim.width;
        int maxH = dim.height;
        for(int i = 0; i < ncomponents; i++) {
            Component  com = parent.getComponent(i);
            if(com.isVisible() == false)
                continue;
            int  w = com.getPreferredSize().width + dim.width;
            int  h = com.getPreferredSize().height + dim.height;
            if( w > maxW )
                maxW = w;
            if( h > maxH )
                maxH = h;
        }
        //System.out.println("prefer Size: width="+maxW+"\t\theight="+maxH);
        return  new Dimension(maxW, maxH);
    }


    public Dimension minimumLayoutSize(Container target) {
        return  target.getSize();
    }


    public void layoutContainer(Container target) {
        Insets insets = target.getInsets();
        int    ncomponents = target.getComponentCount();
        Dimension d = target.getSize();
        d.width  -= insets.left + insets.right;
        d.height  -= insets.top + insets.bottom;
        int  startX =0, startY =0, endX =0, endY =0;
        int  left = 0, top =0, right = 0, bottom =0;
        for(int i=0; i< ncomponents; i++) {
            Component comp = target.getComponent(i);
         StringTokenizer token = new StringTokenizer((String)positions.elementAt(i), ", \t");
            String  leftSt = token.nextToken();
            if(leftSt.endsWith("%")) {
                leftRatio = true;
                left  = Integer.parseInt(leftSt.substring(0, leftSt.length()-1));
            } else {
                left  = Integer.parseInt(leftSt);
            }

            String  topSt = token.nextToken();
            if(topSt.endsWith("%")) {
                topRatio = true;
                top  = Integer.parseInt(topSt.substring(0, topSt.length()-1));
            } else {
                top  = Integer.parseInt(topSt);
            }

            String  rightSt = token.nextToken();
            if(rightSt.endsWith("%")) {
                rightRatio = true;
                right  = Integer.parseInt(rightSt.substring(0, rightSt.length()-1));
            } else {
                right  = Integer.parseInt(rightSt);
            }

            String  bottomSt = token.nextToken();
            if(bottomSt.endsWith("%")) {
                bottomRatio = true;
                bottom  = Integer.parseInt(bottomSt.substring(0, bottomSt.length()-1));
            } else {
                bottom  = Integer.parseInt(bottomSt);
            }

            if(leftRatio)
                startX = (d.width * left)/100;
            else
                startX = left;

            if(topRatio)
                startY = (d.height * top)/100;
            else
                startY = top;

            if(rightRatio)
                endX = (d.width * right)/100;
            else
                endX = right;

            if(bottomRatio)
                endY = (d.height * bottom)/100;
            else
                endY = bottom;

            if(startX > endX) {
                int temp = startX;
                startX = endX;
                endX = temp;
            }
            if(startY > endY) {
                int temp = startY;
                startY = endY;
                endY = temp;
            }
            int  w = endX - startX;
            int  h = endY - startY;
            comp.setBounds(startX+insets.left, startY+insets.top, w, h);
            topRatio = false;
            leftRatio = false;
            rightRatio = false;
            bottomRatio = false;
        }
    }

    public String toString() {
        return getClass().getName();
    }
}

+ Recent posts