선그리기
두 점 , 에 대한 직선의 방정식은 다음과 같이 쓸 수 있습니다.
직선의 방정식에 대해 수학적으로 완벽한 선을 그리려면 float 자료형으로 표현해야 합니다. 하지만 컴퓨터 모니터의 픽셀 좌표는 정수형입니다. 좌표를 반올림해서 픽셀을 그리는 방법도 있지만 int 보다 float 을 다루는 것이 느리고 부동 소수점이기 때문에 부정확할 수 있습니다. 컴퓨터 그래픽스에서 흔히 사용하는 브레슨햄 선그리기 알고리즘은 int 자료형만으로 선그리기를 가능케 합니다.
브레슨햄 선그리기 ( 와 의 선택)
브레슨햄 알고리즘은 직선의 기울기가 0과 1 사이일 때를 가정해 정리됩니다. 이후에 다른 가정에서는 변수만 바꾼 후 같은 알고리즘으로 적용가능합니다. 평면 상에 아래와 같은 직선이 그려져 있습니다.
브레슨햄 알고리즘의 핵심은 마킹된 현재 픽셀로부터 다음 픽셀을 어떻게 선택할지의 결정입니다. 기울기가 0과 1 사이이므로 값은 항상 1씩 증가합니다. 그렇다면 매 이터레이션마다 값을 이전과 똑같은 값으로 유지할지 또는 1 증가된 값을 선택할지가 관건이겠습니다. 위 그림에서 가장 왼편 아래 픽셀을 초기값이라 하고 오른쪽으로 갈수록 값이 증가한다고 하겠습니다. 그림의 과 는 실제 직선의 방정식에 대해 각각 와 로부터의 거리라 할 수 있습니다. 브레슨햄 알고리즘은 과 중 더 작은 경우를 찾고 이 더 작을 경우 값을 유지하고 가 더 작을 경우 값을 1 증가시킵니다.
브레슨햄 선그리기 (점화식)
첫 픽셀의 위치를 , 현재 픽셀의 위치를 라 하고 직선의 방정식을 와 같이 쓰겠습니다. 이 때 과 는 다음과 같이 쓸 수 있습니다.
위 식의 값에 직선의 방정식을 대입할 때 과 대응하는 값을 구해야 하므로 를 로 바꿔 씁니다.
과 중 더 큰 값을 구하기 위해서 가 0보다 작은지 큰지를 이용할 수 있습니다.
나눗셈을 포함하고 있는 위 식을 그대로 코드로 옮기면 나눗셈 연산으로 인해 연산속도가 감소할 수 있으므로 식의 양변에 를 곱합니다.
식의 우변에서 은 상수이므로 로 치환해 둡니다. 이제 위 식을 이용해 점화식을 찾아보겠습니다. 을 라 하고 를 구해보겠습니다.
이 때 는 항상 1입니다. 만약 이여서 라면 은 아래와 같이 정리할 수 있습니다.
만약 이라면 이므로 아래와 같이 정리할 수 있습니다.
점화식에 필요한 초깃값은 에 1을 대입해 구할 수 있습니다.
알고리즘을 C 코드로 옮겨 보겠습니다.
typedef struct s_coord
{
int x;
int y;
} t_coord;
void plot_line_low(t_coord *p1, t_coord *p2, int (*mark_pixel)(t_coord *))
{
int dx;
int dy;
int D;
t_coord p;
p = *p1;
dx = p2->x - p1->x;
dy = p2->y - p1->y;
D = 2 * dy - dx;
while (p.x <= p2->x)
{
mark_pixel(&p);
if (D > 0)
{
p.y += 1;
D -= 2 * dx;
}
D += 2 * dy;
p.x++;
}
}
(위 코드에서 mark_pixel
함수는 화면에 직접 마킹하는 함수가 될 수도 있고 단순히 터미널 창에 좌표를 출력하는 함수가 될 수도 있겠습니다.)
브레슨햄 선그리기 (여러 상황에서의 적용)
기울기가 0 과 1 사이인 경우에 대해 점화식과 그 초깃값을 알아보고 코드로 옮겨 보았습니다. 이번에는 기울기가 0 과 -1 사이인 경우를 생각해보겠습니다. 이 경우 직선을 기울기가 0 과 1 사이인 경우로 대체하면( 축 대칭) 반대되는 부호에 상관없이 는 그대로 이용할 수 있습니다. 대신 인 경우 이 됩니다. 이를 앞서 작성했던 코드에 적용해 보겠습니다.
void plot_line_low(t_coord *p1, t_coord *p2, int (*mark_pixel)(t_coord *))
{
int dx;
int dy;
int yi;
int D;
t_coord p;
p = *p1;
dx = p2->x - p1->x;
dy = p2->y - p1->y;
yi = 1;
if (dy < 0 && (yi = -1)) /* important */
dy = -dy; /* important */
D = 2 * dy - dx;
while (p.x <= p2->x)
{
mark_pixel(&p);
if (D > 0)
{
p.y += yi;
D -= 2 * dx;
}
D += 2 * dy;
p.x++;
}
}
이제 기울기가 1 보다 큰 경우를 생각해보겠습니다. 기울기가 1보다 클 경우 값은 항상 1씩 증가하고 값은 이전 픽셀과 같은 값을 유지하거나 1 증가합니다. 따라서 과 를 다음과 같이 쓸 수 있습니다.
위 식을 이용해 를 정리하면 다음과 같습니다.
양변에 를 곱해주면 다음을 얻습니다.
이전과 동일한 절차로 진행하면 다음과 같은 점화식과 초깃값을 얻습니다.
짐작하셨다시피 와 가 모두 전치되었습니다. 아래는 모든 상황에 브레슨햄 알고리즘을 적용한 최종 코드입니다.
# define ABS(N) ((N < 0) ? (-N) : (N))
typedef struct s_coord
{
int x;
int y;
} t_coord;
static void plot_line_low(t_coord *p1, t_coord *p2, int (*mark_pixel)(t_coord *))
{
int dx;
int dy;
int yi;
int D;
t_coord p;
p = *p1;
dx = p2->x - p1->x;
dy = p2->y - p1->y;
yi = 1;
if (dy < 0 && (yi = -1))
dy = -dy;
D = 2 * dy - dx;
while (p.x <= p2->x)
{
mark_pixel(&p);
if (D > 0)
{
p.y += yi;
D -= 2 * dx;
}
D += 2 * dy;
p.x++;
}
}
static void plot_line_high(t_coord *p1, t_coord *p2, int (*mark_pixel)(t_coord *))
{
int dx;
int dy;
int xi;
int D;
t_coord p;
p = *p1;
dx = p2->x - p1->x;
dy = p2->y - p1->y;
xi = 1;
if (dx < 0 && (xi = -1))
dx = -dx;
D = 2 * dx - dy;
while (p.y <= p2->y)
{
mark_pixel(&p);
if (D > 0)
{
p.x += xi;
D -= 2 * dy;
}
D += 2 * dx;
p.y++;
}
}
void plot_line(t_coord *p1, t_coord *p2, int (*mark_pixel)(t_coord *))
{
if (ABS(p2->x - p1->x) > ABS(p2->y - p1->y))
{
if (p2->x > p1->x)
plot_line_low(p1, p2, mark_pixel);
else
plot_line_low(p2, p1, mark_pixel);
}
else
{
if (p2->y > p1->y)
plot_line_high(p1, p2, mark_pixel);
else
plot_line_high(p2, p1, mark_pixel);
}
}