Министерство образования и науки Российской Федерации
Федеральное агентство по образованию
Поволжский государственный технологический университет
Кафедра ИВС
Отчет по лабораторной работе №3
МЕТОДЫ ЗАКРАСКИ ОБЪЕКТОВ
По дисциплине
«Компьютерная графика»
Выполнили:
студенты группы ВМ-31
_Мочалов О.М. ___________ _______
(ФИО) (подпись) (дата)
Проверил:
преподаватель каф. ИВС
_Морохин Д. В.__ ___________ _______
(ФИО) (подпись) (дата)
Йошкар-Ола,
2012 г.
Цель работы: изучение методов заполнения области определенным цветом, приобретение навыков использования алгоритмов при составлении графических программ.
теоретические сведения
В большинстве приложений используется одно из существенных достоинств растровых устройств – возможность заполнения областей экрана.
Существуют две разновидности заполнения:
первая, связанная как с интерактивной работой, так и с программным синтезом изображения, служит для заполнения внутренней части многоугольника, заданного координатами его вершин;
вторая, связанная в первую очередь с интерактивной работой, служит для заливки области, которая либо очерчена границей с кодом пикселя, отличающимся от кодов любых пикселей внутри области, либо закрашена пикселями с заданным кодом;
Простейший способ заполнения многоугольника, заданного координатами вершин, заключается в определении, принадлежит ли текущий пиксель внутренней части многоугольника. Если принадлежит, то пиксель заносится.
Определить принадлежность пикселя многоугольнику можно, например, подсчетом суммарного угла с вершиной на пикселе при обходе контура многоугольника. Если пиксель внутри, то угол будет равен 360, если вне - 0 (рис. 3.1).
Рис. 3.1: Определение принадлежности пикселя многоугольнику:
а) Суммарный угол равен AoB+BoC+CoD+DoA=360˚; b) Угол AoB < 0, так как обход производится по часовой стрелке, и равен сумме BoC+CoD+DoA
Вычисление принадлежности должно производиться для всех пикселей экрана, и так как большинство пикселей, скорее всего, находится вне многоугольников, то данный способ слишком расточителен. Объем лишних вычислений в некоторых случаях можно сократить использованием прямоугольной оболочки – минимального прямоугольника, объемлющего интересующий объект, но все равно вычислений будет много. Другой метод определения принадлежности точки внутренней части многоугольника будет рассмотрен ниже при изучении отсечения отрезков по алгоритму Кируса-Бека.
Построчное заполнение
Реально используются алгоритмы построчного заполнения, основанные на том, что соседние пиксели в строке, скорее всего, одинаковы и меняются только там, где строка пересекается с ребром многоугольника. Это называется когерентностью растровых строк (строки сканирования Yi, Yi+1, Yi+2 на рис. 3.2). При этом достаточно определить X-координаты пересечений строк сканирования с ребрами. Пары отсортированных точек пересечения задают интервалы заливки.
Рис. 3.2. Построчная закраска многоугольника
Кроме того, если какие-либо ребра пересекались i-й строкой, то они, скорее всего, будут пересекаться также и строкой i+1 (строки сканирования Yi и Yi+1 на рис. 3.2). Это называется когерентностью ребер. При переходе к новой строке легко вычислить новую X-координату точки пересечения ребра, используя X-координату старой точки пересечения и тангенс угла наклона ребра:
Xi+1 = Xi + 1/k
(тангенс угла наклона ребра k = dy/dx; так как dy = 1, то 1/k = dx).
Смена же количества интервалов заливки происходит только тогда, когда в строке сканирования появляется вершина.
Учет когерентности строк и ребер позволяет построить для заполнения многоугольников различные высокоэффективные алгоритмы построчного сканирования. Для каждой строки сканирования рассматриваются только те ребра, которые пересекают строку. Они задаются списком активных ребер (САР). При переходе к следующей строке для пересекаемых ребер перевычисляются X-координаты пересечений. При появлении в строке сканирования вершин производится перестройка САР. Ребра, которые перестали пересекаться, удаляются из САР, а все новые ребра, пересекаемые строкой, заносятся в него.
Общая схема алгоритма, динамически формирующего список активных ребер и заполняющего многоугольник снизу-вверх, следующая:
-
Подготовить служебные целочисленные массивы Y-координат вершин и номеров вершин.
-
Совместно отсортировать Y-координаты по возрастанию и массив номеров вершин для того, чтобы можно было определить исходный номер вершины.
-
Определить пределы заполнения по оси Y – Y_min и Y_max. Стартуя с текущим значением Y_tek = Y_min, исполнять пункты 4-9 до завершения раскраски.
-
Определить число вершин, расположенных на строке Y_tek - текущей строке сканирования.
-
Если вершины есть, то для каждой из вершин дополнить список активных ребер, используя информацию о соседних вершинах.
-
Для каждого ребра в список активных ребер заносятся:
-
максимальное значение Y-координаты ребра,
-
приращение X-координаты при увеличении Y на 1,
-
начальное значение X-координаты.
Если обнаруживаются горизонтальные ребра, то они просто закрашиваются и информация о них в список активных ребер не заносится. Если после этого обнаруживается, что список активных ребер пуст, то заполнение закончено.
По списку активных ребер определяется Y_след – Y-координата ближайшей вершины. (Вплоть до Y_след можно не заботиться о модификации САР а только менять X-координаты пересечений строки сканирования с активными ребрами).
В цикле от Y_tek до Y_след:
-
выбрать из списка активных ребер и отсортировать X-координаты пересечений активных ребер со строкой сканирования;
-
определить интервалы и выполнить закраску;
-
перевычислить координаты пересечений для следующей строки сканирования.
Проверить, не достигли ли максимальной Y-координаты. Если достигли, то заливка закончена, иначе выполнить пункт 8.
Очистить список активных ребер от ребер, закончившихся на строке Y_след и перейти к пункту 4.
Заливка области с затравкой
Как уже отмечалось, для приложений, связанных в основном с интерактивной работой, используются алгоритмы заполнения области с затравкой.
При этом тем или иным образом задается заливаемая (перекрашиваемая) область, код пикселя, которым будет выполняться заливка и начальная точка в области, начиная с которой начнется заливка.
По способу задания области делятся на два типа:
гранично-определенные, задаваемые своей (замкнутой) границей такой, что коды пикселей границы отличны от кодов внутренней, перекрашиваемой части области. На коды пикселей внутренней части области налагается условие - они должны быть отличны от кода пикселей границы и кода пикселя перекраски. Если внутри гранично-определенной области имеется еще одна граница, нарисованная пикселями с тем же кодом, что и внешняя граница, то соответствующая часть области не должна перекрашиваться;
внутренне-определенные, нарисованные одним определенным кодом пикселя. При заливке этот код заменяется на новый код закраски.
В этом состоит основное отличие заливки области с затравкой от заполнения многоугольника. В последнем случае мы сразу имеем всю информацию о предельных размерах части экрана, занятой многоугольником. Поэтому определение принадлежности пикселя многоугольнику базируется на быстро работающих алгоритмах, использующих когерентность строк и ребер (см. предыдущий раздел). В алгоритмах же заливки области с затравкой нам вначале надо прочитать пиксель, затем определить принадлежность области, и если принадлежит, то перекрасить.
Заливаемая область или ее граница – некоторое связное множество пикселей. По способам доступа к соседним пикселям области делятся на 4- и 8-связные. В 4-связных областях доступ к соседним пикселям осуществляется по четырем направлениям - горизонтально влево и вправо и вертикально вверх и вниз. В 8-связных областях к этим направлениям добавляются еще 4 диагональных. Используя связность, мы можем, двигаясь от точки затравки, достичь и закрасить все пиксели области.
Важно отметить, что для 4-связной прямоугольной области граница 8-связна (рис. 3.3,а) и наоборот у 8-связной области граница 4-связна (см. рис. 3.3,б). Поэтому заполнение 4-связной области 8-связным алгоритмом может привести к "просачиванию" через границу и заливке пикселей в примыкающей области.
В общем, 4-связную область мы можем заполнить как 4-, так и 8-связным алгоритмом. Обратное же неверно. Так, область на рис. 3.3,а мы можем заполнить любым алгоритмом, а область на рис. 3.3,б, состоящую из двух примыкающих 4-связных областей можно заполнить только 8-связным алгоритмом.
а) б)
– пиксели области – пиксели границы
Рис. 3: Связность областей и их границ:
а) четырехсвязная; б) восьмисвязная
С использованием связности областей и стека можно построить простые алгоритмы закраски как внутренне-оределенной, так и гранично-определенной области.
Простой алгоритм заливки
Рассмотрим простой алгоритм заливки гранично-определенной 4-связной области:
Заливка выполняется следующим образом:
определяется, является ли пиксель граничным или уже закрашенным;
если не является, то пиксель перекрашивается, затем проверяются, и если надо, перекрашиваются 4 соседних пикселя.
Понятно, что, несмотря на простоту и изящество программы, рекурсивная реализация проигрывает итеративной в том, что требуется много памяти для вложенных вызовов.
Логика работы алгоритма следующая:
-
поместить координаты затравки в стек;
-
пока стек не пуст;
-
извлечь координаты пикселя из стека;
-
перекрасить пиксель;
-
для всех четырех соседних пикселей проверить, является ли он граничным или уже перекрашен;
-
если пиксель не является граничным или перекрашенным, то занести его координаты в стек.
Ясно, что такой алгоритм экономнее, так как в стек надо помещать только координаты.
Рассмотренный алгоритм легко модифицировать для работы с 8-связными гранично-определенными или с внутренне-определенными областями.
Как уже отмечалось, очевидный недостаток алгоритмов, непосредственно использующих связность закрашиваемой области, – большие затраты памяти на стек, так как на каждый закрашенный пиксель в стеке по максимуму будет занесена информация о еще трех соседних. Кроме того, информация о некоторых пикселях может записываться в стек многократно. Это приведет не только к перерасходу памяти, но и потере быстродействия за счет многократной раскраски одного и того же пикселя. Значительно более экономен далее рассмотренный построчный алгоритм заливки.
Построчный алгоритм заливки с затравкой
Данный алгоритм использует пространственную когерентность:
пиксели в строке меняются только на границах;
при перемещении к следующей строке размер заливаемой строки скорее всего или неизменен, или меняется на 1 пиксель.
Таким образом, на каждый закрашиваемый фрагмент строки в стеке хранятся координаты только одного начального пикселя, что приводит к существенному уменьшению размера стека.
Последовательность работы алгоритма для гранично-определенной области следующая:
-
Координата затравки помещается в стек, затем до исчерпания стека выполняются пункты 2-4.
-
Координата очередной затравки извлекается из стека, и выполняется максимально возможное закрашивание вправо и влево по строке с затравкой, т.е. пока не попадется граничный пиксель. Пусть это Хлев и Хправ, соответственно.
-
Анализируется строка ниже закрашиваемой в пределах от Хлев до Хправ, и в ней находятся крайние правые пиксели всех незакрашенных фрагментов. Их координаты заносятся в стек.
-
То же самое проделывается для строки выше закрашиваемой.
Алгоритм выполнения программы
Данная программа написана на языке С++. Ввод данных осуществляется через командную строку.
Закрашивает согласно варианту.
Описание с начала работы программы:
1) Этап 1: подключение библиотек, запуск графического режима, объявление переменных, рисование в окне координатной оси с обозначенными на ней значениями по Х и по Y.
#include <graphics.h>
#include <stdio.h>
#include <string>
#include <iostream.h>
#include <math.h>
#include <stdlib.h>
void setka ()
{
int i;
for(i = 0; i < 800; i = i + 10 ) {
setcolor(COLOR(71,71,71));
line(10+i, 10, 10+i, 590);
line(10, 10+i, 790, 10+i);
}
for(i = 0; i < 800; i = i + 50 ) {
setcolor(COLOR(251,151,151));
line(50+i,295,50+i,305);
}
for(i = 0; i < 600; i = i + 50 ) {
setcolor(COLOR(251,151,151));
line(395,50+i,405,50+i);
}
setcolor(COLOR(151,151,151));
line(10,300,790,300);
line(400,10,400,590);
line(790,300,780,305);
line(790,300,780,295);
line(400,10,395,20);
line(400,10,405,20);
outtextxy(380, 3, "y");
outtextxy(780, 307, "x");
outtextxy(385, 305, "0");
outtextxy(443, 305, "50");
outtextxy(489, 305, "100");
outtextxy(340, 305, "-50");
outtextxy(380, 242, "50");
}
2) Этап 2: Приводим в числа в целый вид, необходимый для построения точек.
void zapolnenie(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,int x5,int y5,int x6,int y6)
{
setcolor(COLOR(251,151,151));
int a[100][3],i,j,m,n,xt,yt,ys,miny,k,ymin,ymax,b[30],q,minx,o,p,e;
float cap[100][3],z1,z2,z3,z4,x[30],y[30],t;
n=6; //кол-во вершин
x[1]=x1;
x[2]=x2;
x[3]=x3;
x[4]=x4;
x[5]=x5;
x[6]=x6;
y[1]=y1;
y[2]=y2;
y[3]=y3;
y[4]=y4;
y[5]=y5;
y[6]=y6;
3) Этап 3: Подготовим служебные целочисленные массивы Y-координат вершин и номеров вершин.
for(i=1;i<=n;i++)
{
a[i][1]=x[i];
a[i][2]=y[i];
a[i][3]=i;
a[i][4]=x[i+1];
a[i][5]=y[i+1];
a[i][6]=i+1;
4) Этап 4: Совместно отсортируем Y-координаты по возрастанию и массив номеров вершин, для того, чтобы можно было определить исходный номер вершины.
for(i=1;i<=n-1;i++)
{
miny=a[i][2];
for(j=i;j<=n;j++)
if (miny>=a[j][2]) { miny=a[j][2]; k=j; }
for(j=1;j<=3;j++)
{
t=a[i][j];
a[i][j]=a[k][j];
a[k][j]=t;
}
5) Этап 5: Определим пределы заполнения по оси Y - Y_мin и Y_max, стартуя с текущим значением Y_tek = Y_min.
ymin=a[1][2];
ymax=a[n][2];
yt=ymin;
m=0;
6) Этап 6: Определим число вершин, расположенных на строке Y_tek - текущей строке сканирования. Если вершины есть, то для каждой из вершин дополнить список активных ребер, используя информацию о соседних вершинах.
punkt4:
k=0;
for(i=1;i<=n;i++)
if (yt==a[i][2]) { k++; b[k]=i;}
for(i=1;i<=k;i++)
{
if (i!=k) if (a[b[i]][2]==a[b[i+1]][2]) { line(400+a[b[i]][1],300-a[b[i]][2],400+a[b[i+1]][1],300-a[b[i+1]][2]); /*printf("line\n"); REMOVED */ }
for(j=1;j<=n;j++)
if ((((a[j][3]+1==a[b[i]][3]) || (a[j][3]-1==a[b[i]][3])) && (a[j][2]>a[b[i]][2])) || ((a[b[i]][3]==n) && (a[j][3]==1) && (a[j][2]>a[b[i]][2])))
{
m++;
cap[m][1]=a[j][2];
z1=a[j][1];
z2=a[b[i]][1];
z3=a[j][2];
z4=a[b[i]][2];
cap[m][2]=(z1-z2)/(z3-z4);
cap[m][3]=a[b[i]][1];
}
}
7) Этап 7: Поиск номера текущей вершниы yt
for(i=1;i<=n;i++)
if ((a[i][2]==yt) && (a[i][2]!=a[i+1][2])) q=i;
ys=a[q+1][2];
8) Этап 8: Отсортиpуем координаты, определим интервалы и выполним закраску.
9) Этап 9: Выход из графического режима после нажатия на любую клавишу. Завершение работы программы.
7) Этап 7: Конец. Завершение работы программы.
Вывод
В результате проделанной работы мы ознакомились с методами геометрических преобразований графических объектов, изучив методы закраски объектов, научились закрашивать многоугольники.