Алгоритм Брезенхема
Уравнение прямой на плоскости
Напомним, что прямая на плоскости — это множество всевозможных точек, координаты \(x,y\) которых удовлетворяют уравнению вида
\[Ax+By+C=0\]
для каких-то \(A,B,C\) (\(A\) и \(B\) не должны быть одновременно равны нулю).
У этого уравнения есть очень простой геометрический смысл. Пусть \(X,Y\) — произвольная точка прямой. Тогда \(C=-AX-BY\). Поэтому уравнение можно переписать в виде
\[A(x-X)+B(y-Y)=0\]
Слева (в предположении ортонормированности системы координат) — скалярное произведение вектора с координатами \(A,B\) и направляющего вектора прямой. Равенство скалярного произведения нулю — перпендикулярность. То есть уравнение прямой дословно говорит о том, что прямая — множество всех точек, радиус-вектор которых (относительно некоторой фиксированной точки) перпендикулярен некоторому фиксированному вектору.
Коэффициенты \(A\) и \(B\) легко вычислить, если знать координаты каких-нибудь двух точек на прямой. Если прямая проходит через точки \(x_0,y_0\) и \(x_1,y_1\), то можно взять
\[ A = y_1 - y_0;\qquad B = x_0 - x_1 \]
или любые другие числа, пропорциональные этой паре.
Изображение прямой
Для того, чтобы изобразить прямую на растровом изображении, нужно пометить все пиксели, достаточно близкие к этой прямой (из-за того, что растровое изображение представляет собой целочисленную решётку, уравнению прямой точно удовлетворяет довольно мало пикселей).
В качестве критерия близости пикселя к прямой можно использовать результат его подстановки в левую часть рассмотренного выше уравнения прямой: этот результат является (если вспомнить геометрический смысл скалярного произведения) расстоянием от пикселя до прямой (нормированным величиной \(\sqrt{A^2+B^2}\)).
Именно такой критерий использует алгоритм Брезенхема, строящий отрезок между парой заданных точек. Этот алгоритм (точнее, одну из многочисленных его вариаций) можно грубо описать так:
- Выбираем два перпендикулярных напраления, направленных от начальной точки отрезка в сторону конечной. Например, вправо и вверх.
- Закрашиваем начальный пиксель.
- Создаём переменную, хранящую значение левой части уравнения для текущего пикселя. Её начальное значение — ноль.
- Пока мы не попали в конечную точку, делаем шаг в одном из выбранных в пункте 1 направлений в зависимости от знака переменной из пункта 3 (или её близости к нулю). Соответствующим образом обновляем эту переменную и красим текущий пиксель.
Основное достоинство этого алгоритма — он вообще не использует никакой арифметики, кроме сложения и сравнения с нулём.
Приведём две различных реализации этого алгоритма. Первая реализация работает при условии того, что конечная точка строго правее начальной и не ниже её.
def mark_pixel(image, x, y):
put_pixel(image, x, y, 255, 255, 255)
def draw_line(image, x0, y0, x1, y1):
y = y0
error = 0
A = y1-y0
B = x0-x1
for x in range(x0, x1+1):
mark_pixel(image, x, y)
while error > 0:
y += 1
error += B
mark_pixel(image, x, y)
x += 1
error += A
mark_pixel(image, x, y)
Вторая реализация более полноценна и работает всегда:
def sign(x):
if x > 0: return 1
if x < 0: return -1
return 0
def draw_line(image, x0, y0, x1, y1):
x,y = x0,y0
error = 0
A = y1-y0
B = x0-x1
dx, dy = -sign(B), sign(A)
while x != x1 or y != y1:
mark_pixel(image, x, y)
ex = error + A*dx
ey = error + B*dy
if abs(ex) < abs(ey):
x += dx
error = ex
else:
y += dy
error = ey
mark_pixel(image, x, y)