JavaFX 셰이프(Shape)을 사용한 벡터 그래픽

JavaFX에서는 GUI 부품처럼 화면에 배치 할 수 있는 벡터 그래픽 부분이 있다. 이를 이용한 그래픽의 생성을 설명한다.

FXML 셰이프를 사용하기

Canvas를 사용한 그래픽 그리기는 Swing, AWT 등과 감각적으로는 비슷하다. 바꿔 말하자면, 그래픽 컨텍스트를 얻어서 렌더링 메소드를 호출하는 화면에 그리는 방식이다. 그려지는 그래픽은 단순한 비트맵 그래픽이다. 그 자체가 그리게 되면 그것으로 끝이다.

이러한 비트맵 그래픽과는 별도로, 벡터 그래픽 작업용 기능도 JavaFX에 포함되어 있다. 벡터 그래픽이라는 것은 위치나 크기 등의 그래픽 정보를 보유하고있는 도형이다. 그리고 난 후에 그것을 변경하고 위치 나 크기 등을 조작할 수 있다. 또한 데이터로 만들어 필요에 따라 그리고 고칠수 있기 때문에, 확대 축소해도 비트맵 그래픽처럼 도형이 거칠거나 하지 않는다.

이 벡터 그래픽은 javafx.scene.shape라는 패키지에 포함되어 있다. 이것은 FXML 태그로 사용할 수 있다. 그러면 실제로 간단한 사용 예를 살펴 보자.

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<Pane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.tuyano.libro.AppController">
    <Rectangle x="50" y="50" width="100" height="100"
        fill="RED" stroke="BLUE" strokeWidth="5"/>
    <Circle centerX="150" centerY="150" radius="50"
        fill="GREEN" stroke="CYAN" strokeWidth="5"/>
</Pane>

위에 예제는 사각형과 원형 모양을 표시하는 것이다. 이를 FXML 파일에 작성하고 Java에서 로드 Scene에 표시한다. 예를 들어, app.fxml라는 파일로 생성했다면 아래와 같은 식으로 실행하면 된다.

Scene scene = new Scene(FXMLLoader.load(getClass().getResource("app.fxml")),300,300);
stage.setScene(scene);
stage.show();

여기에서는 빨간색 사각형과 녹색의 원형이 윈도우에 표시된다. 이번에는 셰이프(Shape)을 사용하여 윈도우에서 자유롭게 그래픽을 배치하기 때문에 <BorderLayout> 같은 레이아웃 컨테이너는 사용 의미가 없을 것이다. 그래서 <Pane>라는 컨테이너를 루트 태그로 지정하고 있다. <Pane>은 어떤 레이아웃 기능도 없는 가장 단순한 컨테이너이다. 셰이프처럼 레이아웃이 필요가 없는 컨포넌트를 배치할 때 이용하면 좋을 것이다.

주요 셰이프 FXML 태그

여기에서는 사각형과 원형 모양을 만들었다. 이것들 외에도 다른 모양도 있다. 여기에서 중요한 것은에 대해 정리해 보도록 하겠다.

사각형 모양 “Rectangle”

예제로 사용했던 사각형 모양이다. 여기에는 다음과 같은 속성이 포함되어 있다.

속성 설명
x 가로 위치를 지정한다.
y 세로 위치를 지정한다.
width 폭을 지정한다.
height 높이 (세로 폭)을 지정한다.

원 모양 “Circle”

이것도 예제로 사용했었다. 둥근 원를 표시하는 셰이프이다. 사각형과는 특성이 약간 다르다.

속성 설명
centerX 중심의 가로 위치를 지정한다.
centerY 중심의 세로 위치를 지정한다.
radius 반경을 지정한다.

타원 모양 “Ellipse”

이것은 타원을 그리는 셰이프이다. 둥근 원형과 달리 수직 및 수평 반경을 각각 별도로 지정할 수 있다.

속성 설명
centerX 중심의 가로 위치를 지정한다.
centerY 중심의 세로 위치를 지정한다.
radiusX 가로 반경을 지정한다.
radiusY 세로 방향의 반경을 지정한다.

원호 모양 “Arc”

원형의 일부만을 자른거 같은 원호를 그리는 것이다. 타원의 특성 이외에 호 크기(각도)에 관한 것이 추가되어 있다.

속성 설명
centerX 중심의 가로 위치를 지정한다.
centerY 중심의 세로 위치를 지정한다.
radiusX 가로 반경을 지정한다.
radiusY 세로 방향의 반경을 지정한다.
startAngle 호의 시작점의 각도 (0 ~ 360의 값)을 지정한다.
length 원호의 크기를 각도로 지정한다.
type 호 유형이다. ROUND (중심에서 자르기), CHORD (원호의 끝을 직선으로 연결), OPEN (원호의 끝을 닫지) 중 하나를 지정한다.

직선 모양 “Line”

2점을 연결하는 직선을 그리는 셰이프이다. 시작 지점과 끝 지점의 속성이 필요하다.

속성 설명
startX 시작 지점의 수평 위치를 지정한다.
startY 시작 지점의 수직 위치를 지정한다.
endX 종료 지점의 수평 위치를 지정한다.
endY 끝점의 세로 위치를 지정한다.

모양 전반에 관한 특성

이 밖에 모든 셰이프에 공통적으로 포함되는 속성도 있다. 기본적으로 채우기나 선 정보가 포함되어 있다. 선은 뾰족한 끝의 셰이프의 상태 등 아주 많은 속성이 있는데, 우선 아래의 3개만 기억해두도록 하자.

속성 설명
fill 채우기 색을 지정한다.
stroke 선의 색상을 지정한다.
strokeWidth 선 두께를 지정한다.

우선, 이런 것들이 사용할 수 있게 되면, 기본 도형은 그릴 수 있게 된다. 실제로 태그를 써서 연습을 해보길 바란다.

직선, 곡선의 셰이프

원형과 사각형은 아주 간단한 도형이지만, 더 복잡한 도형을 필요로 할 수도 있다. 이러한 경우에 사용되는 것이 “직선의 다각형(polygon)“이나 “곡선"같은 도형이다.

이러한 도형은 위치 정보에 대한 특성이 매우 많이 필요하며 그 만큼 작성도 복잡하게 된다. 다음 사용법을 대해 정리해보자.

직선 (다각형)의 모양 “Polygon”, “Polyline”

여러 점을 직선으로 연결하는 다각형을 그리기 위한 것이다. Polygon은 시작 지점과 끝 지점을 연결하는 “닫힌 도형"을 그리고, Polyline은 양 끝을 맺지 않는 “개방 된 도형"을 그린다. 기본적인 사용법은 모두 동일하다.

이러한 FXML로 작성하는 경우에는 시작 태그와 종료 태그 사이에 <points>라는 태그를 제공하고, 나아가 그 안에 <Double> 태그를 사용하여 각 위치의 가로 세로 위치 정보를 작성해 간다.

곡선 모양 “QuadCurve”, “CubicCurve”

곡선은 두 가지 모양이 있다. “QuadCurve"는 2차 곡선을 그리기 위한 것이다. 이것은 시작과 끝 지점의 2점 외에 1개의 컨트롤 포인트를 사용해 그려진다. Arc으로 그리는 원호 같은 곡선이다.

CubicCurve은 3차 곡선을 그리기 위한 것이다. 이것은 시작과 끝 지점의 다른 두 개의 컨트롤 포인트를 사용해 그려진다. 이른바 베지어 곡선(Bezier curves)이라고 불리는 것이 이에 해당된다.

이들은 시작점과 끝점, 그리고 컨트롤 포인트의 위치를 모든 속성으로 작성한다.

속성 설명
startX 시작 지점의 수평 위치를 지정한다.
startY 시작 지점의 수직 위치를 지정한다.
endX 종료 지점의 수평 위치를 지정한다.
endY 끝점의 세로 위치를 지정한다.

QuadCurve의 경우

속성 설명
controlX 컨트롤 포인트의 수평 위치를 지정한다.
controlY 컨트롤 포인트의 세로 위치를 지정한다.

CubicCurve의 경우

속성 설명
controlX1 제 1 컨트롤 포인트의 수평 위치를 지정한다.
controlY1 제 1 제어 포인트의 세로 위치를 지정한다.
controlX2 제 2 컨트롤 포인트의 수평 위치를 지정한다.
controlY2 제 2 제어 포인트의 세로 위치를 지정한다.

아래 Polygon과 CubicCurve을 표시하는 FXML 샘플이다.

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.shape.Ellipse?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.shape.Polygon?>
<?import javafx.scene.shape.CubicCurve?>
<Pane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.devkuma.javafx.AppController">
 
    <Polygon fill="RED">
    <points>
        <Double fx:value="110.0" />
        <Double fx:value="10.0" />
        <Double fx:value="210.0" />
        <Double fx:value="20.0" />
        <Double fx:value="150.0" />
        <Double fx:value="100.0" />
    </points>
    </Polygon>
 
    <CubicCurve fill="YELLOW" stroke="BLUE" strokeWidth="5"
        startX="50" startY="50" endX="200" endY="200"
        controlX1="200" controlY1="50"
        controlX2="50" controlY2="200" />
 
</Pane>

<Polygon> 태그 안에 <points> 태그가 있고, 또 안에는 <Double> 태그를 사용하여 각 정점의 가로와 세로를 순서대로 작성되어 있다. 작성 방법이 조금 이해하기 어렵기 때문에 잘 태그의 구조를 이해하고 사용해야 한다.

Java 소스 코드로 셰이프 사용

FXML를 사용하지 않고, Java 소스 코드에서 직접 모양의 개체를 만들고, 스테이지에 설정 표시 할 수 있다. 실제로 해보록 하자.

아래에 샘플은 아와 같다.

package com.devkuma.javafx;
     
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
 
public class App extends Application {
     
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Pane root = new Pane();
        createShape(root);
        Scene scene = new Scene(root,300,300);
        stage.setScene(scene);
        stage.show();
    }
     
    public void createShape(Pane root){
        Rectangle r = new Rectangle(50, 50, 100, 100);
        r.setFill(Color.RED);
        r.setStroke(Color.BLUE);
        r.setStrokeWidth(3);
        root.getChildren().add(r);
        Circle c = new Circle(150, 150, 50);
        c.setFill(Color.YELLOW);
        c.setStroke(Color.GREEN);
        c.setStrokeWidth(10);
        root.getChildren().add(c);
    }
}

여기에서는 createShape라는 메소드를 사용하고, 그 속에서 사각형과 원형 모양을 그릴 수 있다. 먼저 FXML로 만든 샘플과 같은 것이기 때문에, 양자를 비교해 보면 차이를 잘 알 수 있다.

인스턴스 만들기

모양의 클래스는 Rectangle와 Circle 모두 FXML 태그와 같은 이름이다. 인스턴스의 생성은 모두 필수 속성을 인수로 지정하는 형태로 되어 있다.

new Rectangle(가로 위치, 세로 위치, 폭, 높이)
new Circle(중심 가로 위치, 중심 세로 위치, 반경)

이 클래스에는 인수가 없는 디폴트 생성자에서 여러 종류의 생성자가 존재하며, 다양한 방식으로 인스턴스를 생성할 수 있다. 필수 항목이 되는 속성의 값을 모두 인수로 넣은 모양이 가장 잘 알기 쉽다.

속성 설정

작성 후, fill, stroke, strokeWidth의 속성을 설정하는 메소드를 호출한다. Rectangle라면 다음과 같다.

r.setFill (Color.RED);
r.setStroke (Color.BLUE);
r.setStrokeWidth (3);

FXML 태그에 포함되어 있던 속성은 “set 속성 이름"라는 메소드로 값을 설정할 수 있다. 또한 이번에는 사용하지 않았지만 “get 속성 이름” 또는 “is 속성 이름"라는 메소드로 값을 얻을 수 있다.

색상 값에 대해

이번 setFill과 setStroke에서는 Color 클래스의 필드를 지정하고 있다. 이들은 먼저 Canvas에서 이용을 하였다. 채우기 및 선 색상을 Color로 설정하는 등 기본적인 아이디어는 Canvas 그리기와 거의 같은 것이다.

클리핑(clipping)으로 오려내기

기본 도형을 만드는 방법은 대체로 알게 되었다. 하지만, 좀 더 복잡한 도형을 만드는 방법에 대한 배우고 싶은 기능에 대해서도 몇 가지 보충 하도록 하자.

우선 ‘클리핑(clipping)‘에 대해서이다. 클리핑라는 것은 윈도우에 ‘창’을 열고, 거기에서 표시하는 것이다. 요약하면, 그려진 그래픽 일부분만 잘라내어 표시할 수 있다.

이 클리핑은 “Node"라는 클래스에 있는 “setClip"라는 메소드로 설정할 수 있다. Node 클래스는 BorderLayout과 Pane 등의 컨테이너 종류이며, 나아가서는 여기에서 다룬 모양 종류의 슈퍼 클래스이기도 하다.

"Node".setClip("Node");

이 setClip는 인수로 Node 인스턴스를 지정한다. 그거로 부터 그 부품에 인수 노드의 형상을 클리핑 영역으로 설정한다. 그 부품의 표시는 인수에 지정된 노드 형상의 모양으로 잘라낸 것이 표시된다. 인수 노드의 형상은 외부에 아무것도 그려지지 않는다.

그럼 실제로 해 봅시다. 아래에 예제 코드와 같이 작성해 보자. 이전에 createShape 방법을 수정하여 사용하자.

public void createShape(Pane root){
    Rectangle r = new Rectangle(50, 50, 100, 100);
    r.setFill(Color.RED);
    r.setStroke(Color.BLUE);
    r.setStrokeWidth(3);
    root.getChildren().add(r);
    Circle c = new Circle(150, 150, 50);
    c.setFill(Color.YELLOW);
    c.setStroke(Color.GREEN);
    c.setStrokeWidth(10);
    root.getChildren().add(c);
    Circle clip = new Circle(120, 120, 75);
    root.setClip(clip);
}

여기에서는 RectangleCircle을 작성하여 root에 설정한 후, 클리핑 용 Circle을 만들었다.

root.setClip(clip);

그리고 그것을 위와 같이 클리핑으로 설정하고 있다. 그러면 setClipCircle 원형 모양으로 잘라 내서 그래픽이 표시가 된다.

경로(Path)로 복잡한 도형 생성

또 다른 복잡한 도형 그리는 방법으로 “경로(Path)“를 이용할 있다. 경로는 단번에 그리는 것과 같이 직선과 곡선을 그려 나가기로 도형을 작성하는 것이다.

이것은 “Path"클래스를 이용하여 작성한다. Path 인스턴스를 만들고 거기에 선 그래픽이 되는 인스턴스를 생성하고 설정하여 도형을 만들다. Path에 설정 할 수 있는 그래픽 부분은 “PathElement"라는 클래스의 서브 클래스로 다음과 같은 것이 있다.

드로잉 위치를 이동하는 MoveTo

new MoveTo(가로 위치, 세로 위치)

단번에 그려 그리기 위치를 이동하는 것이다. 인수로 지정된 위치에 렌더링 위치를 이동한다.

직선을 그리는 LineTo

new LineTo(종단 가로 위치, 종단 세로 위치)

현재 드로잉 위치에서 지정된 지점까지 직선을 그린다. 인수로 선의 종단이 되는 위치를 지정한다.

2차 곡선을 그리는 QuadCurveTo

new new QuadCurveTo(CP 가로 위치, CP 세로 위치, 종단 가로 위치, 종단 세로 위치)

현재 드로잉 위치에서 2차 곡선을 그린다. 인수에는 컨트롤 포인트의 위치와 종료 위치를 지정한다.

3차 곡선을 그리는 CubicCurveTo

new new QuadCurveTo(CP 가로 위치1, CP 세로 위치1, CP 가로 위치2, CP 세로 위치2, 종단 가로 위치, 종단 세로 위치)

현재 드로잉 위치에서 3차 곡선을 그린다. 인수에는 2개의 컨트롤 포인트의 위치와 종료 위치를 지정한다.

그럼, 실제 사용 예제를 보도록 하자. 아래의 목록은 아까의 샘플 createShape 메소드를 수정 한 것이다. 이것으로 두 개의 직선과 1개의 3차 곡선으로 이루어진 경로 도형을 만들고 표시한다.

public void createShape(Pane root){
    Path path = new Path();
    MoveTo mt1 = new MoveTo(50, 50);
    path.getElements().add(mt1);
    LineTo lt1 = new LineTo(250, 50);
    path.getElements().add(lt1);
    CubicCurveTo cc1 = new CubicCurveTo(250, 250,50, 50, 50, 250);
    path.getElements().add(cc1);
    LineTo lt2 = new LineTo(50, 50);
    path.getElements().add(lt2);
    path.setFill(Color.RED);
    root.getChildren().add(path);
}

여기에서는 Path 인스턴스를 만들고 MoveTo, LineTo, CubicCurveTo와 같은 인스턴스를 생성하지 포함되어 있다. 도형의 연결은 아래와 같이 한다.

path.getElements().add(mt1);

Path의 getElements메소드는 Path에 설정되어 있는 List(ObserbableList) 인스턴스를 얻기 위해 사용된다.

Path에는 이 List에 그리는 도형의 PathElement를 관리하고 여기에 add 메소드에서 인스턴스를 추가해 나갈 것으로 그리는 도형이 추가되어 간다. 단순한 도형의 조합으로 그릴 수 없는 복잡한 형상의 모양도 Path를 이용하면 그릴 수 있다.




최종 수정 : 2017-09-19