이번에는 팔레트 구현을 알아보겠습니다.

 

오른쪽 상단에 보이는 것이 팔레트 기능이다.

 

일단 아래와 같은 배경지식을 알고 갔으면 좋겠습니다.

 

* 그림판 프로그램 : RGB 기반 색 표현

* 팔레트 기능 : HSV 기반 색 표현

* RGB <-> HSV 서로 변환 가능

 

구현하고자 하는 프로그램은 RGB 기반으로 색을 표현합니다.

HSV 기반인 팔레트 기능을 구현하려면 먼저 HSV 색을 RGB로 표현하는 방법을 알아야 합니다.

 

double* hsv_to_rgb(double H, double S, double V) {
   //H, S and V input range = 0 ÷ 1.0
   //R, G and B output range = 0 ÷ 1.0

   double R, G, B;

   if (S == 0)
   {
      R = V * 255;
      G = V * 255;
      B = V * 255;
   }
   else
   {
      double var_h, var_i, var_g, var_r, var_1, var_2, var_3, var_b;
      var_h = H * 6;
      if (var_h == 6) var_h = 0;      //H must be < 1
      var_i = int(var_h);             //Or ... var_i = floor( var_h )
      var_1 = V * (1 - S);
      var_2 = V * (1 - S * (var_h - var_i));
      var_3 = V * (1 - S * (1 - (var_h - var_i)));


      if (var_i == 0) { var_r = V; var_g = var_3; var_b = var_1; }
      else if (var_i == 1) { var_r = var_2; var_g = V; var_b = var_1; }
      else if (var_i == 2) { var_r = var_1; var_g = V; var_b = var_3; }
      else if (var_i == 3) { var_r = var_1; var_g = var_2; var_b = V; }
      else if (var_i == 4) { var_r = var_3; var_g = var_1; var_b = V; }
      else { var_r = V; var_g = var_1; var_b = var_2; }

      R = var_r;
      G = var_g;
      B = var_b;
      double arr[3] = { R, G, B };
      return arr;
   }
}

위 코드는 HSV 값을 받아 RGB로 출력하는 코드입니다.

출처 : https://ko.wikipedia.org/wiki/HSV_%EC%83%89_%EA%B3%B5%EA%B0%84#/media/File:HSV_cone.jpg

RGB는 0~255의 값을 갖고

HSV의 경우 , H는 0~360도의 값을, (최대 360도)

S, V는 0~100%의 값을 갖습니다.

또한 H의 경우 색상을, S와 V는 각각 채도와 명도를 나타냅니다.

 

제가 구현하는 그림판 프로그램의 경우 RGB를 0~1로 정규화 하여 구현합니다.

그리고 HSV 또한 0~1 사이의 실수로 정규화 하여 표현합니다.

 

따라서 HSV 값을 0~1 사이의 실수 값으로 받고, 출력값 또한 0~1사이의 실수값으로 RGB를 출력합니다.

 

위는 공식이기 때문에 공식을 찾아 그것을 프로그램에서 작동할 수 있도록 옮겨 적은 것이기 때문에 이 과정만 수월하게 한다면 HSV를 RGB로 바꾸는 데는 어려움은 없을것입니다.

 

/* color_bar*/
void color_bar() {
   /* H */
   /* x : ww+400 ~ ww+500 */
   /* y : wh ~ wh - 360 */
   for (int i = 0; i < 360; i++) {
      double* rgbp = hsv_to_rgb(i / 360.0, 1.0, 1.0);
      double r = rgbp[0];
      double g = rgbp[1];
      double b = rgbp[2];

      glColor3f(r, g, b);
      glBegin(GL_QUADS);
      glVertex2f(ww + 500, wh - i);
      glVertex2f(ww + 500, wh - i - 1);
      glVertex2f(ww + 450, wh - i - 1);
      glVertex2f(ww + 450, wh - i);
      glEnd();
   }
}

 

오른쪽에 있는 세로로 긴 생상 선택칸을 color_bar라고 이름붙였다.

다음은 color_bar를 구현하는 방법입니다. 먼저, 100 * 360의 사이즈로 구현했습니다.

HSV 모델에서 색상을 표현하는 범위가 360도이기 때문에 구현에 편리함을 위해 세로 사이즈를 360픽셀로 정했습니다.

 

( 여담이지만 이또한 비율로 구현했다면 임의의 변수 X에 대하여 100X * 360X 값으로 자유자재로 변환할 수 있는 팔레트가 될 수 있었을텐데 아쉽습니다. )

 

먼저 RGB로 표현한 HSV를 구하기 위하여 for문을 돌렸습니다.

i는 0부터 360까지 증가하고 이 값을 360으로 나누어 0~1 사이의 소수로 정규화 시켰습니다.

또한 컬러바에는 색상 자체를 표현해야하기 때문에 S와V는 1값을 준 순수 색인 HSV를 RGB로 변환해서,

HSV 기반의 RGB 컬러를 상단부터 하단까지 360픽셀만큼 쭈욱 출력합니다.

가로 50픽셀, 세로 1픽셀짜리 직사각형을 360개 그리는 방식으로 구현했습니다.

 

/* h = 전역변수로 선언된 hsv 기반의 컬러 값 */

void palette() {
   /* S, V */
   for (int i = 200; i >= 0; i -= 2) {
      for (int j = 200; j >= 0; j -= 2) {
         double* rgbp = hsv_to_rgb(h, j / 200.0, i / 200.0);
         double r = rgbp[0];
         double g = rgbp[1];
         double b = rgbp[2];
         glColor3f(r, g, b);
         glBegin(GL_QUADS);
         glVertex2f(ww + 248 + j, wh - i);
         glVertex2f(ww + 248 + j, wh - i - 2);
         glVertex2f(ww + 248 + j + 2, wh - i - 2);
         glVertex2f(ww + 248 + j + 2, wh - i);
         glEnd();
      }
   }
}

이번엔 왼쪽 팔레트에 대한 구현입니다.

먼저 h값은 전역변수로 선언되어있습니다.

 

if (y >= 640 && x >= wh + 350) {
      draw_mode = 0;
      h = (y - 1000) * (-1.0) / 360.0;
      //printf("%f\n", h);
      palette();
      color_bar();
      return 0;
   }

h값을 구하는 방식은 위와 같은데,

컬러바의 경우 위에서부터 360픽셀만큼 그려져 있고, 0과 1 사이로 정규화 하기 위해 y값에서 1000을 빼고 ( 0~-360의 값을 가지게됨 ) -1을 곱하여 양수로 만든 뒤 , 360으로 나누었습니다.

 

컬러바가 구현되는 방식과 같은 원리로 구한다고 보시면 되겠습니다.

여기서 y = wh - y의 값을 가집니다. 쉽게 표현하자면 y는 2차원 좌표상 마우스의 y좌표라고 보시면 되겠습니다.

 

이 부분은 마우스 클릭 이벤트를 활용해 구현하였습니다. 컬러바에 마우스 클릭을 하게 된다면 그 좌표를 기반으로 h값을 정한다고 생각하시면 됩니다.

 

 else if (y >= 800 && x >= wh + 150) {
      draw_mode = 0;
      double s = (x - 1150) / (200.0);
      double v = (y - 1000) * (-1.0) / (200.0);
      double* rgbp = hsv_to_rgb(h, s, v);
      double nr = rgbp[0];
      double ng = rgbp[1];
      double nb = rgbp[2];
      r = nr;
      g = ng;
      b = nb;
      palette();
      return 0;
   }

컬러바 왼쪽의 팔레트에서 값을 선택하는 기능입니다.

S와 V의 값은 0~100의 값을 가지고 팔레트의 사이즈는 200X200으로 0~1 사이의 값으로 정규화 하기 위해 200을 나누었습니다.

 

이렇게 구해진 값을 기반으로 ( 팔레트 상에 있는 값은 HSV 이므로 ) hsv_to_rbg 함수를 활용해 그리기 위한 rgb 컬러로 만들어 전역변수 rgb를 구한 값으로 초기화합니다.

 

 

크게 구현에 있어서 어려운 부분은 없으나, 

rgp <-> hsv

컬러 변환에 대한 이해만 있다면 쉽게 구현할 수 있을것이라 생각합니다.

이 포스팅 말고도 컬러모델에 관한 자료는 많으니 다른 참고도 참고하시면 좋을 것 같습니다.

CG 과제로 기존의 그림판의 기능을 확장 시키는 과제를 받았다.

 

기존 프로그램 동작

기존 프로그램은 위와 같다. 선, 사각형, 삼각형, 도트, 텍스트 입력 기능을 지원한다.

 

이 프로그램을 기반으로 추가적으로 기능을 구현하고자 한다.

 

프로젝트는 팀단위로 실시하게 되었다.

 

* 도트 기능의 확장

-> 선택한 색으로 나오게 하기

* 별 그리기 기능

* 브러쉬 기능

 

팀원들은 각자의 구현파트가 있고, 내가 추가할 기능은 위의 세가지이다. 

 

* 팔레트 기능

* 윈도우 확장 및 추가기능

 

추가적으로 위의 기능들에 대하여는 내가 개인적으로 하고싶어서 하게 되었다.

 

이번 포스팅에서는 확장기능에 대한 코드를 발췌하여 설명하고, 다음 포스팅에서는 프로그램의 전반적인 동작이 어떻게 이루어지는지에 대하여 포스팅 하겠다.

 

각 항목을 어떻게 프로그램에 적용시킬지에 대한 궁금증을 가졌다면 다음 포스팅을 참고하면 될 것 같다.

 

1. 도트 기능의 확장 

먼저, 현재 도트기능이 어떻게 작동하는지 보기로 했다.

void drawSquare(int x, int y)
{
        y=wh-y;
        glColor3ub((char)rand() % 256, (char)rand() % 256, (char)rand() % 256);
        glBegin(GL_POLYGON);
                glVertex2f(x+size, y+size);
                glVertex2f(x-size, y+size);
                glVertex2f(x-size, y-size);
                glVertex2f(x+size, y-size);
        glEnd();
}

컬러를 랜덤값으로 받는 모습이다.

 

/* 새로 추가한 전역변수 */
int random_color = 0;

double r, g, b; // 기존에 있던 전역변수

void drawSquare(int x, int y)
{
    y = wh - y;
    if (random_color == 1)
        glColor3ub((char)rand() % 256, (char)rand() % 256, (char)rand() % 256);
    else glColor3f(r, g, b);
    glBegin(GL_POLYGON);
    glVertex2f(x + size, y + size);
    glVertex2f(x - size, y + size);
    glVertex2f(x - size, y - size);
    glVertex2f(x + size, y - size);
    glEnd();
}

새로운 전역변수 random_color를 추가하여 if문으로 분기를 나뉘어서 도트 기능을 확장하였다.

 

random_color의 값이 1이면 무작위 색의 도트를 찍고 그렇지 않다면 기존에 있던 r,g,b 값으로 색을 초기화 하여 출력한다.

 

2. 별 그리기 기능

별 그리기 알고리즘

case(STAR):
        {
            rx = x;
            ry = wh - y;
            if(random_color == 0) glColor3f(r, g, b);
            else glColor3ub((char)rand() % 256, (char)rand() % 256, (char)rand() % 256);
            glBegin(GL_POINTS);
            for (double i = 0.0; i < 720; i += 0.1)
                glVertex3f(size * 5 * cos(2 * i) + size * 2.0 * cos(3 * i) + rx, size * 2 * sin(3 * i) - size * 5.0 * sin(2 * i) + ry, 0.0);
            glEnd();
            count = 0;
            break;
        }

별 그리기 기능은 다음과 같이 코딩하였다.

switch문의 분기중 하나를 위와같이 발췌하였다.

 

범위를 지정하는 형식이 아니라 클릭을 하면 별을 그리는 방식으로 구현하였다.

 

별을 그리는 방법은 극좌표 방정식을 사용하였다.

 

출처 : http://lg-sl.net/product/scilab/sciencestorylist/ALMA/readSciencestoryList.mvc?sciencestoryListId=ALMA2018070004&subjectId=MAT 

 

 

초기 버전

rx = x;
            ry = wh - y;
            glBegin(GL_POINTS);
            for (int i = 0.0; i < 720; i++)
                glVertex3f(size*5*cos(2*i)+size*2*cos(3*i)+rx, size*2*sin(3*i)-size*5*sin(2*i)+ry, 0.0);
            glEnd();
            draw_mode = 0;
            count = 0;
            
            break;

위의 코드의 경우 초기 버전의 별그리기다.

 

i의 증가량이 1밖에 되지 않아 별의 크기를 늘리자 별의 선이 이어지지 않고 점으로 끊어지는 현상을 발견하였다.

 

i의 증가량을 0.1로 바꾸어 구현하였더니 첫번째 결과와 같이 선명해지고 선의 끊어짐 현상이 줄어들었다.

3. 브러쉬 기능

브러쉬 기능

void mouseMove(int x, int y) {
    switch (draw_mode) {
    case BRUSH:
            rx = x;
            ry = wh - y;
            glBegin(GL_POLYGON);
            glColor3f(r, g, b);
            for (double i = 0; i < 360; i += 1)
                glVertex2f(size * 5 * cos(i) + rx, size * 5 * sin(i) + ry);
            glEnd();
            break;
	...

 

브러쉬 기능의 경우 고민을 좀 했다.

 

원래 고민 했던 방법은

 

mouse 상태에 따라 마우스 왼쪽이 눌리면 선택한 모드로의 그림이 그려지는 구조인데,

왼쪽이 버튼이 DOWN(눌린)상태에서 UP될때까지 반복문으로 좌표를 받아 드로우 하려고 했으나,

마우스 클릭이 이루어지는 순간만 이벤트로 받아서 그게 잘 안됬다.

 

그래서 찾아보니 moseMove()라는 함수가 있었다.

마우스가 움직이는 동안 좌표를 계속 보내주는 것이다.

 

이를 활용하여 움직이는 경로에 원을 계속적으로 그리는 것으로 브러쉬를 구현하였다.

 

 

초기버전 브러쉬

case BRUSH:
        rx = x;
        ry = wh - y;
        glBegin(GL_POINTS);
        glColor3f(r, b, g);
        glVertex2f(rx, ry);
        glEnd();
         
    }
    glPopAttrib();
    glFlush();

경로상에 GL_POINTS를 활용하여 점을 계속 찍어보자는 생각을 했는데,

굉장히 얇고 마우스 이동이 빨라지면 완전하게 그려지지 않는다는 점을 활용했다.

 

따라서 조금 더 큰 사이즈의 원을 마우스 경로에 지속적으로 그리는 방법으로 구현했다.

4. 팔레트 기능 및 윈도우 확장, 기능 추가

팔레트 기능

이태까지 본 윈도우와 조금 다른 모습을 보일 것이다.

팀 프로젝트로 진행하다 보니 각자의 파트가 있었고, 브러쉬 별 도트 기능 확장까지가 내 파트였다.

이후 지우개와 원 그리기, 배경색 바꾸기가 추가되었는데,

지우개의 경우 브러쉬 컬러를 배경색과 동일하게 맞추어 브러쉬 기능을 하면 되는 것이라 팀원이 구조를 만들고 거기 안에 내가 코딩만 했다.

 

다만, 팔레트 기능의 경우 설명이 길어질 것 같으니 따로 글을 작성하겠다.

 

5. 현재 기능 정리

결과 1

현재 선, 삼각형, 사각형, 점, 문자, 별, 브러쉬(PEN), 지우개, 원 그리기, 팔레트 기능이 구현 되었다.

 

 

구현 메뉴

마우스 우클릭을 할경우 quit, clear 메뉴가 나오고

마우스 휠을 클릭 할 경우 DrawColors, BackgroundColors, OneClickOption, Fill 메뉴가 나오며,

각 하위 메뉴들은 위의 사진과 같다.

6. 정리

생각보다 구현함에 있어서 어려움은 없었다.

특히나 인터넷에 소스코드가 매우 많이 뿌려져 있기 때문에 참고자료가 충분하고, 

 

구현에 있어서 가장 힘들었던 것은 팔레트를 구현하는 것이었다. 이는 다음번에 자세히 다뤄보도록 하겠다.

 

특이한 점은 gl의 문법이 신기했다는 것이다.

glBegin()의 경우 중괄호로 시작되는 것이 아니라 이 함수가 시작된 시점부터 다음 나오는 vertex들이 모두 그리기에 사용되는 vertex로 취급이 된다. 이후 glEnd()가 호출되면 하나의 도형이 완성되는 것이다.

 

 

 

 

+ Recent posts