Previous Next Table of Contents

4. X 프로그래밍 기초 : 그래픽

아직 창에 대하여 할 이야기들은 많이 남아 있지만, 썰렁한 창만 가지고 이리 저리 장난치는 것은 그만두고 당분간은 GRAPHICS 에 대하여 공부해보도록 하겠습니다.

4.1 X 윈도우 그래픽의 기본 개념

어디에(Where) 그릴 것인가?

뭐, 너무 당연한 것 아니겠어요? 창(Window) 안에다 그림을 그리겠지요. 하지만, 오직 그것만은 아니랍니다. 창 말고도 그릴 수 있는 곳이 있는데, 그것을 Pixmap 이라고 합니다. 중요한 자료형(Variable Type) 중에 Window와 Pixmap 이 두 가지가 있는데, 이 두 개를 합쳐서 자료형 Drawable이라고 합니다. Drawable? 자, 그림을 그리는 것이 가능하다는 뜻이죠? 어디서 본 것도 같죠? 바로 전번 강의 XGetGeometry 함수에서 잠깐 나왔던 녀석입니다. 당분간 계속 창에다만 그릴 것이오니, Pixmap에 대한 설명을 다음으로 미룹니다. 이렇게 미뤄도 되냐구요? 걱정하지 마십시요. Drawable 자료형이라고 되어 있는 것은 Window나 Pixmap 두 가지에 똑같이 적용되므로 새롭게 배우실 필요가 없고 이해하시기도 쉬울 겁니다.

어떤 붓을 가지고? ( Graphics Context )

큰 붓, 작은 붓 ... 빨간색, 노란색, 주황색 ... 무엇인가를 그릴 때 자신의 구미에 맞게 다양한 도구를 선택할 수 있어야 하겠죠? X 윈도우 시스템은 Graphics Context 첫글자만 따서 GC라고 하는 아주 특별한 자원(Resource)을 가지고 있습니다. 요 GC라고 하는 것에 과연 어떻게(How) 그릴 것인가? 라는 문제에 대한 정보를 전적으로(!) 가지고 있습니다. 항상 자원은 서버가 독재적으로(!) 관리를 하는 모습을 여기서도 엿볼 수 있군요. :)

어떤 것을 그릴 수 있는가? ( 그래픽 내용 )

그래픽에 기본인 점(Point), 선(Line)을 그릴 수 있습니다. 하나의 점, 하나의 선을 그리라고 서버에게 요청하는 함수들이 당연히 구비되어 있고, 여기에 개발자 편리를 위해서 여러 개의 점, 여러 개의 선을 그리는 함수, 사각형, 원호 등등을 그리도록 요청하는 함수가 또한 준비되어 있습니다. 마지막으로 텍스트 문자를 그려낼 수 있는 함수들이 있어서 원하는 폰트를 가지고 문자를 표현할 수 있습니다. X 윈도우는 GUI 체제이므로 글자 또한 쓰는 게 아니라 다른 도형과 마찬가지로 그리는 것이죠. 참고로 여러분이 슬랙을 설치할 때 PEX 라이브러리를 설치하셨다면 3차원 그래픽을 아주 쉽게 그릴 수 있습니다. 나중에 기회가 되면 약간 설명을 드릴 예정입니다만...

그래픽의 기본 절차

어떤 프로그래밍이든 마찬가지이겠지만, X 윈도우 프로그래밍은 어떤 일을 수행하든지 정해져 있는 몇 단계의 절차를 따라야 합니다. 이러한 절차를 머리 속에 항상 기억하실 수 있는 분들이야말로 X 윈도우 프로그래밍 도사들이 되시는 겁니다.

<기본 절차>

  1. 우선 그릴 대상이 미리 준비되어 있어야 하겠죠? 우리는 XCreateSimpleWindow 함수를 통해 창을 만들어 놓으면 됩니다.
  2. 중요!!! 그 다음에는 GC라고 하는 특별한 자원을 서버에게 만들어 달라고 요청합니다. 그리고 우리는 GC의 ID만 서버에게서 반환을 받습니다. 우리가 GC 자체내용을 다 가져올 수는 없습니다. 단지! 그 ID만 가져옵니다.
  3. 그리고 싶은 스타일을 정하고 GC의 특정값을 바꾸어 달라고 요청합니다. 예를 들어 선의 굵기, 점선이냐 실선이냐, 색상은 어떤 것으로 하느냐 등등
  4. 그리고 싶은 도형을 선택해서 알맞는 함수를 호출하여 점이든 선이든 또는 사각형이든가에 어떤 창에 어떤 GC를 이용해서 그리라고 서버에게 요청합니다.

지금부터는 이 순서 그대로 설명을 해나갈 것입니다. 위 기본절차를 꼭 기억하세요.

우선 GC 를 만들자.

기본 절차 1 에 대해서는 설명드리지 않습니다. 창 만드는 거 지금까지 해왔잖아요?

 함수의 원형 : <X11/Xlib.h>

 GC XCreateGC ( Display *display,
                Drawable drw,
                unsigned long bitmask,
                XGCValues *values
              );

bitmask, XGCValues 형 변수 values에 대한 설명만이 필요하겠군요.

<X11/Xlib.h>에 정의되어 있는 XGCValues구조체엔 다음과 같은 멤버들이 있습니다.

--------------------------------------------------------------------
 #   멤 버 명                   비트 매스트             기본값
--------------------------------------------------------------------
 1  int function                GCFunction              GXCopy
 2  unsigned long plane_mask    GCPlaneMask             모두 1
 3  unsigned long foreground    GCForeground            0
 4  unsigned long background    GCBackground            1
 5  int line_width              GCLineWidth             0
 6  int line_style              GCLineStyle             LineSolid
 7  int cap_style               GCCapStyle              CapButt
 8  int join_style              GCJoinStyle             JoinMiter
 9  int fill_style              GCFillStyle             FillSolid
10  int fill_rule               GCFillRule              EvenOddRule
11  int arc_mode                GCArcMode               ArcPieSlice
12  Pixmap tile                 GCTile                  ?
13  Pixmap stipple              GCStipple               ?
14  int ts_x_origin             GCTileStipXOrigin       0
15  int ts_y_origin             GCTileStipYOrigin       0
16  Font font                   GCFont                  ?
17  int subwindow_mode          GCSubwindowMode         ClipByChildren
18  Bool graphics_exposures     GCGraphicsExposures     True
19  int clip_x_origin           GCClipXOrigin           0
20  int clip_y_origin           GCClipYOrigin           0
21  Pixmap clip_mask            GCClipMask              None
22  int dash_offset             GCDashOffset            0
23  char dashes                 GCDashList              4

비트 매스크 매크로와 기본값에서의 매크로는 <X11/Xlib.h> 화일에서 자동으로 포함(#include)하는 <X11/X.h> 화일의 GRAPHICS DEFINITION 부분에 정의되어 있으니 그 부분을 찾아보시기 바랍니다. ? 표한 부분은 아직은 알 필요가 없거나 16번 Font의 경우에는 시스템마다 다른 다른 값이므로 정해지지 않은 값이라는 뜻입니다.

위 도표를 주의깊게 살펴보십시요. 멤버명과 비트 매스크명 사이에는 한 눈에도 척 알아볼 수 있는 명명규칙이 있습니다. 비트 매스명은 항상 GC로 시작하고 멤버명이 소문자인 것과 달리 항상 대문자, 그리고 단어마다 대문자를 씁니다. 멤버명에 있는 대쉬(-) 문자는 없앱니다. 멤버명을 소문자로만 썼기 때문에 단어구별을 위해 대쉬가 들어간 것에 불과하니까요. 이러한 명명 방식은 X 전반에 사용되고 있으니 소홀히 넘기지 마시기 바랍니다.

프로그램 내에서 보통 다음과 같은 과정을 통해 GC를 서버 측에 생성,등록시키고 ID를 입수합니다.

 Display *display;      /* 디스플레이 서버의 ID */
 Window window;         /* 생성될 창의 ID */
 GC gc;                 /* 우리가 사용할 붓 */
 XGCValues gvalue;      /* 붓의 속성을 지정할 때 그 속성을 저장해둘 변수 */

 ...

 /* 우리가 원하는 그래픽 속성만을 셋팅한다 */
 gvalue.line_width = 20;
 gvalue.line_style = LineOnOffDash;
 gvalue.cap_style = CapRound;
 gvalue.join_style = JoinRound;         /* 주의 */

 gc = XCreateGC ( display, window, GCLineWidth | GCLineStyle | GCCapStyle,
                  &gvalue );

 /* 여기서 눈여겨 볼 것은 GCLineWidth | GCLineStyle | GCCapStyle 부분이다.
    우리가 XGCValues 형 변수 gvalue에 셋팅한 속성의 매스크를 OR 형식으로
    지정해주어야 한다.                                                    */

붓을 처음 만들 때부터 미리 몇 가지 속성을 만들어 놓고 시작하는 전형적인 방식 입니다. 비트 매스크 쓴 방식을 정말로 유심히 살펴보십시요. 그러면 한 가지 사실을 눈치채실 수 있습니다. /* 주의 */라고 한 줄에서 두 줄이 이어질 때 어떤 모양으로 이어질지에 대하여 정해주었지만 GC를 만들 때 비트 매스크에서 GCJoinStyle 을 넣어주지 않았기 때문에 속성이 되지 못합니다. 어떻게 보면 당연하죠? 컴퓨터가 무슨 재주로 우리가 무슨 값을 원하는지 알겠습니다. 이러저러한 것을 지정해 두었으니 그것을 셋팅하라. 이런 식으로 자세히 알려주지 않으면 안됩니다.

GC의 스타일 설정하기

매번 GC를 만들 때만 속성을 정할 수 있는 것은 아닙니다. 이미 빨강 물감을 적신 붓이라 하더라도 물에 씻어내고 파랑 물감을 적실 수도 있는 것 아니겠습니까?

 함수의 원형 : <X11/Xlib.h>

 XChangeGC (    Display *display,
                GC gc,
                unsigned long mask,
                XGCValues *gvalues
           );

사용법은 위에서와 같습니다. 붓의 속성을 바꾸고 싶을 때는 XGCValues 형 변수의 멤버에 적절한 값을 셋팅한 후, 이 함수를 부르면 됩니다. 비트매스크를 잘 쓰셔야겠죠?

이제 물감에 적신 붓으로 그려봅시다.

점 그리는 함수

 함수 원형 : <X11/Xlib.h>

 XDrawPoint( Display *display, Drawable d, GC gc, int x, int y );
 XDrawPoints ( Display *display, Drawable d, GC gc, XPoint *points,
                int npoints, int mode );

 typedef struct {
        short x, y;
 } XPoint;
 /* 참고 : XSegment, XRectangle, XArc */

XDrawPoints 함수는 뒤에 복수형임을 의미하는 s가 붙은 것에 유의합시다. 여러 개의 점을 XPoint 형 구조체 배열에 넣어서 한 번의 함수 부르기로 그리고자 할 때 유용합니다. 모든 스타일에 대한 셋팅은 항상 GC 형 변수가 가리키는 서버의 메모리에 있다는 것을 기억합시다. npoints 변수는 점 배열의 갯수를 지정해줍니다. mode 에는 다음 두 가지가 있는데 CoordModeOrigin은 주어진 모든 좌표가 Drawable의 원점에 대한 좌표로 주어졌음을 의미하며, CoorDModePrevious는 최초의 점만 Drawable 원점에 대한 좌표이며 나머지는 바로 직전의 점에 대한 상대 좌표로 표시되어 있음을 나타내줍니다.

직선 그리는 함수

 함수 원형 : <X11/Xlib.h>

 XDrawLine( Display *display, Drawable d, GC gc, int x1, int y1,
                                                int x2, int y2 );
 XDrawLines( Display *display, Drawable d, GC gc, XPoints *points,
                                int npoints, int mode );
 XDrawSegments( Display *display, Drawable d, GC gc, XSegment *segments,
                                                        int nsegments );

첫번째 함수는 Drawable 내에서 ( x1, y1 )에서 ( x2, y2 ) 좌표까지 선을 긋는 아주 일반적인 함수입니다. 두번째 함수는 연필 떼지 않고 그리기를 말합니다. XPoint 구조체 배열의 점들이 계속 이어지는 형태로 그려진다. 좌표에 대한 표기는 mode 값으로 합니다. 점 그리는 함수 XDrawPoints에서와 같습니다. 세번째 XDrawSegments는 두번째 함수와 달리 연속된 배열의 점들을 한 쌍 씩 묶어서 선을 여러 개 그린다. (x1,y1)에서 (x2,y2)에 긋고, 다시 (x3,y3)에서 (x4,y4)에 긋는 방식입니다.

선 스타일이 많으므로 Xlib에 대한 레퍼런스 PS 화일 등을 구하는 것이 좋습니다. 책을 사지 않아도 완벽한 설명서가 될 만큼 잘 만들어져 있습니다.

 참고 함수 : XSetLineAttributes ()
             XSetDash ()

직사각형 그리는 함수

 함수 원형 : <X11/Xlib.h>

 XDrawRectangle ( Display *display, Drawable d, GC gc, int x, int y,
                        unsigned int width, unsigned int height );
 XDrawRectangles( Display *display, Drawable d, GC gc,
                        XRectangle rectangels[], int nrectangles );

나머지

그래픽에 관심이 많으신 분들은 스스로 나머지 함수들을 조사하시기 바랍니다. 이것에 만족하시지 못하는 분들은 3D 그래픽 라이브러리인 PEXlib 에 대한 공부를 하시면 됩니다. 이것에 대한 내용은 Xlib 프로그래밍 수준을 넘어서고 그 자체로도 방대하기 때문에 여러분의 의지에 맡깁니다.

위에서 설명한 함수들을 가지고 다음에는 실습을 해보기로 합시다. 그리고 간략하게 폰트를 가지고 문자열 나타내기를 다루어보겠습니다.

4.2 그래픽의 연습

Xlib 그래픽은 X 프로그래밍의 기초가 되어주는 아주 기본적인 기능입니다. X 윈도우는 온통 그래픽으로 가득 차 있다고 생각하시면 됩니다. 점 하나부터 모든 복잡한 도형은 결국 Xlib 의 함수를 통해서 가능한 것입니다. 제 계획으로는 엄청난 속도로 Xlib 프로그래밍을 마치고 어서 빨리 여러분이 재미있어 할 Xt 프로그래밍으로 넘어가고 싶은데요, 바로 그 곳에서 위젯을 새로 만든다든가 할 때 아주 유용하게 쓰입니다. 저를 따라 오시면 여러분만의 멋진 위젯을 만들 수 있도록 안내해드리겠습니다. 또 모르죠. 여러분 중에서 정말로 상상력이 뛰어나고 멋진 위젯이 나오면 FWF(Free Widget Foundation)의 위젯셋에 포함되어 전세계의 프로그래머들에게 알려질 수도 있습니다. 소스가 공개되어 있지 않는 다른 시스템에서는 불가능한 일 아니겠습니까?

다시 한 번 확인합니다! Xlib 그래픽은 항상 GC 라고 하는 리소스를 가지고 합니다. 그 GC는 그래픽에 관한 여러 정보를 지니고 있는 녀석인데요, 당연히 서버 측에다 GC 생성을 의뢰하고 우리는 그 GC의 ID만을 받아옵니다. 이것은 X 윈도우의 네트워크 지향적 특성에서 나오는 것으로서, 네트워크를 통해서 최소의 정보를 전하면서도 원하는 기능을 얻고자 하기 때문입니다. 예를 들어 매번 red 라는 색상으로 선을 그으라는 100번의 명령이 있다면, red로 그리라고는 명령이 계속 네트워크를 타고 서버로 전해져야 할 것입니다. 여러 가지 주문사항을 미리 등록해놓고 간단히 그 ID만을 전송하는 방식이 훨씬 네트워크의 로드를 줄이는 방법이 되겠죠? 물론 죽어나는 것은 항상 서버입니다. :)

이제부터 그래픽을 연습해봅시다

#include <X11/Xlib.h>

/* 색상 이름에 해당하는 픽셀값 반환하는 함수 */
unsigned long UsrColorPixel( Display*, char* );
/* 요청을 플러쉬하고 지정된 초만큼 지연시키는 함수 */
void    Pause( Display*, int );

static XPoint points[] = {{ 200, 50 },{ 100, 150 },{ 300, 150 },{ 200, 50 }};
static XSegment segs[] = {{ 10, 10, 390, 190 },{ 10, 190, 390, 10 }};
static char dash_list[] = { 20, 5, 10, 5 };

int main()
{
        Display *d;
        Window w; /* One TopLevel Window & Two Child Windows */
        GC gc;
        XGCValues gv;

        /* 서버와 접속하기 그리고 변수들을 초기화 */
        d = XOpenDisplay ( NULL );

        /* 자, 시작해볼까요? */

        w = XCreateSimpleWindow ( d, DefaultRootWindow( d ),
                                  100, 100, 400, 200, 1,
                                  BlackPixel(d,0), WhitePixel(d,0) );
        XMapWindow( d, w );
        Pause( d, 2 );



/*
 *      [1] 점(Point)을 찍어봅니다. XDrawPoint(s) 함수 사용
 */
        gv.foreground = UsrColorPixel( d, "red" );
        gc = XCreateGC( d, w, GCForeground, &gv );
        XDrawPoint ( d, w, gc, 200, 100 );
        Pause( d, 3 );

        gv.foreground = UsrColorPixel( d, "blue" );
        XChangeGC ( d, gc, GCForeground, &gv );
        XDrawPoints ( d, w, gc, points, 3, CoordModeOrigin );
        Pause( d, 3 );

/*
 *      고해상도에서는 뭐 거의 안보일 정도로 RED색의 작은 점 하나,
 *      그리고 나서 BLUE색의 점 3개를 찍고 있습니다. 정말로 집중해서
 *      보셔야 할 겁니다. 모니터가 더러우면 먼지와 구별이 안될지도... :)
 */
        XClearWindow( d, w );
        {
            int i, j;
            for ( j = 0; j < 200 ; j++ )
                for ( i = 0; i < 400 ; i++ )
                {
                    XDrawPoint( d, w, gc, i, j );
                    XFlush ( d );
                }
        }
        Pause( d, 3 );

/*
 *      BLUE 점으로 창 하나 가득히 칠해봅니다. XFlush 함수를 고의로 써서
 *      그 과정을 알아볼 수 있도록 하였습니다. 생각보다 빠르게 찍힐 겁니다.
 *      리눅스는 완전한 의미의 32비트 OS 이니까요.
 */



/*
 *      [2] 선(Line)을 그려봅니다.
 */

        XSetWindowBackground ( d, w, UsrColorPixel( d, "blue" ) );
        gv.foreground = WhitePixel( d, 0 );
        gv.background = UsrColorPixel( d, "red" );
        gv.line_width = 20;
        XChangeGC( d, gc, GCForeground|GCBackground|GCLineWidth, &gv );
        XDrawLine ( d, w, gc, 20, 20, 380, 180 ); /* CapButt */
        Pause ( d, 3 );

        gv.cap_style = CapRound;
        XChangeGC( d, gc, GCCapStyle, &gv );
        XClearWindow( d, w );
        XDrawLine ( d, w, gc, 380, 20, 20, 180 );
        Pause ( d, 3 );

        gv.cap_style = CapProjecting;
        XChangeGC( d, gc, GCCapStyle, &gv );
        XClearWindow( d, w );
        XDrawLine ( d, w, gc, 20, 20, 380, 180 );
        Pause ( d, 3 );

/*
 *      여기까지는 XDrawLine 함수를 사용하였습니다. 캡 스타일(Cap)이 서로
 *      어떻게 다른지 확인해 보십시요.  
 */

        XSetLineAttributes ( d, gc, 10, LineDoubleDash, CapRound, JoinRound );
        XSetDashes( d, gc, 0, dash_list, 4 );
        XClearWindow( d, w );
        XDrawSegments ( d, w, gc, segs, 2 );
        Pause ( d, 3 );

        XClearWindow ( d, w );
        XSetLineAttributes ( d, gc, 15, LineOnOffDash, CapButt, JoinBevel );
        XDrawLines( d, w, gc, points, 4, CoordModeOrigin );
        Pause ( d, 3 );

/*
 *      선끝점 스타일에는 CapButt, CapRound, CapProjecting 3 가지가 있습니다.
 *      선과 선이 만나는 방식에도 세 가지가 있습니다. JoinMiter, JoinRound,
 *      그리고 JoinBevel 이 그것이죠.
 *      중요한 것! 선의 스타일에는 그냥 LineSolid, LineOnOffDash, LineDouble-
 *      Dash 가 있습니다.
 *      XSetLineAttributes(), XSetDashes()는 연구과제입니다.
 */




/*
 *      [3] 이제 2차원으로 가봅시다. 사각형부터 시작!
 */

        XClearWindow( d, w );
        XSetLineAttributes ( d, gc, 5, LineSolid, CapButt, JoinRound );
        XDrawRectangle( d, w, gc, 50 /* X */, 50 /* Y */,
                                300 /* width */, 100 /* height */ );
        Pause( d, 3 );

        {
            XRectangle rect[7];
            int i;

            for ( i = 0; i < 7 ; i ++ )
            {
                rect[i].x = 10 + i * 50; rect[i].y = 20;
                rect[i].width = 40 ; rect[i].height = 150;
            }
            XClearWindow( d, w );
            XSetLineAttributes(d,gc,1,LineDoubleDash,CapProjecting,JoinMiter);
            XDrawRectangles ( d, w, gc, rect, 7 );
            Pause ( d, 3 );
        } 

/*
 *      자, XDrawRectangle(s)의 사용법을 아셨나요? 그렇다면 연구과제가 있스
 *      니다. 이들 대신에 XFillRectangle(s)를 써보십시요. 어떤 일이 일어납
 *      니까?
 */



/*
 *      [4] 둥그런 것도 그려봅시다.
 */

        {
            int i, radi;
            XArc arc[2];

            XClearWindow ( d, w );
            XSetLineAttributes(d,gc,10,LineSolid,CapRound,JoinRound);
            for ( radi = 0 ; radi <= 360 ; radi += 90 )
            {
                XDrawArc ( d, w, gc, 10, 10, 380, 180, 0 * 64, radi * 64 );
                Pause( d, 1 );
            }
            for ( i = 0 ; i < 2 ; i ++ ) {
                arc[i].x = 30 + 200 * i;
                arc[i].y = 30;
                arc[i].width = arc[i].height = 100;
                arc[i].angle1 = 0 * 64;
                arc[i].angle2 = 90 * ( i + 1 ) * 64;
            }
            XClearWindow ( d, w );
            XDrawArcs( d, w, gc, arc, 2 );
            Pause( d, 1 );
            XSetForeground( d, gc, UsrColorPixel(d,"yellow") );
            for ( i = 0 ; i < 2 ; i ++ ) {
                XClearWindow ( d, w );
                XSetArcMode ( d, gc, i == 0 ? ArcPieSlice : ArcChord );
                for ( radi = 0 ; radi <= 360 ; radi += 90 )
                {
                    XFillArc ( d, w, gc, 10, 10, 380, 180, 0 * 64, radi * 64 );
                    Pause( d, 1 );
                }
                XClearWindow( d, w );
                XFillArcs( d, w, gc, arc, 2 );
                Pause( d, 1 );
            }
        }



        /* GC, 창 파괴 & 서버와의 접속 해제 */
        XFreeGC( d, gc );
        XUnmapWindow( d, w );
        XDestroyWindow( d, w );

        XCloseDisplay( d );

        return 0;
}

/*

  UsrColorPixel() : 주어진 이름의 색상에 대한 기본 컬러맵의 픽셀값 반환

*/

unsigned long UsrColorPixel( display, name )
Display *display;
char *name;
{
        Colormap cmap;
        XColor c0, c1;

        cmap = DefaultColormap( display, 0 );

        XAllocNamedColor( display, cmap, name, &c0, &c1 );

        return ( c0.pixel );
}

void Pause( Display *d, int secs )
{
        XFlush ( d );
        sleep( secs );
}

컴파일은 -lX11 옵션을 주셔서 Xlib 와 링크시켜 주시면 됩니다.

최대한 Xlib 그래픽 함수들의 기능을 보이려고 노력했습니다. 하지만 위에서 보여드린 것만으로는 많이 부족합니다. 예를 들어 자유로운 다각형을 그리면서 색상을 칠할 수 있는 XFillPolygon()과 같이 유용한 함수는 설명드리지 않았습니다. 자세한 것은 여러분의 탐구에 달려 있습니다.

다음 번에는 폰트 출력하는 법에 대해서 알아보기로 합시다. 영문자 출력과 당연히 한글 출력에 관한 얘기를 해보도록 하죠. 폰트 강의를 마치면 이제 X 윈도우다운 프로그램을 짤 수 있는 "이벤트"를 다룰 수 있게 되겠군요. Xlib 도 얼마 남지 않았습니다. 힘내세요. :)


Previous Next Table of Contents