Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Гущин_SDL.pdf
Скачиваний:
168
Добавлен:
17.03.2018
Размер:
1.09 Mб
Скачать

y*screen->pitch + x * 3; if(SDL_BYTEORDER == SDL_LIL_ENDIAN)

{/* Учет возможного различия в порядке следования байтов

вмашинном слове различных процессорных архитектур */ bufp[0] = color;

bufp[1] = color >> 8; bufp[2] = color >> 16;

}else {

bufp[2] = color; bufp[1] = color >> 8;

bufp[0] = color >> 16;

}

}

break;

case 4: // Возможно 32-bpp...

{

Uint32 *bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x;

/* Поскольку (Uint32 *)screen->pixels возвращает указатель на целое число, занимающее в памяти 4 байта, при вычислении смещения делим длину строки в байтах

на 4 */

*bufp = color;

}

break;

}

}

4. Графические примитивы библиотеки SDL_draw

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

Приведем объявления функций в библиотеке SDL_draw из заголовочного файла SDL_draw.h:

extern DECLSPEC /* Отображение точки заданного цвета */ void (*Draw_Pixel)(SDL_Surface *super,

Sint16 x, Sint16 y, Uint32 color);

extern DECLSPEC /* Рисование произвольной прямой линии */ void (*Draw_Line)(SDL_Surface *super,

Sint16 x1, Sint16 y1,

Sint16 x2, Sint16 y2, Uint32 color);

extern DECLSPEC /* Рисование окружности (контура) */ void (*Draw_Circle)(SDL_Surface *super,

Sint16 x0, Sint16 y0, Uint16 r, Uint32 color);

extern DECLSPEC /* Рисование круга (закрашен одним цветом) */ void (*Draw_FillCircle)(SDL_Surface *super,

Sint16 x0, Sint16 y0, Uint16 r, Uint32 color);

extern DECLSPEC /* рисование горизонтальной линии */ void (*Draw_HLine)(SDL_Surface *super,

17

Sint16 x0,Sint16 y0, Sint16 x1,

Uint32 color);

extern DECLSPEC /* Рисование вертикальной линии */ void (*Draw_VLine)(SDL_Surface *super,

Sint16 x0,Sint16 y0, Sint16 y1, Uint32 color);

extern DECLSPEC /* Рисование прямоугольника (контура) */ void (*Draw_Rect)(SDL_Surface *super,

Sint16 x,Sint16 y, Uint16 w,Uint16 h, Uint32 color);

/* Макрос для единообразного с Draw_Rect рисования закрашенного прямоугольника. Используется функция SDL_FillRect из собственно библиотеки SDL. При этом производится преобразование параметров в структуру

SDL_Rect. */

#define Draw_FillRect(SUPER, X, Y, W, H, COLOR) \

do {

\

SDL_Rect r = {(X), (Y), (W), (H)};

\

SDL_FillRect((SUPER), &r, (COLOR));

\

}while(0)

 

extern DECLSPEC /* Рисование эллипса (контура) */ void (*Draw_Ellipse)(SDL_Surface *super,

Sint16 x0, Sint16 y0,

Uint16 Xradius, Uint16 Yradius, Uint32 color);

extern DECLSPEC /* Рисование эллипса, закрашенного одним цветом со своим контуром */

void (*Draw_FillEllipse)(SDL_Surface *super, Sint16 x0, Sint16 y0,

Uint16 Xradius, Uint16 Yradius, Uint32 color);

extern DECLSPEC /* Рисование контура прямоугольника со скругленными углами */

void (*Draw_Round)(SDL_Surface *super,

Sint16 x0,Sint16 y0, Uint16 w,Uint16 h, Uint16 corner, Uint32 color);

extern DECLSPEC /* Рисование закрашенного одним цветом со своим контуром прямоугольника со скругленными углами */ void (*Draw_FillRound)(SDL_Surface *super,

Sint16 x0,Sint16 y0, Uint16 w,Uint16 h,

Uint16 corner, Uint32 color);

Все функции, независимо от вида отображаемого графи-ческого примитива, содержат в числе своих параметров поверх-ность, на которой должно производиться рисование (super – указа-тель на структуру SDL_Surface), и цвет, которым производится рисование (color – целое число без знака, представленное 4 бай-тами). Следует обратить внимание, что параметр цвета должен быть уже сформированным из отдельных цветовых компонентов с учетом формата представления пикселей основной поверхности отображения (окна программы или экрана) по аналогии с ранее рассмотренной функцией DrawPixel с помощью функции SDL_MapRGB. Все поверхности, на которых производится рисо-вание функциями библиотеки SDL_draw, должны иметь одина-ковый формат с основной поверхностью рисования (той, которая соответствует окну программы или всему дисплею в полно-экранном режиме, т.е. поверхностью, возвращенной функцией SDL_SetVideoMode). Рассмотрим параметры, специфичные для отдельных функций или групп функций.

18

Параметры x и y функции Draw_Pixel задают координаты закрашиваемого пикселя. Параметры x1, y1 и x2, y2 функции Draw_Line задают коорди-наты начала и конца

отображаемой линии в целочисленных координатах поверхности (пикселях).

Уфункций Draw_HLine и Draw_VLine, отображающих горизонтальную и вертикальную линии, на один параметр меньше, поскольку координата y или x обоих концов линии у них соответственно постоянна. Наличие отдельных функций для двух частных случаев вызвано возможностью более эффективной реализации и однозначным соответствием им конкретного набора пикселей поверхности.

Уфункций Draw_Circle, рисующей окружность, и Draw_FillCircle, рисующей круг (закрашенную окружность), параметры x0, y0 задают центр, а r – радиус фигуры.

Уфункций Draw_Ellipse, рисующей эллипс с полуосями, параллельными осям координат, и Draw_Fill Ellipse, рисующей аналогичный закрашенный эллипс, параметры x0, y0 задают центр, а Xradius и Yradius – длины соответствующих полуосей фигуры.

Уфункций Draw_Rect, рисующей прямоугольник, и Draw_FillRect, рисующей закрашенный прямоугольник, параметры x, y задают координаты верхнего левого угла, а w, h – соответ-ственно ширину и высоту фигуры.

Уфункций Draw_Round, рисующей прямоугольник со скруг-ленными углами, и Draw_FillRound, рисующей аналогичный закрашенный прямоугольник, имеется дополнительный параметр corner, задающий радиус дуг, заменяющих углы прямоугольника.

Для использования функций библиотеки в программе необходимо добавить в ее текст подключение заголовочного файла SDL_draw.h:

#include "SDL_draw.h"

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

Общая последовательность использования блокировок для некоторой поверхности screen при работе с SDL:

1.Проверка необходимости блокировки поверхности функ-цией SDL_MUSTLOCK(screen). Возвращает ненулевое значение при необходимости блокировки.

2.Блокировка поверхности при необходимости функцией SDL_LockSurface(screen). Возвращает 0 при успешной блокировке и –1, если поверхность не заблокирована.

3.При успешной блокировке или отсутствии необходимости в ней – рисование на поверхности.

4.Если производилась блокировка – снятие блокировки функ-цией

SDL_UnlockSurface(screen).

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

SDL_UpdateRect(screen, 0, 0, 0, 0);

Подробности работы с функциями SDL_UpdateRect и SDL_UpdateRects рекомендуется изучить самостоятельно.

5. Рисование сложных фигур

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

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

19

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

/* Определяем структуру для задания координат точек: */ struct MPoint {

Sint16 x, y;

};

typedef struct MPoint Point;

/* surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы; points – одномерный массив структур, описывающих координаты вершин многоугольника; n – число вершин (элементов массива points), color – цвет линий многоугольника */

void Draw_Polygon(SDL_Surface *surf, Point points[], Uint32 n, Uint32 color)

{

int i;

for(i = 1; i < n; i++) Draw_Line(surf,points[i-1].x,points[i-1].y,

points[i].x,points[i].y,color); Draw_Line(surf,points[n-1].x,points[n-1].y,

points[0].x,points[0].y,color);

}

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

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

void Draw_RightPolygon(SDL_Surface *surf, Uint16 x0, Uint16 y0, Uint16 r, Uint32 n, double phi, Uint32 color)

{

Point *points = NULL; if(n > 2)

{

points = (Point*)malloc(n*sizeof(Point)); if(points)

{

double omega = M_PI*2/n; int i;

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

{

points[i].x=floor(x0 + r*cos(omega*i + phi)); points[i].y=floor(y0 - r*sin(omega*i + phi));

}

Draw_Polygon(surf, points, n, color); free(points);

}

}

}

20

Особенность данной функции – расчет координат вершин пра-вильного многоугольника исходя из координат центра и радиуса описанной окружности. По умолчанию радиус-вектор к первой вершине сонаправлен положительному направлению оси OX, последующие вершины вычисляются при движении радиуса-вектора против часовой стрелки. Для изменения пространственной ориентации многоугольника введен дополнительный параметр phi – угол поворота в радианах радиуса-вектора для расчета коор-динат первой вершины. Данную функцию можно оптимизировать, перейдя от вызова функции Draw_Polygon непосредственно к вызовам функции Draw_Line или создав семейство функции для рисования многоугольников с заданным числом вершин, заданной пространственной ориентацией, заданным цветом и т.д.

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

Если убрать требование совпадения конца последнего отрезка с началом первого, то вместо многоугольника можно рисовать ломаную линию из N–1 отрезка, заданную с помощью N точек:

/* surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы;

points – одномерный массив структур, описывающих координаты концов отрезков ломаной линии;

n – число концов отрезков (элементов массива points), число отрезков равно n-1, color – цвет линии */

void Draw_Polyline(SDL_Surface *surf, Point points[], Uint32 n, Uint32 color)

{

int i;

for(i = 1; i < n; i++) Draw_Line(surf,points[i-1].x,points[i-1].y,

points[i].x,points[i].y,color);

}

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

/* Draw_Arc – рисование дуги.

surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы;

x0, y0, к – координаты центра и радиус окружности, от которой взята дуга,

phi1, phi2 – углы начала и конца дуги в радианах, считая от оси OX против часовой стрелки,

color – цвет линии */

void Draw_Arc(SDL_Surface *surf, Uint16 x0, Uint16 y0, Uint16 r,

double phi1, double phi2, Uint32 color)

{

Point *points = NULL; double delta = M_PI / 180;

int n = (phi2-phi1) / delta; if(n)

n++; if(n > 2)

{

points = (Point*)malloc(n*sizeof(Point)); if(points)

{

int i;

21

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

{

points[i].x=floor(x0 + r*cos(delta*i + phi1)); points[i].y=floor(y0 - r*sin(delta*i + phi1));

}

Draw_Polyline(surf, points, n, color); free(points);

}

}

}

/* Draw_Sector – рисование незакрашенного сектора. surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы;

x0, y0, к – координаты центра и радиус окружности, от которой взят сектор,

phi1, phi2 – углы начала и конца сектора в радианах, считая от оси OX против часовой стрелки,

color – цвет линий */

void Draw_Sector(SDL_Surface *surf, Uint16 x0, Uint16 y0, Uint16 r,

double phi1, double phi2, Uint32 color)

{

Point *points = NULL; double delta = M_PI / 180;

int n = (phi2-phi1) / delta; if(n)

n++; if(n > 2)

{

points = (Point*)malloc(n*sizeof(Point)); if(points)

{

int i; for(i=0;i<n;i++)

{

points[i].x=floor(x0 + r*cos(delta*i + phi1)); points[i].y=floor(y0 - r*sin(delta*i + phi1));

}

Draw_Polyline(surf, points, n, color); Draw_Line(surf,x0,y0,points[0].x,points[0].y, color); Draw_Line(surf,x0,y0,points[n-1].x,points[n-1].y, color);

free(points);

}

}

}

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

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

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

/* Линия с заданной толщиной. Параметры совпадают c Draw_Line из SDL_draw, дополнительный параметр bold – Толщина линии в пикселях,

22

0 или 1 – просто линия.

Рисование серией параллельных линий */ void Draw_BoldLine1(SDL_Surface *surf,

Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint16 bold,

Uint32 color)

{

Sint16 dx=x2-x1, dy=y2-y1;

/* Всегда рисуем основную линию */ Draw_Line(surf,x1,y1,x2,y2,color); if(bold >1 && !(dx == 0 && dy == 0))

{

double r = bold/2; /* радиус "кисти" для рисования */ double phi; /* направляющий угол */

double ri; /* для рисования параллельных линий */

if(dy==0)

phi = dx>0 ? 0 : M_PI; else

if(dx==0)

phi = dy<0 ? M_PI_2 : M_PI + M_PI_2; else

phi = acos(dx/sqrt(dx*dx+dy*dy)); if(dy>0)

phi = 2*M_PI - phi; for(ri = 0; ri < r; ri+=0.5)

{/* рисуем линии, параллельные исходной */ Sint16 px1, py1, px2, py2;

px1 = floor(x1 + ri*cos(phi + M_PI_2)); py1 = floor(y1 - ri*sin(phi + M_PI_2)); px2 = floor(x2 + ri*cos(phi + M_PI_2)); py2 = floor(y2 - ri*sin(phi + M_PI_2)); Draw_Line(surf,px1,py1,px2,py2,color); px1 = floor(x1 + ri*cos(phi - M_PI_2)); py1 = floor(y1 - ri*sin(phi - M_PI_2));

px2 = floor(x2 + ri*cos(phi - M_PI_2)); py2 = floor(y2 - ri*sin(phi - M_PI_2)); Draw_Line(surf,px1,py1,px2,py2,color);

}

}

}

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

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

Поскольку изображение на мониторе (в окне программы для SDL) рассматривается как матрица пикселей, рисование некото-рого примитива изменяет состояние части из них. Пусть изначаль-но все окно черное. Нарисуем горизонтальную зеленую линию посередине окна на всю его ширину. Затем нарисуем вертикаль-ную красную линию на всю высоту окна. Можно заметить, что точка пересечения линий имеет красный цвет, соответствующий второй линии. Это происходит потому, что при рисовании зеленой линии все принадлежащие ей пиксели изменили цвет с предыдущего на зеленый, а предыдущий цвет перед еѐ рисованием был черный у всех пикселей. Соответственно при рисовании красной линии все принадлежащие ей пиксели изменили цвет с предыдущего на красный, но у пикселя, соответствующего точке пересечения, он был не черный, а зеленый, и он тоже стал крас-ным. Для большей наглядности вместо линий нарисуем закра-шенные прямоугольники шириной 20 пикселей. Тогда вся область пересечения двух прямоугольников будет красной, поскольку красный прямоугольник был нарисован вторым.

23

Можно сказать, что при рассмотрении изображения (поверх-ности в SDL, экрана монитора или окна программы) как дву-мерной матрицы пикселей, изменяющей свое состояние при рисовании на нем, цвет каждого конкретного пикселя соответствует цвету, полученному при последнем по времени изменении состоя-ния пикселя. То есть в первом приближении каждый после-дующий примитив можно считать нарисованным «поверх» ранее нарисованных.

Это позволяет сформулировать второй принцип построения сложных изображений – наложение графических примитивов од-ного или различных цветов друг на друга в последовательности, формирующей требуемое изображение. При этом если некоторый цвет принять за «цвет фона» и первоначально закрасить («залить») им все изображение (или принять за цвет фона цвет, установлен-ный по умолчанию, например черный), то при последующем наложении примитивов цвета фона получается эффект «стирания» части изображения, поскольку в этом месте пиксели приобретают цвет фона (фон начинает «просвечивать» сквозь изображение).

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

/* Рисование кольца.

surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы;

x0, y0, координаты центра окружности, rout – внешний радиус

rin – внутренний радиус

color – цвет кольца между rout и rin bgcolor – цвет "фона" внутри rin */

void Draw_Ring(SDL_Surface *surf,

Sint16 x0, Sint16 y0, Uint16 rout, Uint16 rin, Uint32 color, Uint32 bgcolor)

{

Draw_FillCircle(surf,x0,y0,rout,color); Draw_FillCircle(surf,x0,y0,rin,bgcolor);

}

Однако данная функция не поможет, если необходимо сквозь отверстие в кольце «увидеть», что было нарисовано до этого на месте отверстия, поскольку заливка внутреннего круга цветом фона уничтожает предшествующее изображение. Этого можно было бы избежать, если вместо наложения двух закрашенных кругов изобразить две вложенные окружности и затем закрасить только область между ними. Поскольку в библиотеке SDL_draw и собственно в библиотеке SDL таких функций нет, рассмотрим алгоритм заливки произвольной области, ограниченной замкнутым контуром без самопересечений известного цвета (как совпа-дающего с цветом заливки, так и отличающегося от него).

Предварительно введем определение непосредственных и косвенных соседей пикселя. Непосредственными соседями (или 4-соседями) называются пиксели, если у них различается только одна из координат и притом только на единицу. Косвенными сосе-дями (или 8-соседями) называются пиксели, если у них различаются горизонтальная или вертикальная координаты (воз-можно, одновременно), но не более чем на единицу. Любой непосредственный сосед является и косвенным соседом. Любой пиксель, кроме находящихся на границе поверхности (области отображения, экрана, окна программы), имеет четыре непосред-ственных и восемь косвенных соседей. Пиксели, находящиеся на границе поверхности, но не являющиеся угловыми, имеют три непосредственных и пять косвенных соседей, угловые пиксели –два непосредственных и три косвенных соседа.

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

Поместить в стек координаты затравочной точки (x_init, y_init); ПОКА стек не пуст, ПОВТОРЯТЬ:

Извлечь из вершины стека координаты точки (x, y); ЕСЛИ цвет точки (x, y) не равен internal_color ТО

24

Закрасить точку (x, y) цветом internal_color; Получить список соседних точек для (x, y);

ДЛЯ каждой точки (xa, ya) из списка соседних точек ВЫПОЛНИТЬ:

ЕСЛИ цвет точки (xa, ya) не равен internal_color И цвет точки (xa, ya) не равен border_color ТО Поместить координаты точки (xa, ya) в стек;

КОНЕЦ цикла по списку соседних точек; КОНЕЦ цикла выборки точек из стека.

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

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

Рассмотрим варианты реализации алгоритма. Если при реализации в качестве стека использовать стек вызова функций, то определение функции будет следующим:

/* Рекурсивная функция заливки произвольного контура. surf – поверхность, на которой находится контур.

x, y – координаты затравочной точки внутри контура при первом вызове; при последующих – координаты одного из соседей предыдущей точки.

internal color – цвет заливки.

border_color – цвет контура, ограничивающего область*/ void recurs_fill(SDL_Surface *surf, Sint16 x, Sint16 y,

Uint32 internal_color, Uint32 border_color)

{

Point areal[4]; Point todraw[4];

int i,arealcnt,todrawcnt; printf("recurs_fill enter\n"); if(GetPixel(surf, x, y)!=internal_color)

DrawPixel(surf, x, y,internal_color); arealcnt=GetAreal4(surf, x, y, areal); todrawcnt = 0;

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

{

Uint32 color = GetPixel(surf, areal[i].x, areal[i].y); if(color != internal_color && color != border_color)

todraw[todrawcnt++]=areal[i];

}

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

recurs_fill(surf, todraw[i].x, todraw[i].y, internal_color, border_color);

}

Используемые дополнительные функции: Draw_Pixel – анало-гичная ранее рассмотренной функция закрашивания отдельного пикселя по его координатам на поверхности из библиотеки SDL_draw, Get_Pixel – обратная ей функция, возвращающая цвет пикселя на поверхности как беззнаковое 32-разрядное целое число, GetAreal4 – функция получения координат непосредственных соседей конкретной точки. Еѐ параметры: поверхность SDL (SDL_Surface*); x, y – координаты точки, непосредственных сосе-дей которой ищем; areal – массив для помещения координат сосе-дей в виде структуры Point (выходной параметр, в функцию должен передаваться массив не менее чем из четырех структур, из которых будут использовано от двух до четырех элементов). Функция возвращает фактическое число соседей конкретной точки.

25

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

/* Определяем структуру для задания элемента стека координат точек: */

struct StPointElem{ Point pnt;

struct StPointElem *prev;

};

typedef struct StPointElem PointElem;

/* Поместить в pnt стек. Возвращает 1 при успешном выполнении и 0 при неудаче (нехватке памяти и т.д.)*/

int PointElemPush(PointElem **stack, Point pnt)

{

PointElem *added; if(stack)

{

added = (PointElem*)malloc(sizeof(PointElem)); if(added)

{

added->pnt = pnt; if(*stack)

added->prev = *stack; else

added->prev = NULL; *stack = added; return 1;

}

else

return 0; /* не удалось выделить память */

}

else

return 0; /* нет самого стека */

}

/* Снять координаты точки с вершины стека. */ Point PointElemPop(PointElem **stack)

{

Point pnt; pnt.x = -1; pnt.y = -1; if(stack)

{

if(*stack)

{

PointElem *tmp = (*stack); pnt = tmp->pnt;

(*stack)= tmp->prev; free(tmp);

}

}

return pnt;

}

/* Параметры функции stack_fill совпадают с параметрами функции recursive_fill */

void stack_fill(SDL_Surface *surf, Sint16 x, Sint16 y, Uint32 internal_color, Uint32 border_color)

{

Point initpnt, wrkpnt, areal[4];

26

int i, arealcnt, status =

1;

PointElem *todraw = NULL;

/* стек точек */

Uint32 color;

 

initpnt.x=x;

 

initpnt.y=y;

 

if(PointElemPush(&todraw,

initpnt))

{

 

while(todraw && status)

/* стек не пустой */

{

 

wrkpnt=PointElemPop(&todraw); if(Get_Pixel(surf, wrkpnt.x, wrkpnt.y)!=internal_color)

Draw_Pixel(surf, wrkpnt.x, wrkpnt.y,internal_color); arealcnt=GetAreal4(surf, wrkpnt.x, wrkpnt.y, areal); for(i=0; i<arealcnt && status; i++)

{

color = Get_Pixel(surf, areal[i].x, areal[i].y); status = PointElemPush(&todraw, areal[i]);

}

}

}

}

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

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

Поместить в стек координаты затравочной точки (x_init, y_init); ПОКА стек не пуст, ПОВТОРЯТЬ:

Извлечь из вершины стека координаты точки (x, y); ЕСЛИ цвет точки (x, y) не равен internal_color ТО

Закрасить точку (x, y) цветом internal_color;

Заполняем максимально возможный интервал от точки (x, y) вправо и влево до достижения граничных точек (имеющих цвет internal_color или border_color) или края поверхности;

Запоминаем крайнюю левую xl и крайнюю правую xr абсциссы полученного интервала;

Всоседних строках над и под интервалом (xl, xr) находим интервалы, состоящие из незаполненных (цвет которых неравен internal_color или border_color) к данному моменту

точек, координаты точки, соответствующей правому концу каждого такого интервала помещаем в стек;

КОНЕЦ цикла выборки точек из стека.

Пример реализации алгоритма на основе интервалов с добавлением необходимых проверок принадлежности анализи-руемых точек поверхности:

/* Параметры функции stack_interv_fill совпадают c параметрами функции stack_fill */

void stack_interv_fill(SDL_Surface *surf, Sint16 x, Sint16 y, Uint32 internal_color, Uint32 border_color)

{

Point initpnt, wrkpnt, intervpnt; int i, arealcnt, status = 1L;

27

PointElem *todraw = NULL; /* стек точек */

Uint32 color; initpnt.x=x; initpnt.y=y;

Sint16 xl, xr; /* крайние точки интервала внутри области */ Sint16 xc, yc; /* текущая точка при заполнении интервала */ Uint32 is_internal = 0L; /* находимся ли внутри интервала*/ if(PointElemPush(&todraw, initpnt))

{

while(todraw && status) /* стек не пустой – не равен NULL */

{

/* Проверяем затравочную точку */ wrkpnt=PointElemPop(&todraw); if(Get_Pixel(surf, wrkpnt.x, wrkpnt.y)!=internal_color)

{

Draw_Pixel(surf, wrkpnt.x, wrkpnt.y,internal_color); xl = xr = wrkpnt.x;

}

/* Идем от нее по горизонтали влево */ yc = wrkpnt.y;

xc = wrkpnt.x-1; if(xc >= 0)

color = Get_Pixel(surf, xc, yc); while(color != internal_color &&

color != border_color && xc >=0)

{

Draw_Pixel(surf, xc, yc, internal_color); xl = xc;

xc–;

if(xc >= 0)

color = Get_Pixel(surf, xc, yc);

}

/* Идем от нее по горизонтали вправо */ xc = wrkpnt.x+1;

if(xc < surf->w)

color = Get_Pixel(surf, xc, yc); while(color != internal_color &&

color != border_color && xc < surf->w)

{

Draw_Pixel(surf, xc, yc, internal_color); xr = xc;

xc++;

if(xc < surf->w)

color = Get_Pixel(surf, xc, yc);

}

/* Ищем непрерывные интервалы в строках над заполненной */

yc = wrkpnt.y - 1; if(yc >= 0)

{

for(xc = xl; xc <= xr; xc++)

{

color = Get_Pixel(surf, xc, yc); if(color != internal_color &&

color != border_color)

{

if(!is_internal)

is_internal = 1L; /* Начало внутреннего интервала */

}

else

if(is_internal) /* Конец внутреннего интервала */

{

28

is_internal = 0L; intervpnt.y = yc; intervpnt.x = xc-1;

status = PointElemPush(&todraw, intervpnt);

}

/* А если интервал и не начинался – ничего не делаем */

}

if(is_internal)

{/* Принудительно завершаем последний интервал */ intervpnt.y = yc;

intervpnt.x = xr;

status = PointElemPush(&todraw, intervpnt);

is_internal = 0L;

}

}

/* Ищем непрерывные интервалы в строках под заполненной */

yc = wrkpnt.y + 1; if(yc < surf->h)

{

for(xc = xl; xc <= xr; xc++)

{

color = Get_Pixel(surf, xc, yc); if(color != internal_color &&

color != border_color)

{

if(!is_internal)

is_internal = 1L; /* Начало внутреннего интервала */

}

else

if(is_internal) /* Конец внутреннего интервала */

{

is_internal = 0L; intervpnt.y = yc; intervpnt.x = xc-1;

status = PointElemPush(&todraw, intervpnt);

}

/* А если интервал и не начинался – ничего не делаем */

}

if(is_internal)

{/* Принудительно завершаем последний интервал */ intervpnt.y = yc;

intervpnt.x = xr;

status = PointElemPush(&todraw, intervpnt); is_internal = 0L;

}

}

}

}

}

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

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

29

Соседние файлы в предмете Программирование