본문

SWT에서의 GC, Drawable, Canvas #2. Canvas의 기능과 사용

[그림 1] 이전 글(SWT에서의 GC, Drawable, Canvas #1. 설명)에서 사용한 것과 동일한 그림.

Control 객체가 그 객체의 paintEvent를 통하여 그림을 그릴 수 있지만, 이보다는 Control 클래스를 상속받아 그래픽 작업을 위해 설계된 특별한 클래스인 Canvas클래스를 사용하여 그림을 그리는것이 더욱 좋습니다. Canvas 클래스를 생성한 후 addPaintListener()를 통하여 페인트 리스너를 추가하거나, 사용자가 직접 정의한 Control을 상속받음으로서 그림을 그릴 수 있습니다. Canvas클래스는 그림을 그릴때 사용할 수 있는 다양한 종류의 스타일 비트를 가지고 있습니다.

Canvas의 기본적인 동작은 다음과 같습니다. 객체의 할당영역을 그리기 앞서, 객체에 할당된 전체 영역을 현재 배경색으로 칠합니다. 이때 화면이 깜박이는 현상을 볼 수 있는데 이는 paintEvent가 배경색을 칠하고, 그 위에 Canvas의 요소들을 덧칠하는 순간을 우리가 보는 것입니다. 이 현상을 방지하기 위한 방법 중 하나는, Canvas를 생성할 때 SWT.NO_BACKGROUND 스타일 비트를 사용하여 배경이 그려지지 않게 하는것입니다. 단, 이 비트를 사용할 경우, 프로그램은 영역 전체에 대하여 모든 픽셀을 직접 그려야 합니다.

위젯의 사이즈가 변경될 때에도 paintEvent가 발생합니다. 이 역시 화면을 깜박거리게 할 수 있는데, 왜냐하면 사이즈가 변경될 때마다 해당 영역에 대해 모든 요소들을 다시 그려야 하기 때문입니다. 이는 SWT.NO_REDRAW_RESIZE 스타일 비트를 사용하여 완화시킬 수 있으며, 이는 컨트롤의 사이즈가 변경되더라도 paintEvent가 발생하지 않는다는것을 의미합니다. 이는 컨트롤이 불필요하게 다시 그려지는것을 방지한다는 의미를 갖으며, 만약 크기자 증가된다고 한다면, paintEvent의 GC는 이 영역을 제외한 부분만을 다시 그리게 됩니다. 단 사각형 모양이 캔버스가 존재할 경우 사각형의 오른쪽 하단에 L이 거꾸로 된 형태가 그려질 수 있다는것이 알려졌습니다.

NO_REDRAW_RESIZE 스타일 비트는 새로 그려지는 부분에 대해서 적절히 처리해줄 경우, 고정사이즈의 그림이 GC에 그려질 때 나타나는 깜박임을 감소시킬 수 있습니다. 하지만 잘못 쓰였을 경우, NO_REDRAW_RESIZE는 치즈(cheese)라는 효과를 불러올 수 있습니다. 치즈는 다시 그려야 할 상황에 제대로 다시 그려지지 않는 부분이 있는 경우를 지칭합니다. paintEvent가 클라이언트 전 영역을 다시 그려야 하는 아래의 예시를 확인해보세요. 화면이 작아질 때에는 paintEvent가 발생하지 않기 때문에 그림이 다시 그려지지 않습니다. SWT.NO_REDRAW_RESIZE상태에서 화면이 커질 경우, paintEvent는 새로 그려야 하는 부분에 대해서만 영역을 다시 그리기 떄문에, 그리고 기존에 그려졌던 부분이 지워지지 않았기 때문에 치즈 현상이 발생하게 됩니다. 캔버스 사이즈가 증가할 때 GC는 필요한 부분만 다시 그리기 때문에 치즈 현상이 발생하게 됩니다.

shell.setLayout(new FillLayout());
final Canvas canvas = new Canvas(shell,SWT.NO_REDRAW_RESIZE);

canvas.addPaintListener(new PaintListener() {
    public void paintControl(PaintEvent e) {
        Rectangle clientArea = canvas.getClientArea();
        e.gc.setBackground(display.getSystemColor(SWT.COLOR_CYAN));
        e.gc.fillOval(0,0,clientArea.width,clientArea.height);
    }
});

이 문제를 해결하는 방법은 SWT.NONE 스타일 비트를 사용하여 GC가 크기가 커질 때 커진 부분만 새로 그리는것이 아닌, 모든 부분을 새로 그리도록 하고, 동시에 paintEvent가 셸 사이즈가 작아졌을 때에도 발생하게 하여, canvas의 전체부분이 다시 그려지게 하는 것입니다.

final Canvas canvas = new Canvas(shell,SWT.NONE);

각각의 SWT 위젯에 대해, 만약 하나이상의 영역이 다시 그려져야 할 경우, 운영체제는 다시 그려야 할 영역을 하나의 영역으로 묶어서 paintEvent를 발생하여 효율적인 처리를 가능하게 합니다. Canvas의 NO_MERGE_PAINTS 스타일 비트는 각각의 위젯에서 다시 그릴 영역을 하나로 묶지 않고, 각각의 영역에 대해 일일히 paintEvent를 호출하는 방식으로 변경합니다.

NO_BACKGROUND, NO_REDRAW_RESIZE, NO_MERGE_PAINTS 스타일 비트는 Composite와 이 하부 클래스인 Canvas, Shell, Group등에서 사용할 수 있습니다, SWT에서 허용하긴 하지만, Composite클래스의 Javadoc에서는 스타일 비트에 대해서 다음과 같이 기술합니다. "Canvas 이외의 Composite의 하위클래스에서의 사용은 정의되지 않았다". 그러므로 Canvas 클래스가 그림 그리는데에 가장 적합한 컨트롤이라 할 수 있습니다.

깜박임을 방지하는 또다른 방법은 더블 버퍼링을 사용하여 그림그리기를 한번에 처리하는 것입니다. 더블 버퍼링은 paintEvent에서 제공하는 GC기 아닌 GC에 그림을 미리 그린 후, 이것을 제공되는 GC에 복사하는 기술입니다. 이를 위하여, Canvas의 영역과 동일한 사이즈의 Image 객체를 만들고, GC(Image)를 통하여 이 객체에 그림을 그립니다. 그려진 그림(Image)은 drawImage(Image, int, int) 메소드를 호출함으로서 paintEvent의 GC로 그려지게 됩니다. 이 기술을 사용할 때 주의할 점은 몇몇 운영체제는 이미 더블버퍼링을 자체적으로 구현하고 있으므로, 결국 트리플 버퍼링이 이루어질 수 있다는 점입니다.


================
어느정도 의역을 하였습니다. 대표적으로 lientArea는 해당 영역, flatform은 운영체제, cropping 개념은 풀어서 설명했습니다. 사실 NO_BACKGROUND, NO_REDRAW_RESIZE, NO_MERGE_PAINTS는 왠만해선 사용하지 않고 SWT.NONE을 기본으로 붙여 사용합니다. 그리고 참고로 윈도우 환경에서는 더블 버퍼링을 기본으로 지원하지 않기 때문에 더블 버퍼링을 구현해 주어야 합니다. 이에대한 예제는 java2s의 Java » SWT JFace Eclipse » Image카테고리에 위치한 Double Buffer 페이지에 잘 나와있습니다. 기존의 버퍼를 저장해서 횔용하는 방법으로 더욱 좋은 효율의 더블 버퍼링을 구현하였네요, 대신 간단한 개념을 좀 복잡하게 해놔서 입문자에게는 좀 불리합니다. 그리고 생성한 GC는 dispose()로 꼭 없애는것을 잊지마세요.


댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.