Алгоритм брезенхема для построения отрезка. Алгоритм Брезенхема построения окружности

Поскольку экран LCD-дисплея можно рассматривать как матрицу дискретных элементов (пикселей), каждый из которых может быть подсвечен, нельзя непосредственно провести отрезок из одной точки в другую. Процесс определения пикселей, наилучшим образом аппроксимирующих заданный отрезок, называется разложением в растр. В сочетании с процессом построчной визуализации изображения он известен как преобразование растровой развертки. Для горизонтальных, вертикальных и наклоненных под углом 45°. отрезков выбор растровых элементов очевиден. При любой другой ориентации выбрать нужные пиксели труднее, что и показано на рис.1.

Рис.1. Разложение в растр отрезков прямых.

Общие требования к алгоритмам вычерчивания отрезков следующие: Отрезки должны выглядеть прямыми, начинаться и заканчиваться в заданных точках, яркость вдоль отрезка должна быть постоянной и не зависеть от длины и наклона, рисовать нужно быстро.

Постоянная вдоль всего отрезка яркость достигается лишь при проведении горизонтальных, вертикальных и наклоненных под углом 45° прямых. Для всех других ориентаций разложение в растр приведет к неравномерности яркости, как это показано на рис. 1.

В большинстве алгоритмов вычерчивания отрезков для упрощения вычислений используется пошаговый алгоритм. Приведем пример подобного алгоритма:

Простой пошаговый алгоритм

позиция = начало

шаг = приращение

1. if позиция - конец < точность then 4

if позици > конец then 2

if позиция < конец then 3

2. позиция = позиция - шаг

3. позиция = позиция + шаг

4. finish

Алгоритм Брезенхема.

Хотя алгоритм Брезенхема был первоначально разработан для цифровых графопостроителей, однако он в равной степени подходит для LCD-монитора. Алгоритм выбирает оптимальные растровые координаты для представления отрезка. В процессе работы одна из координат - либо x, либо y (в зависимости от углового коэффициента) - изменяется на единицу. Изменение другой координаты (на 0 или 1) зависит от расстояния между действительным положением отрезка и ближайшими координатами сетки. Такое расстояние мы назовем ошибкой.

Алгоритм построен так, что требуется проверить лишь знак этой ошибки. На рис.2 это иллюстрируется для отрезка в первом октанте, т.е. для отрезка с угловым коэффициентом, лежащим в диапазоне от 0 до 1. Из рисунка можно заметить, что если угловой коэффициент отрезка из точки (0,0) больше, чем 1/2, то пересечение с прямой x = 1 будет расположено ближе к прямой y = 1, чем к прямой y = 0. Следовательно, точка растра (1,1) лучше аппроксимирует ход отрезка, чем точка (1,0). Если угловой коэффициент меньше 1/2, то верно обратное. для углового коэффициента, равного 1/2, нет какого либо предпочтительного выбора. В данном случае алгоритм выбирает точку (1,1).

Рис. 2. Основная идея алгоритма Брезенхема.

Не все отрезки проходят через точки растра. Подобная ситуация иллюстрируется рис.3, где отрезок с тангенсом угла наклона 3/8 сначала походит через точку растра (0,0) и последовательно пересекает три пикселя. Также иллюстрируется вычисление ошибки при представлении отрезка дискретными пикселями.

Рис.3. График ошибки в алгоритме Брезенхема.

Так как желательно проверять только знак ошибки, то она первоначально устанавливается равной -1/2. Таким образом, если угловой коэффициент отрезка больше или равен 1/2, то величина ошибки в следующей точке растра с координатами (1,0) может быть вычислена как

e = e + m

где m - угловой коэффициент. В нашем случае при начальном значении ошибки -1/2

e = 1/2 + 3/8 = -1/8

Так как е отрицательно, отрезок пройдет ниже середины пикселя. Следовательно, пиксел на том же самом горизонтальном уровне лучше аппроксимирует положение отрезка, поэтому у не увеличивается. Аналогично вычисляем ошибку

e = -1/8 + 3/8 = 1/4

в следующей точке растра (2,0). Теперь е положительно, значит отрезок пройдет выше средней точки. Растровый элемент (2,1) со следующей по величине координатой у лучше аппроксимирует положение отрезка. Следовательно у увеличивается на 1. Прежде чем рассматривать следующий пиксель, необходимо откорректировать ошибку вычитанием из нее 1. Имеем

e = 1/4 - 1 = -3/4

Заметим, что пересечение вертикальной прямой x = 2 с заданным отрезком лежит на 1/4 ниже прямой у = 1. Если же перенести отрезок 1/2 вниз, мы получим как раз величину -3/4. Продолжение вычислений для следующего пикселя дает

e = -3/4 + 3/8 = -3/8

Так как е отрицательно, то у не увеличивается. Из всего сказанного следует, что ошибка - это интервал, отсекаемый по оси у рассматриваемым отрезком в каждом растровом элементе (относительно -1/2).

Приведем алгоритм Брезенхема для первого октанта, т.е. для случая 0 =< y =< x.

Алгоритм Брезенхема разложения в растр отрезка для первого октанта

Integer - функция преобразования в целое

x, y, x, y - целые

е - вещественное

инициализация переменных

Инициализация с поправкой на половину пикселя

начало основного цикла

while (e => 0)

Блок-схема алгоритма приводится на рис.4.

Рис.4. Блок-схема алгоритма Брезенхема.

Пример алгоритма Брезенхема.

Рассмотрим отрезок проведенный из точки (0,0) в точку (5,5). Разложение отрезка в растр по алгоритму Брезенхема приводит к такому результату:

начальные установки

е = 1 - 1/2 = 1/2

Результат показан на рис.5 и совпадает с ожидаемым. Заметим, что точка растра с координатами (5,5) не активирована. Эту точку можно активировать путем изменения цикла for-next на 0 to x. Активацию точки (0,0) можно устранить, если поставить оператор Plot непосредственно перед строкой next i.

Рис. 5. Результат работы алгоритма Брезенхема в первом октанте.

Общий алгоритм Брезенхема.

Чтобы реализация алгоритма Брезенхема была полной необходимо обрабатывать отрезки во всех октантах. Модификацию легко сделатть, учитывая в алгоритме номер квадранта, в котором лежит отрезок и его угловой коэффициепт. Когда абсолютная величина углового коэффициента больше 1, у постоянно изменяется на единицу, а критерий ошибки Брезенхема используется для принятия решения об изменении величины x . Выбор постоянно изменяющейся (на +1 или -1) кооординаты зависит от квадранта (рис.6.). Общий алгоритм может быть оформлен в следующем виде:

Обобщенный целочисленный алгоритм Брезенхема квадрантов

предполагается, что концы отрезка (x1,y1) и (x2,y2) не совпадают

все переменные считаются целыми

Sign - функция, возвращающая -1, 0, 1 для отрицательного, нулевого и положительного аргумента соответственно

инициализация переменных

x = abs(x2 - x1)

y = abs(y2 - y1)

s1 = Sign (x2 - x1)

s2 = Sign (y2 - y1)

обмен значений x и y в зависимости от углового коэффициента наклона отрезка

if y < x then

end if

инициализация е с поправкой на половину пикселя

основной цикл

for i = 1 to x

Plot (x,y)

while (е =>0)

if Обмен = 1 then

end while

if Обмен = 1 then


Рис.6. Разбор случаев для обобщенного алгоритма Брезенхема.

Пример. Обобщенный алгоритм Брезенхема.

Для иллюсрации рассмотрим отрезок из точки (0,0) в точку (-8, -4).

начальные установки

результаты работы пошагового цикла

Рис.7. Результат работы обобщенного алгоритма Брезенхема в третьем квадранте.

На рис. 7 продемонстрирован результат. Сравнение с рис. 5 показывает, что результаты работы двух алгоритмов отличаются.

В следующем разделе рассматривается алгоритм Брезенхема для генерации окружности.

Алгоритм Брезенхема для генерации окружности.

В растр нужно разлагать не только линейные, но и другие, более сложные функции. Разложению конических сечений, т. е. окружностей, эллипсов, парабол, гипербол, было посвящено значительное число работ. Наибольшее внимание, разумеется, уделено окружности. Один из наиболее эффективных и простых для понимания алгоритмов генерации окружности принадлежит Брезенхему. Для начала заметим, что необходимо сгенерировать только одну восьмую часть окружности. Остальные ее части могут быть получены последовательными отражениями, как это показано на рис. 8. Если сгенерирован первый октант (от 0 до 45° против часовой стрелки), то второй октант можно получить зеркальным отражением относительно прямой у = х, что дает в совокупности первый квадрант. Первый квадрант отражается относительно прямой х = 0 для получения соответствующей части окружности во втором квадранте. Верхняя полуокружность отражается относительно прямой у = 0 для завершения построения. На рис. 8 приведены двумерные матрицы соответствующих преобразований.

Рис. 8. Генерация полной окружности из дуги в первом октанте.

Для вывода алгоритма рассмотрим первую четверть окружности с центром в начале координат. Заметим, что если работа алгоритма начинается в точке х = 0, у = R, то при генерации окружности по часовой стрелке в первом квадранте у является монотонно убывающей функцией аргументам (рис. 9). Аналогично, если исходной точкой является у = 0, х = R, то при генерации окружности против часовой стрелки х будет монотонно убывающей функцией аргумента у. В нашем случае выбирается генерация по часовой стрелке с началом в точке х = 0, у = R. Предполагается, что центр окружности и начальная точка находятся точно в точках растра.

Для любой заданной точки на окружности при генерации по часовой стрелке существует только три возможности выбрать следующий пиксел, наилучшим образом приближающий окружность: горизонтально вправо, по диагонали вниз и вправо, вертикально вниз. На рис. 10 эти направления обозначены соответственно m H , m D , m V . Алгоритм выбирает пиксел, для которого минимален квадрат расстояния между одним из этих пикселей и окружностью, т. е. минимум из

m H = |(x i + 1) 2 + (y i) 2 -R 2 |

m D = | |

m V = |(x i) 2 + (y i -1) 2 -R 2 |

Вычисления можно упростить, если заметить, что в окрестности точки (xi,yi,) возможны только пять типов пересечений окружности и сетки растра, приведенных на рис. 11.

Рис. 11. Пересечение окружности и сетки растра.

Разность между квадратами расстояний от центра окружности до диагонального пикселя (x i , + 1, у i - 1) и от центра до точки на окружности R 2 равна

d i = (x i + 1) 2 + (y i -1) 2 -R 2

Как и в алгоритме Брезенхема для отрезка, для выбора соответствующего пикселя желательно использовать только знак ошибки, а не ее величину.

При d i < 0 диагональная точка (x i , + 1, у i - 1) находится внутри реальной окружности, т. е. это случаи 1 или 2 на рис. 11. Ясно, что в этой ситуации следует выбрать либо пиксел (x i , + 1, у i), т. е. m H , либо пиксел (x i , + 1, у i - 1), т. е. m D . Для этого сначала рассмотрим случай 1 и проверим разность квадратов расстояний от окружности до пикселей в горизонтальном и диагональном направлениях:

d = |(x i + 1) 2 + (y i) 2 -R 2 | - |(x i + 1) 2 + (y i -1) 2 -R 2 |

При d < 0 расстояние от окружности до диагонального пикселя больше, чем до горизонтального. Напротив, если d > 0, расстояние до горизонтального пикселя больше. Таким образом,

при d <= 0 выбираем m H в (x i , + 1, у i - 1)

при d > 0 выбираем m D в (x i , + 1, у i - 1)

При e = 0, когда расстояние от окружности до обоих пикселей одинаковы, выбираем горизонтальный шаг.

Количество вычислений, необходимых для оценки величины e, можно сократить, если заметить, что в случае 1

(x i + 1) 2 + (y i) 2 -R 2 >= 0

(x i + 1) 2 + (y i -1) 2 -R 2 < 0

так как диагональный пиксел (x i , + 1, у i - 1) всегда лежит внутри окружности, а горизонтальный (x i , + 1, у i) - вне ее. Таким образом, e можно вычислить по формуле

d = (x i + 1) 2 + (y i) 2 -R 2 + (x i + 1) 2 + (y i -1) 2 -R 2

Дополнение до полного квадрата члена (y i) 2 с помощью добавления и вычитания - 2y i + 1 дает

d = 2[(x i + 1) 2 + (y i -1) 2 -R 2 ] + 2y i - 1

В квадратных скобках стоит по определению e i и его подстановка

d = 2(e i + y i) - 1

существенно упрощает выражение.

Рассмотрим случай 2 на рис. 11 и заметим, что здесь должен быть выбран горизонтальный пиксел (x i , + 1, у i), так как у является монотонно убывающей функцией. Проверка компонент e показывает, что

(x i + 1) 2 + (y i) 2 -R 2 < 0

(x i + 1) 2 + (y i -1) 2 -R 2 < 0

поскольку в случае 2 горизонтальный (x i , + 1, у i) и диагональный (x i , + 1, у i -1) пиксели лежат внутри окружности. Следовательно, d < 0, и при использовании того же самого критерия, что и в случае 1, выбирается пиксел (x i , + 1, у i).

Если e i > 0, то диагональная точка (x i , + 1, у i -1) находится вне окружности, т. е. это случаи 3 и 4 на рис. 11. В данной ситуации ясно, что должен быть выбран либо пиксел (x i , + 1, у i -1), либо (x i , у i -1). Аналогично разбору предыдущего случая критерий выбора можно получить, рассматривая сначала случай 3 и проверяя разность между квадратами расстояний от окружности до диагонального m D и вертикального m V пикселей,

т. е. d" = |(x i + 1) 2 + (y i -1) 2 -R 2 | - |(x i) 2 + (y i -1) 2 -R 2 |

При d" < 0 расстояние от окружности до вертикального пикселя (x i , у i -1) больше и следует выбрать диагональный шаг к пикселу (x i , + 1, у i -1). Напротив, в случае d" > 0 расстояние от окружности до диагонального пикселя больше и следует выбрать вертикальное движение к пикселу (x i , у i -1). Таким образом,

при d" <= 0 выбираем m D в (x i +1, у i -1)

при d" > 0 выбираем m V в (x i , у i -1)

Здесь в случае d" = 0, т. е. когда расстояния равны, выбран диагональный шаг.

Проверка компонент e" показывает, что

(x i) 2 + (y i -1) 2 -R 2 >= 0

(x i + 1) 2 + (y i -1) 2 -R 2 < 0

поскольку для случая 3 диагональный пиксел (x i +1, у i -1) находится вне окружности, тогда как вертикальный пиксел (x i , у i -1) лежит внутри ее. Это позволяет записать e" в виде

d" = (x i +1) 2 + (y i -1) 2 -R 2 + (x i) 2 + (y i -1) 2 -R 2

Дополнение до полного квадрата члена (x i) 2 с помощью добавления и вычитания 2x i + 1 дает

d" = 2[(x i +1) 2 + (y i -1) 2 -R 2 ] - 2x i - 1

Использование определения d i приводит выражение к виду

d" = 2(e i - x i )- 1

Теперь, рассматривая случай 4, снова заметим, что следует выбрать вертикальный пиксел (x i , у i -1), так как у является монотонно убывающей функцией при возрастании х.

Проверка компонент d" для случая 4 показывает, что

(x i +1) 2 + (y i -1) 2 -R 2 > 0

(x i) 2 + (y i -1) 2 -R 2 > 0

поскольку оба пикселя находятся вне окружности. Следовательно, e" > 0 и при использовании критерия, разработанного для случая 3, происходит верный выбор m V .

Осталось проверить только случай 5 на рис. 11, который встречается, когда диагональный пиксел (x i , у i -1) лежит на окружности, т. е. d i = 0. Проверка компонент e показывает, что

(x i +1) 2 + (y i) 2 -R 2 > 0

Следовательно, d > 0 и выбирается диагональный пиксел (x i +1 , у i -1) . Аналогичным образом оцениваем компоненты d" :

(x i +1) 2 + (y i -1) 2 -R 2 = 0

(x i +1) 2 + (y i -1) 2 -R 2 < 0

и d" < 0, что является условием выбора правильного диагонального шага к (x i +1 , у i -1) . Таким образом, случай d i = 0 подчиняется тому же критерию, что и случай d i < 0 или d i > 0. Подведем итог полученных результатов:

d <= 0 выбираем пиксел (x i +1 , у i) - m H

d > 0 выбираем пиксел (x i +1 , у i -1) - m D

d" <= 0 выбираем пиксел (x i +1 , у i -1) - m D

d" > 0 выбираем пиксел (x i , у i -1)- m V

d i = 0 выбираем пиксел (x i +1 , у i -1) - m D

Легко разработать простые рекуррентные соотношения для реализации пошагового алгоритма. Сначала рассмотрим горизонтальный шаг m H к пикселу (x i + 1, у i). Обозначим это новое положение пикселя как (i + 1). Тогда координаты нового пикселя и значение e i равны

d i +1 = (x i +1 +1) 2 + (y i +1 -1) 2 -R 2 dd i + 2x i +1 + 1

Аналогично координаты нового пикселя и значение d i для шага m D к пикселу (x i + 1, у i -1) таковы:

d i+1 = d i + 2x i+1 - 2y i+1 +2

То же самое для шага m V к (x i , у i -1)

d i+1 = d i - 2y i+1 +1

Реализация алгоритма Брезенхема на псевдокоде для окружности приводится ниже.

Пошаговый алгоритм Брезенхема для генерации окружности в первом квадранте

все переменные - целые

инициализация переменных

Предел = 0

1 Plot (x i , y i )

if y i <= Предел then 4

Выделение случая 1 или 2, 4 или 5, или 3

if D i < 0 then 2

if D > 0 then 3

if D i = 0 then 20

определение случая 1 или 2

2 d = 2d i + 2у i - 1

if d <= 0 then 10

if d > 0 then 20

определение случая 4 или 5

3 d = 2D i + 2х i - 1

if d <= 0 then 20

if d > 0 then 30

выполнение шагов

10 х i = х i + 1

D i = D i + 2х i + 1

g о to 1

20 х i = х i + 1

D i = D i + 2х i - 2у i + 2

gо to 1

4 finish

Переменная предела устанавливается в нуль для окончания работы алгоритма на горизонтальной оси, в результате генерируется окружность в первом квадранте. Если необходим лишь один из октантов, то второй октант можно получить с помощью установки Предел = Integer (R/sqrt(2)), а первый - с помощью отражения второго октанта относительно прямой у = х (рис. 8). Блок-схема алгоритма приводится на рис. 12.

Рис. 12. Блок-схема пошагового алгоритма Брезенхема для генерации окружности в первом квадранте.

Кривая Безье и ее геометрический алгоритм.

Кривые Безье были разработаны в 60 – х годах XX века независимо друг от друга Пьером Безье (Bezier) из автомобилестроительной компании «Рено» и Полем де Кастелье (de Casteljau) из компании «Ситроен», где применялись для проектирования кузовов автомобилей.

Несмотря на то, что открытие де Кастелье было сделано несколько ранее Безье (1959), его исследования не публиковались и скрывались компанией как производственная тайна до конца 1960 – х.

Впервые кривые были представлены широкой публике в 1962 году французским инженером Пьером Безье, который, разработав их независимо от де Кастелье, использовал их для компьютерного проектирования автомобильных кузовов. Кривые были названы именем Безье, а именем де Кастелье назван разработанный им рекурсивный способ определения кривых (алгоритм де Кастелье).

Впоследствии это открытие стало одним из важнейших инструментов систем автоматизированного проектирования и программ компьютерной графики.

Кривая Безье – параметрическая кривая, задаваемая выражением

, 0 < t <1

где – функция компонент векторов опорных вершин, а – базисные функции кривой Безье, называемые также полиномами Бернштейна.

где n – степень полинома, i – порядковый номер опорной вершины.

Виды кривых Безье

Линейные кривые

При n = 1 кривая представляет собой отрезок прямой линии, опорные точки P0 и P1 определяют его начало и конец.

Кривая задаётся уравнением:

,

Квадратичные кривые

(n = 2) задаётся 3 – мя опорными точками: P0, P1 и P2.

Квадратичные кривые Безье в составе сплайнов используются для описания формы символов в шрифтах TrueType и в SWF файлах.

Кубические кривые

(n = 3) описывается следующим уравнением:

Четыре опорные точки P0, P1, P2 и P3, заданные в 2 – х или 3 – мерном пространстве определяют форму кривой.

Линия берёт начало из точки P0 направляясь к P1 и заканчивается в точке P3 подходя к ней со стороны P2. То есть кривая не проходит через точки P1 и P2, они используются для указания её направления. Длина отрезка между P0 и P1 определяет, как скоро кривая повернёт к P3.

В матричной форме кубическая кривая Безье записывается следующим образом:

,

где называется базисной матрицей Безье:

В современных графических системах, таких как PostScript, Metafont и GIMP для представления криволинейных форм используются сплайны Безье, составленные из кубических кривых.

Применение в компьютерной графике

Благодаря простоте задания и манипуляции, кривые Безье нашли широкое применение в компьютерной графике для моделирования гладких линий. Кривая целиком лежит в выпуклой оболочке своих опорных точек. Это свойство кривых Безье с одной стороны значительно облегчает задачу нахождения точек пересечения кривых (если не пересекаются выпуклые оболочки, то не пересекаются и сами кривые), а с другой стороны позволяет визуализировать кривую с помощью её опорных точек. Кроме того, аффинные преобразования кривой (перенос, масштабирование, вращение) также могут быть осуществлены путём применения соответствующих трансформаций к опорным точкам.

Наибольшее значение имеют кривые Безье второй и третьей степеней (квадратичные и кубические). Кривые высших степеней при обработке требуют большего объёма вычислений и для практических целей используются реже. Для построения сложных по форме линий отдельные кривые Безье могут быть последовательно соединены друг с другом в сплайн Безье. Для того, чтобы обеспечить гладкость линии в месте соединения двух кривых, смежные опорные точки обеих кривых должны лежать на одной линии. В программах векторной графики наподобие Adobe Illustrator или Inkscape подобные фрагменты известны под названием «путей» (path).

Геометрический алгоритм для кривой Безье

Этот алгоритм позволяет вычислить координаты (х, у) точки кривой Безье по значению параметра t .

1. Каждая сторона контура многоугольника, проходящего по точкам – ориентирам, делится пропорционально значению t .

2. Точки деления соединяются отрезками прямых и образуют новый многоугольник. Количество узлов нового контура на единицу меньше, чем количество узлов предыдущего контура.

3. Стороны нового контура снова делятся пропорционально значению t . И так далее. Это продолжается до тех пор, пока не будет получена единственная точка деления. Эта точка и будет точкой кривой Безье.

Приведем запись геометрического алгоритма на языке С + + :

for (i = 0; i < = m; i + +)

R[ i] = P[ i]; / / формируем вспомогательный массив R

for (j = m; i > 0; i – –)

for (i = 0; i < j; i + +)

R[ i] = R[ i] + t * (R[ i + 1] – R[ i]);

Результат работы алгоритма – координаты одной точки кривой Безье записываются в R.

Модели описания поверхностей. Аналитическая модель.

Аналитической моделью называется описание поверхности математическими формулами:

z = f(x,y) – описание с помощью функции,

F(x,y,z) = 0 – описание с помощью неявного уравнения.

Зачастую используется параметрическая форма описания поверхности:

где s и t – параметры, которые изменяются в определенном диапазоне, а функции Fx, Fy и Fz определяют форму поверхности.

Преимущество параметрической формы заключается в легкости описания поверхностей, которые отвечают неоднозначным функциям, и замкнутых поверхностей.

Параметрическое описание можно задать таким образом, что формула не будет существенно изменяться (усложняться) при поворотах поверхности, и ее масштабировании.

В качестве примера рассмотрим аналитическое описание поверхности шара.

– явная функция двух аргументов,

– неявное уравнение,

x = R sin s cos t, y = R sin s sin t, z = R cos s – в параметрической форме.

Аналитическая модель наиболее пригодна для многих операций анализа поверхностей.

Достоинства модели (с позиций КГ):

  • легкость расчета координат каждой точки поверхности, нормали;
  • небольшой объем данных для описания достаточно сложных форм.

Недостатки:

  • сложность формул описания с использованием функций, которые медленно вычисляются на компьютере, снижают скорость выполнения операций отображения;
  • невозможность в большинстве случаев применить данную форму описания непосредственно для изображения поверхности – поверхность отображается как многогранник, координаты вершин и граней которого рассчитываются в процессе отображения, что уменьшает скорость сравнительно с полигональной моделью описания.

Модель поверхности «равномерная сетка».

Эта модель описывает координаты отдельных точек поверхности следующим способом. Каждому узлу сетки с индексами (i, j) приписывается значение высоты zij. Индексам (i, j) отвечают определенные значения координат (x, y). Расстояние между узлами одинаковое – dx по оси x, dy по оси y. Фактически такая модель – это двумерный массив, растр, матрица, каждый элемент которой сохраняет значение высоты.

Не каждая поверхность может быть представлена этой моделью. Если в каждом узле (i, j) записывается только одно значение высоты, то это означает, что поверхность описывается однозначной функцией z = f (x, y). Иначе говоря, это такая поверхность, которую каждая вертикаль пересекает только один раз. Не могут моделироваться также вертикальные грани. Необходимо заметить, что сетка может быть задана не только в декартовых координатах. Например, для того чтобы описать поверхность шара однозначной функцией, можно использовать полярные координаты. С помощью равномерной сетки часто описывают рельеф земной поверхности.

Положительные черты равномерной сетки:

  • простота описания поверхностей;
  • возможность быстро узнать высоту любой точки поверхности простой интерполяцией.

Недостатки равномерной сетки:

  • поверхности, которые соответствуют неоднозначной функции высоты в узлах сетки, не могут быть смоделированы;
  • для описания сложных поверхностей необходимо большое количество узлов, которое может быть ограничено объемом памяти компьютера.

Модель поверхности «неравномерная сетка».

Неравномерной сеткой называется модель описания поверхности в виде множества отдельных точек {(x0, y0, z0), (x1, y1, z1), …,(xn – 1, yn – 1, zn – 1)}, принадлежащих поверхности. Эти точки могут быть получены, например, в результате измерений поверхности какого – нибудь объекта с помощью определенного оборудования. Такую модель можно считать обобщением для некоторых рассмотренных выше моделей. Например, векторная полигональная модель и равномерная сетка могут считаться разновидностями неравномерной сетки.

Рассмотрим модель поверхности в виде множества точечных значений, логически никак не связанных между собой. Неравномерность задания опорных точек усложняет определение координат для других точек поверхности, которые не совпадают с опорными точками. Требуются специальные методы пространственной интерполяции.

Пусть задача заключается в вычислении значения координаты z по известным координатам (x, y). Для этого необходимо найти несколько самых близких точек, а затем вычислить искомое значение z, исходя из взаимного расположения этих точек в проекции (x, y). Для равномерной сетки эта задача решается достаточно просто – поиска фактически нет, сразу рассчитываются индексы самых близких опорных точек.

Вторая задача заключается в отображении (визуализации) поверхности. Эту задачу можно решать несколькими способами. Один из наиболее распространенных – триангуляция.

Процесс триангуляции может быть представлен следующим образом:

  • находим первые три самые близкие друг к другу точки – получаем одну плоскую треугольную грань;
  • находим точку, ближайшую к этой грани, и образовываем смежную грань, и т.д., пока не останется ни одной отдельной точки.

Алгоритм Брезенхема был предложен Джеком Е. Брезенхэмом (Jack E. Bresenham) в 1962 году и предназначен для рисования фигур точками на плоскости. Этот алгоритм находит широкое распространение в машинной графике для рисования линий на экране. Алгоритм определяет, какие точки двумерного растра необходимо закрасить.

Графическая интерпретация алгоритма Брезенхема представлена на рисунке.

Для рисования прямых отрезков на плоскости с использованием алгоритма Брезенхема запишем уравнение прямой в общем виде

f(x,y)=Ax+By+C=0

где коэффициенты A и B выражаются через коэффициенты k и b уравнения прямой. Если прямая проходит через две точки с координатами (x1 ;y1 ) и (x2 ;y2 ) , то коэффициенты уравнения прямой определяются по формулам

A=y2-y1
B=x1-x2
C=y1∙x2-y2∙x1

Для любой растровой точки с координатами (xi ;yi ) значение функция

  • f(xi,yi) =0 если точка лежит на прямой
  • f(xi,yi) >0 если точка лежит ниже прямой
  • f(xi,yi) где i – номер отображаемой точки.

Таким образом, одним из методов решения того, какая из точек P или Q (см. рисунок) будет отображена на следующем шаге, является сравнение середины отрезка |P-Q| со значением функции f(x,y) . Если значение f(x,y) лежит ниже средней точки отрезка |P-Q| , то следующей отображаемой точкой будет точка P , иначе - точка Q .
Запишем приращение функции

∆f=A∆x+B∆y

После отображения точки с координатами (xi,yi) принимается решение о следующей отображаемой точке. Для этого сравниваются приращения Δx и Δy , характеризующие наличие или отсутствие перемещения по соответствующей координате. Эти приращения могут принимать значения 0 или 1. Следовательно, когда мы перемещаемся от точки вправо,

когда мы перемещаемся от точки вправо и вниз, то

∆f=A+B ,

когда мы перемещаемся от точки вниз, то

Нам известны координаты начала отрезка, то есть точки, заведомо лежащей на искомой прямой. Ставим туда первую точку и принимаем f = 0 . От текущей точки можно сделать два шага - либо по вертикали (по горизонтали), либо по диагонали на один пиксель.
Направление движения по вертикали или горизонтали определяется коэффициентом угла наклона. В случае если угол наклона меньше 45º, и

|A|<|B|

с каждым шагом осуществляется движение по горизонтали или диагонали.
Если угол наклона больше 45º, с каждым шагом движение осуществляется вертикали или диагонали.
Таким образом, алгоритм рисования наклонного отрезка следующий:

Реализация на C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

#include
using namespace std;
void Brezenhem(char **z, int x0, int y0, int x1, int y1)
{
int A, B, sign;
A = y1 - y0;
B = x0 - x1;
if (abs(A) > abs(B)) sign = 1;
else sign = -1;
int signa, signb;
if (A < 0) signa = -1;
else signa = 1;
if (B < 0) signb = -1;
else signb = 1;
int f = 0;
z = "*" ;
int x = x0, y = y0;
if (sign == -1)
{
do {
f += A*signa;
if (f > 0)
{
f -= B*signb;
y += signa;
}
x -= signb;
z[y][x] = "*" ;
}
else
{
do {
f += B*signb;
if (f > 0) {
f -= A*signa;
x -= signb;
}
y += signa;
z[y][x] = "*" ;
} while (x != x1 || y != y1);
}
}
int main()
{
const int SIZE = 25; // размер поля
int x1, x2, y1, y2;
char **z;
z = new char *;
for (int i = 0; i < SIZE; i++)
{
z[i] = new char ;
for (int j = 0; j < SIZE; j++)
z[i][j] = "-" ;
}
cout << "x1 = " ; cin >> x1;
cout << "y1 = " ; cin >> y1;
cout << "x2 = " ; cin >> x2;
cout << "y2 = " ; cin >> y2;
Brezenhem(z, x1, y1, x2, y2);
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
cout << z[i][j];
cout << endl;
}
cin.get(); cin.get();
return 0;
}


Результат выполнения



Алгоритм Брезенхема также может применяться в задачах управления, например, для регулирования мощности или скорости вращения. При этом горизонтальной осью является ось времени, а заданное значение устанавливает коэффициент угла наклона прямой.

Алгоритм Брезенхе́ма- это алгоритм, определяющий, какие точки двумерного растра нужно закрасить, чтобы получить близкое приближение прямой линии между двумя заданными точками.

Отрезок проводится между двумя точками - (x0,y0) и (x1,y1), где в этих парах указаны колонка и строка, соответственно, номера которых растут вправо и вниз. Сначала мы будем предполагать, что наша линия идёт вниз и вправо, причём горизонтальное расстояние x1 − x0 превосходит вертикальное y1 − y0, т.е. наклон линии от горизонтали - менее 45°. Наша цель состоит в том, чтобы для каждой колонки x между x0 и x1, определить, какая строка y ближе всего к линии, и нарисовать точку (x,y).

Общая формула линии между двумя точками:

Поскольку мы знаем колонку x, то строка y получается округлением к целому следующего значения:

Однако, вычислять точное значение этого выражения нет необходимости. Достаточно заметить, что y растёт от y0 и за каждый шаг мы добавляем к x единицу и добавляем к y значение наклона

которое можно вычислить заранее. Более того, на каждом шаге мы делаем одно из двух: либо сохраняем тот же y, либо увеличиваем его на 1.

Что из этих двух выбрать - можно решить, отслеживая значение ошибки, которое означает - вертикальное расстояние между текущим значением y и точным значением y для текущего x. Всякий раз, когда мы увеличиваем x, мы увеличиваем значение ошибки на величину наклона s, приведённую выше. Если ошибка превысила 0.5, линия стала ближе к следующему y, поэтому мы увеличиваем y на единицу, одновременно уменьшая значение ошибки на 1. В реализации алгоритма, приведённой ниже, plot(x,y) рисует точку, а abs возвращает абсолютную величину числа:

function line(x0, x1, y0, y1)

int deltax:= abs(x1 - x0)

int deltay:= abs(y1 - y0)

real error:= 0

real deltaerr:= deltay / deltax

int y:= y0

for x from x0 to x1

error:= error + deltaerr

if error >= 0.5

error:= error - 1.0

Пусть начало отрезка имеет координаты (X 1 ,Y 1), а конец(X 1 ,X 2) . Обозначим

Dx=(X 2 -X 1),dy=(Y 2 -Y 1) . Не нарушая общности, будем считать, что начало отрезка совпадает с началом координат, и прямая имеет вид

Где. Считаем что начальная точка находится слева. Пусть на (i-1) -м шаге текущей точкой отрезка является P i -1 =(r,q) . Выбор следующей точки S i или T i зависит от знака разности (s-t). Если (s-t)<0 , то P i =T i =(r+1,q) и тогда

X i +1 =i+1;Y i +1 =Y i , если же (s-t)≥0,то P i =T i =(r+1,q+1) и тогда X i +1 =i+1 ; Y i +1 =Y i +1 ;

dx=(s-t)=2(rdy-qdx)+2dy –dx

Поскольку знак dx=(s-t) совпадает со знаком разности) , то будем проверять знак выражения d i =dx(s-t). . Так как r=X i -1 и q=Y i -1 ,то

d i +1 = d i +2dy -2dx(y i -y i -1) .

Пусть на предыдущем шаге d i <0 , тогда(y i -y i -1)=0 и d i +1 = d i +2dy . Если же на предыдущем шаге d i ≥0 , тогда(y i -y i -1)=1 и d i +1 = d i +2dx(y i -y i -1)

Осталось узнать как вычислить d i . Так как при i=1

Procedure Bresenham(x1,y1,x2,y2,Color: integer);

dx,dy,incr1,incr2,d,x,y,xend: integer;

dx:= ABS(x2-x1);

dy:= Abs(y2-y1);

d:=2*dy-dx; {начальное значение для d}

incr1:=2*dy; {приращение для d<0}

incr2:=2*(dy-dx); {приращение для d>=0}

if x1>x2 then {начинаем с точки с меньшим знач. x}

PutPixel(x,y,Color); {первая точка отрезка}

While x

d:=d+incr1 {выбираем нижнюю точку}

d:=d+incr2; {выбираем верхнюю точку, y-возрастает}

PutPixel(x,y,Color);

26. Общий алгоритм Брезенхема.

Алгоритм выбирает оптимальные растровые координаты для представления отрезка. Большее из приращений, либо Δx, либо Δy, выбирается в качестве единицы растра. В процессе работы одна из координат - либо x, либо y (в зависимости от углового коэффициента) - изменяется на единицу. Изменение другой координаты (на 0 или 1) зависит от расстояния между действительным положением отрезка и ближайшими координатами сетки. Такое расстояние есть ошибкой.

Алгоритм построен так, что требуется лишь знать знак этой ошибки. Следовательно, точка растра (1, 1) лучше аппроксимирует ход отрезка, чем точка (1, 0). Если угловой коэффициент меньше ½, то верно обратное. Для углового коэффициента, равного ½, нет какого-либо предпочтительного выбора. В данном случае алгоритм выбирает точку (1, 1). Так как желательно проверять только знак ошибки, то она первоначально устанавливается равной -½. Таким образом, если угловой коэффициент отрезка больше или равен ½, то величина ошибки в следующей точке растра может быть вычислена как е = -½ + Δy/Δx.

Чтобы реализация алгоритма Брезенхема была полной, необходимо обрабатывать отрезки во всех октантах. Это легко сделать, учитывая в алгоритме номер квадранта, в котором лежит отрезок и его угловой коэффициент. Когда абсолютная величина углового коэффициента больше 1, y постоянно изменяется на единицу, а критерий ошибки Брезенхема используется для принятия решения об изменении величины x. Выбор постоянно изменяющейся (на +1 или -1) координаты зависит от квадранта

var x,y,sy,sx,dx,dy,e,z,i: Integer;
change: boolean;
begin
x:=x1; y:=y1;
dx:=abs(x2-x1); dy:=abs(y2-y1) ;
sx:=sign(x2-x1); sy:=sign(y2-y1);
e:= 2*dy-dx;
if dy
else begin
z:=dx;
dx:=dy; dy:=z;
change:=true
end;
for i:=1 to dx+dy do begin
if dy< dx then begin
if change then y:=y+sy
else x:=x+sx;
e:=e+2*dy;
end else
if change then x:=x+sx
else y:=y+sy;
e:=e-2*dx
end;
Form1.Canvas.Pixels:=clblack; // вывод точки, для примера
end;


27. Алгоритм Брезенхема для генерації окружності

У растр потрібно розкладати як лінійні, а й інші, більш складні функції. Розкладаннюконічних перерізів, тобто кіл, еліпсів, парабол, гіпербол, було присвячено значнукількість робіт. Найбільшу увагу, зрозуміло, приділено кола. Один з найбільшефективних і простих для розуміння алгоритмів генерації окружності належитьБрезенхему. Для початку зауважимо, що необхідно згенерувати тільки одну восьмучастину кола. Решта її частини можуть бути отримані послідовними відбитками. Якщо згенерований перший октант (від 0 до 45 ° протигодинникової стрілки), то другий Октант можна отримати дзеркальним відображеннямвідносно прямої у = х, що дає в сукупності перший квадрант. Перший квадрант відбивається відносно прямої х = 0 для отримання відповідної частини кола у другому квадранті. Верхня півколо відбивається відносно прямої у = 0 для завершення побудови.

Для виведення алгоритму розглянемо першу чверть кола з центром в початкукоординат. Зауважимо, що якщо робота алгоритму починається в точці х = 0, у = R, то при генерації окружності за годинниковою стрілкою в першому квадраті у ємонотонно спадною функцією аргументів. Аналогічно, якщо вихідною точкою є у = 0, х == R, то при генерації окружності проти годинникової стрілки х будемонотонно спадною функцією аргументу у. У нашому випадку вибирається генерація за годинниковою стрілкою з початком в точці х = 0, у = R. Передбачається, що центр кола та початкова точка перебувають точно в точках растру.

Для будь-якої заданої точки на колі при генерації за годинниковою стрілкою існує тільки три можливості вибрати наступний піксел, найкращим чином наближує окружність: горизонтально вправо, по діагоналі вниз і вправо, вертикально вниз. Алгоритм вибирає піксель, для якого мінімальний квадрат відстані між одним з цих пікселів і окружністю.

28.Понятие фрактала. История фрактальной графики

В повседневной жизни часто можно наблюдать изображение (узоры), которые, казалось бы, нельзя описать математически. Пример: окна зимой замерзают, можно в итоге наблюдать картину. Подобные множества называют фрактальными. Фракталы не похожи на известные фигуры из геометрии, и строятся они по определенным алгоритмам, которые можно реализовать на компьютере. Упрощенно, фрактал - это изображение, полученное в результате некоторого преобразования, многократно примененного к исходной фигуре.
Первые идеи фрактальной геометрии возникли в 19 веке. Кантор с помощью простой рекурсивной процедуры превратил линию в набор несвязанных точек, которые в последствии получили название «Пыль Кантора». Он брал линию и удалял центральную треть и после этого повторял то же самое с оставшимися отрезками. Пеано нарисовал особый вид линии. Для ее рисования Пеано использовал следующий алгоритм:
Он брал прямую линию и заменял её отрезками в три раза меньшей длины, чем у исходной линии. Далее он повторял это же действие с каждым из отрезков. Её уникальность в том, что она заполняет всю плоскость, т.е. для каждой точки, находящейся на плоскости можно найти точку, принадлежащую линии Пеано.
Основателем фрактальной геометрии считается Бенуа Мандельброт . Мандельброт ввел понятие «фрактал».

Фрактал - это геометрическая фигура, состоящая из частей и которая может быть поделена на части, каждая из которых будет представлять уменьшенную копию целого. Основным свойством фракталов является самоподобие, т.е. любой фрагмент фрактала в том или ином отношении воспроизводит его глобальную структуру. Фракталы делятся на геометрические, алгебраические, стохастические, системы итерируемых функций.

29. Понятие размерности и её расчет

В своей повседневной жизни мы постоянно встречаемся с размерностями. Мы прикидываем длину дороги, узнаем площадь квартиры и т.д. Это понятие вполне интуитивно ясно и, казалось бы, не требует разъяснения. Линия имеет размерность 1. Это означает, что, выбрав точку отсчета, мы можем любую точку на этой линии определить с помощью 1 числа - положительного или отрицательного. Причем это касается всех линий - окружность, квадрат, парабола и т.д.

Размерность 2 означает, что любую точку мы можем однозначно определить двумя числами. Не надо думать, что двумерный - значит плоский. Поверхность сферы тоже двумерна (ее можно определить с помощью двух значений - углов наподобие ширины и долготы).

Если смотреть с математической точки зрения, то размерность определяется следующим образом: для одномерных объектов - увеличение в два раза их линейного размера приводит к увеличению размеров (в данном случае длинны) в два раза (2^1).

Для двумерных объектов увеличение в два раза линейных размеров приводит к увеличению размера (например, площадь прямоугольника) в четыре раза (2^2).

Для 3-х мерных объектов увеличение линейных размеров в два раза приводи к увеличению объема в восемь раз (2^3) и так далее.

Геометрические фракталы

Именно с этим фракталов началась история развития фракталов в целом. Этот тип фракталов получается путем простых геометрических построений. Обычно при построении геометрических фракталов руководствуются следующим алгоритмом:

  1. Берется набор отрезков, на основании которых будет строится фрактал.
  2. К данному набору применяют определенные правила, которые преобразуют его в какую-либо геометрическую фигуру.
  3. К каждой части этой фигуры применяют тот же набор правил. С каждым шагом фигура будет становиться всё сложнее и, если провести бесконечное количество преобразований, получим геометрический фрактал.

Примеры геометрических фракталов: кривая Пеано, снежинка Коха, лист папоротника, треугольник Серпинского,


Рис. Снежинка Коха

Рис. Лист


Рис. Треугольник Серпинского

Алгебраические фракталы

Фрактал - сложная геометрическая фигура, обладающая свойством самоподобия, то есть составленная из нескольких частей, каждая из которых подобна всей фигуре целиком

Алгебраические фракталы получили своё название за то, что их строят на основе алгебраических функцій.К алгебраическим фракталам относяться: множество Мандельброта, множество Жюлиа, басейны Ньютона, биоморфы.

-множество Мандельброта: Впервые множество Мандельброта было описано в 1905 году Пьером Фату. Фату изучал рекурсивные процессы вида

Начав с точки на комплексной плоскости, можно получить новые точки, последовательно применяя к ним эту формулу. Такая последовательность точек называется орбитой при преобразовании

Фату нашел, что орбита при этом преобразовании показывает достаточно сложное и интересное поведение. Существует бесконечное множество таких преобразований - своё для каждого значения . (названо мандельброта так как он первым провел необходимое количество вычислений использовав компьютер).

-множество Жюлиа : мно́жество Жюлиа́ рационального отображения - множество точек, динамика в окрестности которых в определённом смысле неустойчива по отношению к малым возмущениям начального положения. В случае, если f - полином, рассматривают также заполненное множество Жюлиа - множество точек, не стремящихся к бесконечности. Обычное множество Жюлиа при этом является его границей.

-бассейны Ньютона: Области с фрактальными границами появляются при приближенном нахождении корней нелинейного уравнения алгоритмом Ньютона накомплексной плоскости (для функции действительной переменной метод Ньютона часто называют методом касательных , который, в данном случае, обобщается для комплексной плоскости).

Применим метод Ньютона для нахождения нуля функции комплексного переменного, используя процедуру:

Выбор начального приближения представляет особый интерес. Т.к. функция может иметь несколько нулей, в различных случаях метод может сходиться к различным значениям.

-биоморфы: сокращенная форма множества Жюлиа, вычисляеться по формуле z=z 3 +c. Название получила из-за схожести с одноклеточными организмами.

Стохастические фракталы

Типичным представителем данного вида фракталов является так называемая плазма.

Для её построения берут прямоугольник и для каждого его угла определяют цвет. Далее находят центральную точку прямоугольника и раскрашивают её в цвет, равный среднеарифметическому цветов по углам прямоугольника + некоторое случайное число. Чем больше это случайно число - тем более рваным будет рисунок.

Природные объекты часто имеют фрактальную форму. Для их моделирования могут применяться стохастические (случайные) фракталы. Примеры стохастических фракталов:

траектория броуновского движения на плоскости и в пространстве;

граница траектории броуновского движения на плоскости. В 2001 году Лоулер, Шрамм и Вернер доказали предположение Мандельброта о том, что её размерность равна 4/3.

эволюции Шрамма-Лёвнера - конформно-инвариантные фрактальные кривые, возникающие в критических двумерных моделях статистической механики, например, в модели Изинга и перколяции.

различные виды рандомизированных фракталов, то есть фракталов, полученных с помощью рекурсивной процедуры, в которую на каждом шаге введён случайный параметр. Плазма - пример использования такого фрактала в компьютерной графике.

Фрактальная монотипия, или стохатипия - направления в изобразительном искусстве, заключающиеся в получении изображения случайного фрактала.


Похожая информация.


На что вы сейчас смотрите? Если вы не из параллельной вселенной, где все сидят за векторными мониторами, то перед вами растровое изображение. Поглядите на эту полоску: /. Если придвинуться поближе к монитору, то можно увидеть пиксельные ступеньки, которые пытаются притвориться векторной линией. Для этой цели существует целая куча всевозможных алгоритмов растеризации, но я бы хотел рассказать об алгоритме Брезенхема и алгоритме У, которые находят приближение векторного отрезка в растровых координатах.

С проблемой растеризации мне довелось столкнуться во время работы над процедурным генератором планов зданий. Мне нужно было представить стены помещения в виде ячеек двумерного массива. Похожие задачи могут встретиться в физических расчётах, алгоритмах поиска пути или расчёте освещения, если используется разбиение пространства. Кто бы мог подумать, что знакомство с алгоритмами растеризации однажды может пригодиться?

Принцип работы алгоритма Брезенхема очень простой. Берётся отрезок и его начальная координата x . К иксу в цикле прибавляем по единичке в сторону конца отрезка. На каждом шаге вычисляется ошибка - расстояние между реальной координатой y в этом месте и ближайшей ячейкой сетки. Если ошибка не превышает половину высоты ячейки, то она заполняется. Вот и весь алгоритм.

Это была суть алгоритма, на деле всё выглядит следующим образом. Сначала вычисляется угловой коэффициент (y1 - у0)/(x1 - x0) . Значение ошибки в начальной точке отрезка (0,0) принимается равным нулю и первая ячейка заполняется. На следующем шаге к ошибке прибавляется угловой коэффициент и анализируется её значение, если ошибка меньше 0.5 , то заполняется ячейка (x0+1, у0) , если больше, то заполняется ячейка (x0+1, у0+1) и из значения ошибки вычитается единица. На картинке ниже жёлтым цветом показана линия до растеризации, зелёным и красным - расстояние до ближайших ячеек. Угловой коэффициент равняется одной шестой, на первом шаге ошибка становится равной угловому коэффициенту, что меньше 0.5 , а значит ордината остаётся прежней. К середине линии ошибка пересекает рубеж, из неё вычитается единица, а новый пиксель поднимается выше. И так до конца отрезка.

Ещё один нюанс. Если проекция отрезка на ось x меньше проекции на ось y или начало и конец отрезка переставлены местами, то алгоритм не будет работать. Чтобы этого не случилось, нужно проверять направление вектора и его наклон, а потом по необходимости менять местами координаты отрезка, поворачивать оси, и, в конечном итоге, сводить всё к какому-то одному или хотя бы двум случаям. Главное не забывать во время рисования возвращать оси на место.

Для оптимизации расчётов, применяют трюк с умножением всех дробных переменных на dx = (x1 - x0) . Тогда на каждом шаге ошибка будет изменяться на dy = (y1 - y0) вместо углового коэффициента и на dx вместо единицы. Также можно немного поменять логику, «передвинуть» ошибку так, чтобы граница была в нуле, и можно было проверять знак ошибки, это может быть быстрее.

Примерно так может выглядеть код для рисования растровой линии по алгоритму Брезенхема. Псевдокод из Википедии переделанный под C#.

void BresenhamLine(int x0, int y0, int x1, int y1) { var steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0); // Проверяем рост отрезка по оси икс и по оси игрек // Отражаем линию по диагонали, если угол наклона слишком большой if (steep) { Swap(ref x0, ref y0); // Перетасовка координат вынесена в отдельную функцию для красоты Swap(ref x1, ref y1); } // Если линия растёт не слева направо, то меняем начало и конец отрезка местами if (x0 > x1) { Swap(ref x0, ref x1); Swap(ref y0, ref y1); } int dx = x1 - x0; int dy = Math.Abs(y1 - y0); int error = dx / 2; // Здесь используется оптимизация с умножением на dx, чтобы избавиться от лишних дробей int ystep = (y0 < y1) ? 1: -1; // Выбираем направление роста координаты y int y = y0; for (int x = x0; x <= x1; x++) { DrawPoint(steep ? y: x, steep ? x: y); // Не забываем вернуть координаты на место error -= dy; if (error < 0) { y += ystep; error += dx; } } }


У алгоритма Брезенхэма есть модификация для рисования окружностей. Там всё работает по схожему принципу, в чём-то даже проще. Расчёт идёт для одного октанта, а все остальные куски окружности дорисовываются по симметрии.

Пример кода рисования окружности на C#.

void BresenhamCircle(int x0, int y0, int radius) { int x = radius; int y = 0; int radiusError = 1 - x; while (x >= y) { DrawPoint(x + x0, y + y0); DrawPoint(y + x0, x + y0); DrawPoint(-x + x0, y + y0); DrawPoint(-y + x0, x + y0); DrawPoint(-x + x0, -y + y0); DrawPoint(-y + x0, -x + y0); DrawPoint(x + x0, -y + y0); DrawPoint(y + x0, -x + y0); y++; if (radiusError < 0) { radiusError += 2 * y + 1; } else { x--; radiusError += 2 * (y - x + 1); } } }


Теперь про алгоритм У Сяолиня для рисования сглаженных линий. Он отличается тем, что на каждом шаге ведётся расчёт для двух ближайших к прямой пикселей, и они закрашиваются с разной интенсивностью, в зависимости от удаленности. Точное пересечение середины пикселя даёт 100% интенсивности, если пиксель находится на расстоянии в 0.9 пикселя, то интенсивность будет 10%. Иными словами, сто процентов интенсивности делится между пикселями, которые ограничивают векторную линию с двух сторон.

На картинке выше красным и зелёным цветом показаны расстояния до двух соседних пикселей.

Для расчёта ошибки можно использовать переменную с плавающей запятой и брать значение ошибки из дробной части.

Примерный код сглаженной линии У Сяолиня на C#.

private void WuLine(int x0, int y0, int x1, int y1) { var steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0); if (steep) { Swap(ref x0, ref y0); Swap(ref x1, ref y1); } if (x0 > x1) { Swap(ref x0, ref x1); Swap(ref y0, ref y1); } DrawPoint(steep, x0, y0, 1); // Эта функция автоматом меняет координаты местами в зависимости от переменной steep DrawPoint(steep, x1, y1, 1); // Последний аргумент - интенсивность в долях единицы float dx = x1 - x0; float dy = y1 - y0; float gradient = dy / dx; float y = y0 + gradient; for (var x = x0 + 1; x <= x1 - 1; x++) { DrawPoint(steep, x, (int)y, 1 - (y - (int)y)); DrawPoint(steep, x, (int)y + 1, y - (int)y); y += gradient; } }


Если вам вдруг в будущем придётся работать с сетками, задумайтесь ненадолго, возможно вы изобретаете велосипед и на самом деле вы работаете с пикселями, хотя и не знаете этого. Модификации этих алгоритмов можно использовать в играх для поиска ячеек перед юнитом на карте, расчёта области поражения выстрела или красивой расстановки объектов. Или можно просто рисовать линии, как в программе по ссылкам ниже.

Сложно сегодня найти человека, который бы не сталкивался с машинной графикой в тех или иных проявлениях. Если человек начинает интересоваться алгоритмами, лежащими в её основе, то одними из первых будут алгоритмы Брезенхема. Беда лишь в том, что мне до сих пор не попадалось простого и вразумительного описания этих алгоритмов, а уж тем более — реализации. В этой статье я попытаюсь по возможности просто рассказать о семействе алгоритмов Брезенхема, а также приведу готовый к использованию код на JavaScript, который практически не отличается от кода на C/C++ . Код можно брать и использовать, предварительно написав автору благодарственное письмо.

Хотелось бы выразить свои глубокие и искренние чувства к разработчикам стандартов www и тем, кто их реализует. Вариант JavaScript-кода, работающий во всех доступных броузерах, т.е. IE 6.0, NN 7.0 и Opera 6.0x, не отличается красотой и изысканностью. Впрочем, «к науке, которую я в настоящий момент представляю, это отношения не имеет».

Итак, назначение алгоритмов Брезенхема — нарисовать линию на растровом устройстве, как правило, на мониторе. Как можно видеть на рисунке 1, не все пиксели, входящие в изображение линии, лежат на этой линии, то есть задача алгоритма — найти наиболее близкие пиксели. Главное достоинство алгоритма Брезенхема в том, что в нём не используется в цикле дорогостоящая операция умножения. Алгоритм подходит для прямых или кривых второго порядка*. Существуют модификации алгоритма для четырёхсвязной (т.е. соседними считаются точки, отличающиеся на 1 по одной координате) и восьмисвязной (т.е. соседними считаются точки, обе координаты которых отличаются не больше, чем на 1) линий. Здесь приведён второй вариант — более сложный, но и дающий лучший результат.

Основная идея алгоритма в том, что линия, которую надо нарисовать, делит плоскость на две части. Уравнение кривой записывается в виде Z = f (x,y) . Во всех точках кривой Z = 0 , в точках, лежащих над кривой Z > 0 , а в точках под кривой Z < 0 . Нам известны координаты начала отрезка, то есть точки, заведомо лежащей на искомой кривой. Ставим туда первый пиксель и принимаем Z = 0 . От текущего пикселя можно сделать два шага — либо по вертикали (по горизонтали), либо по диагонали на один пиксель. Конкретные направления шагов выбираются в зависимости от типа линии, которую надо нарисовать. Делая шаг, мы мы вычисляем, как изменятся значение Z:

ΔZ = Z" x Δx + Z" y Δy

При одном из возможных шагов Z растёт, при другом — уменьшается. Каждый шаг выбирается с тем расчётом, чтобы значение Z для нового пикселя было как можно ближе к 0. Таким образом, мы будем двигаться вдоль линии, создавая её изображение.

Рисование отрезка

Сразу договоримся, что алгоритм для прямой не рисует горизонтальные и вертикальные линии. Это связано с тем, что рисование таких линий можно реализовать гораздо более простым способом, часто на уровне BIOS или драйвера.

Оставшиеся отрезки делятся на две группы: горизонтальные и вертикальные. Если представить уравнение прямой в виде y = kx , то горизонтальными считаются отрезки, у которых |k| ≤ 1 , а вертикальными — у которых |k| > 1 . Отнеся отрезок к одной из групп, мы можем поменять местами координаты концов так, чтобы горизонтальные отрезки всегда рисовались слева направо, а вертикальные — сверху вниз.

Для горизонтальных отрезков каждый новый пиксель будет правее предыдущего на 1, при этом он может также быть выше (ниже), т.е. возможны два шага — вправо и вправо-по диагонали. Для вертикальных отрезков возможные шаги — вниз и вниз-по диагонали.

Если координаты концов отрезка (x 1 ,y 1) и (x 2 ,y 2) соответственно, то при каждом шаге по оси x Z изменяется на 1, а по оси y — на (x 2 -x 1)/(y 2 -y 1) . Чтобы не связываться с делением и остаться в пределах целочисленной арифметики, переменную Z будем изменять соответственно на y2-y1 и x2-x1 . Вот, собственно, и вся математика, остальное можно понять из кода.

Рисование окружности

Алгоритм рисования дуги останется за рамками статьи, а вот алгоритм для рисования окружности получился значительно проще, чем для прямой. Связано это со многими причинами.

Во-первых, мы рисуем только одну восьмую часть окружности — от π/2 до π/4 , причём в обратном направлении, то есть по часовой стрелке. Вся остальная окружность получается путём отражения этой части относительно центра окружности, горизонтальной и вертикальной осей, а также прямых y = x + b и y = -x + b , проходящих через центр окружности.

Во-вторых из-за симметрии отклонения линии от окружности не так заметны, как отклонения от прямой, поэтому Z можно сравнивать с нулём, не вычисляя максимально допустимого отклонения.

Допустимые шаги — вправо и вправо-по диагонали, а изменение Z зависит от значений x и y , но зависимость линейная, поэтому операция умножения не требуется.

Вот, собственно, и всё. Ниже вы найдёте скрипт, демонстрирующий работу описанных алгоритмов, а для того, чтобы понять, как он работает, просто посмотрите исходный текст страницы.

Удачи!

Если хотите увидеть демонстрацию работы алгоритмов в окне броузера, включите JavaScript!

x1: y1:
x2: y2:
x0: y0:
R: