- •Введение. Работа с клавиатурой и дисплеем
- •3. Подключение и инициализация библиотеки SDL
- •4. Графические примитивы библиотеки SDL_draw
- •5. Рисование сложных фигур
- •6. Работа с поверхностями в библиотеке SDL
- •7. Имитация движения при выводе на дисплей
- •8. Вывод текста с помощью библиотеки SDL_ttf
- •9. Обработка событий средствами библиотеки SDL
- •10. Построение графиков функций на дискретных устройствах отображения информации
- •Библиографический список
void SDL_FreeSurface(SDL_Surface *surface);
Она освобождает ресурсы, используемые ранее созданной поверхностью, указанной единственным аргументом. При этом если поверхность была создана функцией SDL_CreateRGBSurfaceFrom, то данные об атрибутах пикселей поверхности (surface→pixels) не освобождаются, поскольку были выделены извне библиотеки SDL и структура surface не является их владельцем.
Пример, демонстрирующий возможные комбинации состояний флагов SDL_SRCCOLORKEY, SDL_SRCALPHA, значений полей format→Amask, format→colorkey и format→alpha
накладываемой и целевой поверхности и их влияние на результаты приведен в приложении 1.
В дополнение к рассмотренным функциям библиотека SDL позволяет создавать новые поверхности в памяти на основе внешнего файла изображения в формате Windows BMP или сохранять текущее состояние существующей во внешнем файле в том же формате. Для этого служат функции SDL_LoadBMP и SDL_SaveBMP:
SDL_Surface *SDL_LoadBMP(const char *file);
int SDL_SaveBMP(SDL_Surface *surface, const char *file);
Единственный параметр функции SDL_LoadBMP – указатель на начало последовательности байтов, заканчивающейся нулем и представляющей имя существующего файла изображения в фор-мате WindowsBMP в кодировке utf-8. Должно быть указано имя по правилам используемой операционной системы (либо полное имя, либо относительно текущего каталога, как правило – каталога с исполняемым файлом программы). При успешном выполнении функция возвращает указатель на созданную новую поверхность, формат и состояние пикселей которой определяются содержа-
нием файла изображения. В случае ошибки функция возвращает NULL.
Первый параметр функции SDL_SaveBMP – указатель на по-верхность, состояние которой следует сохранить в файле в фор-мате Windows BMP, второй параметр – указатель на начало после-довательности байтов, заканчивающейся нулем и представляющей имя создаваемого файла изображения в кодировке utf-8. Должно быть указано имя по правилам используемой
операционной |
системы (либо полное |
имя, |
либо относительно |
текущего |
ката- |
лога, как правило – каталога с исполняемым файлом программы). Если |
такой файл |
уже |
|||
существует, |
то |
он |
будет |
перезаписан. |
|
При успешном выполнении функция возвращает 0, в случае ошибки – –1. |
|
|
7. Имитация движения при выводе на дисплей
Имитация движения при выводе на дисплей (монитор) обусловливается инерционностью зрения человека: несколько неподвижных изображений некоторого объекта, на каждом из которых этот объект смещен относительно фона, при последо-вательном предъявлении с достаточно небольшими интервалами воспринимаются как перемещение данного объекта относительно фона. Такой же принцип имитации движения используется в кинематографии и телевидении.
При программном построении движущихся по экрану мони-тора изображений может использоваться ряд способов, обеспе-чивающих видимое смещение движущихся объектов относительно фона и прочих неподвижных объектов.
Самый простейший способ – рисование на мониторе всего изображения (кадра), задержка для его демонстрации, стирание и рисование следующего кадра с новым относительным расположением объектов.
Достоинством способа можно считать возможность непосред-ственного отображения отдельно смоделированного простран-ственного положения подвижных и неподвижных объектов, отсутствие необходимости запоминать предшествующее изобра-жение на мониторе или его фрагменты, универсальность исполь-зования как при продолжении отображения движения, так и при полной смене изображения.
Основным недостатком способа являются требования по быстродействию для обеспечения такой частоты смены кадров, которая бы не вызывала у человека ощущения мерцания экрана. В зависимости от разрешения и размера изображения приемлемой частотой смены кадров может
38
быть частота от 25 до 100 кадров в секунду. При этом если изображение формируется непосредственно в видеопамяти (непосредственно рисуется на экране), а аппаратная частота обновления экрана, по данным видеопамяти, превосходит требуемую частоту кадров, возможна ситуация, когда видеоадаптер за расчетное время отображения одного кадра отобразит несколько кадров, на части которых изображение в кадре будет еще не дорисовано до конца. Чтобы избежать такой ситуации, изображение кадра должно быть сначала полностью построено в некоторой отдельной области памяти и только потом перенесено в видеопамять. Поскольку в библиотеке SDL исходно используется принудительное обновление отображаемой области по готовности изображения, то для устранения влияния недо-строенных кадров достаточно корректно расставить вызовы функций обновления экрана и задержки для отображения. Приведем пример, показывающий реализацию способа с покадровой пере-рисовкой:
SDL_Surface *scr; /* Основная поверхность отображения */
void Draw_Background(SDL_Surface *surf); /* Рисует на поверхности surf неподвижный фон, всегда одинаковый
– реализация не приводится. При этом все предыдущее изображение удаляется */
void Draw_MovedObject(SDL_Surface *surf, Uint32 posnum); /* – Рисует на поверхности surf изображение некоторого движущегося объекта – один раз, в позиции номер posnum (пересчет номера позиции в конкретные координаты – зависит от конкретного изображения и не приводится,
как и остальная реализация */
void Draw_Frame(SDL_Surface *surf, Uint32 framenum) /* – Рисует на поверхности surf один кадр
из последовательности, показывающей движение объекта. Номер кадра передается вторым параметром framenum. Тело функции ниже: */
{
Draw_Background(surf); /* Фон одинаковый */ Draw_MovedObject(surf, framenum); /* На каждом кадре – новая позиция объекта *
}
/* Инициализация библиотеки SDL и создание поверхности scr не приводятся */
/* Цикл отображения ролика примерно на 1 минуту, всего 60 секунд * 25 кадров в секунду = 1500 кадров */
int framecnt = 0;
Uint32 before_next_frame = 40; /* 40 миллисекунд – задержка между перерисовкой кадров, если считать, что собственно рисование одного кадра ОЧЕНЬ быстрое */
/* Первый кадр – без задержек: */ Draw_Frame(sсr, framecnt++ ); /* Первый кадр */ while(framecnt < 1500)
{
SDL_Flip(scr); /* Отображение кадра на экране */
SDL_Delay(before_next_frame);
Draw_Frame(sсr, framecnt++ ); /* следующий кадр */
}
Альтернативой перерисовке всего кадра является перерисовка только его изменяющейся части, включающая два этапа: 1) восстановление фонового изображения на месте предыдущего изображения движущегося объекта; 2) рисование движущегося объекта в новом положении.
Каждый из этапов, в зависимости от сложности изображения (фонового или накладываемого), имеющихся ресурсов и средств программирования отображения, может быть реализован либо с помощью собственно рисования (попиксельного, с помощью при-митивов и т.п.) участка фона или собственно движущегося изо-бражения, либо с помощью операций над областями изображений.
В первом варианте проблемы возникают при достаточно слож-ном фоновом рисунке, если для него не удается создать отно-сительно простую функцию рисования фрагмента заданной фор-мы, находящегося в некоторой окрестности указанной точки. Для рисования самого движущегося
39
объекта дополнительных проблем, по сравнению с покадровым рисованием, не возникает. В целом, по сравнению с покадровым рисованием, данный способ является более быстродействующим, особенно если изменяется небольшой фрагмент изображения по отношению к неизменному фону или имеется несколько таких небольших перемещающихся объектов, возможно, с различными законами движения, а рисование фраг-мента фона в заданных координатах не представляет трудности.
Во втором случае идея рисования выглядит иначе. Фон является заданным и даже столь сложным, что его можно изобразить только целиком. Фрагмент фона в том месте, где будет выведен перемещающийся объект, копируется в некоторый буфер, затем происходит рисование кадра с текущим положением движу-щегося объекта прямо на исходном фоне, а перед рисованием следующего кадра сохраненный фрагмент фона восстанавливается на прежнем месте. Сам движущийся объект тоже может быть нари-сован однократно (если изменяется только его позиция) и на каж-дом кадре только накладываться в новом месте на фоне. Но для этого либо его форма должна совпадать с имеющимися возмож-ностями программных средств по копированию и наложению изо-бражений, либо потребуется явное указание, какие пиксели накла-дываемого изображения на самом деле прозрачны и не должны изменять состояние пикселей фона.
Реализация движения с непосредственным частичным измене-нием изображения является очевидной, но сильно зависящей от конкретной задачи, поэтому отдельный пример рассматриваться не будет, а подобный способ построения движущихся изобра-жений будет приведен далее при рассмотрении обработки событий от клавиатуры.
Рассмотрим пример демонстрации полета вертолета на фоне облачного неба. Само небо формируется заливкой голубым, с последующим нанесением в случайных местах белых и слегка сероватых эллипсов – облаков. Вертолет для имитации вращения винтов на разных кадрах рисуется с разным их положением. Создается четыре поверхности: одна – для отображения, вторая – для рисования вертолета (в разных положениях), третья – для однократного рисования на ней фона (без неѐ можно обойтись, рисуя фон непосредственно на поверхности для отображения) и четвертая – для временного сохранения фрагмента фона, затирае-мого вертолетом. Программа будет выглядеть следующим образом:
#include <stdlib.h> #include <SDL.h> #include <math.h> #include <SDL_draw.h>
const int scrwidth = 1027, scrheight = 768, scrdepth = 32; void draw_heli(SDL_Surface *surf, int centre_x, int centre_y,
int phase_big, int phase_samll)
{
const int lw = 200, lh = 80; if(surf)
{
if(surf->w >= centre_x + lw/2 && surf->h >= centre_y + lh/2 &&
centre_x - lw/2 >= 0 && centre_y - lh/2 >= 0)
{
SDL_Rect dstarea; double phase;
Sint16 x1, y1, x2, y2;
Uint32 keycolor = SDL_MapRGB(surf->format, 0, 255, 0); Uint32 helicolor = SDL_MapRGB(surf->format,
100, 100, 255);
Uint32 phasecolor = SDL_MapRGB(surf->format, 255, 100, 200);
int i;
dstarea.x = centre_x - lw/2; dstarea.y = centre_y - lh/2; dstarea.w = lw;
dstarea.h = lh;
SDL_FillRect(surf, &dstarea, keycolor); Draw_FillEllipse(surf, centre_x + 40, centre_y + 10, 60, 30, helicolor);
Draw_FillEllipse(surf, centre_x + 60, centre_y,
40
25, 15, keycolor); dstarea.x = centre_x + 35; dstarea.y = centre_y - 30; dstarea.w = 10;
dstarea.h = 15;
SDL_FillRect(surf, &dstarea, helicolor); dstarea.x = centre_x - 80;
dstarea.y = centre_y - 20; dstarea.w = 120;
dstarea.h = 10;
SDL_FillRect(surf, &dstarea, helicolor); dstarea.x = centre_x - 80;
dstarea.y = centre_y - 30; dstarea.w = 10;
dstarea.h = 30;
SDL_FillRect(surf, &dstarea, helicolor); for(i = 0; i < 15; i++)
{
phase = M_PI / 180 * (phase_samll + i) ;
x1 = floor(centre_x - 75 - (15 - i/2)*cos(phase)); y1 = floor(centre_y - 25 + (15 - i/2)*sin(phase)); x2 = floor(centre_x - 75 + (15 - i/2)*cos(phase)); y2 = floor(centre_y - 25 - (15 - i/2)*sin(phase)); Draw_Line(surf, x1, y1, x2, y2, phasecolor);
}
for(i = 0; i < 15; i++)
{
phase = M_PI / 180 * (phase_big + i) ; x1 = centre_x + 40;
y1 = centre_y - 30;
x2 = floor(x1 + (60 - i/2)*cos(phase));
y2 = floor(y1 - (7 - i/2)); Draw_Line(surf, x1, y1, x2, y2, phasecolor); x2 = floor(x1 - (60 - i/2)*cos(phase)); Draw_Line(surf, x1, y1, x2, y2, phasecolor);
}
}
}
}
void draw_sky(SDL_Surface *surf, int cloud_cnt)
{
int i;
Sint16 x0, y0; Uint16 xr, yr; Uint8 cl;
Draw_FillRect(surf, 0, 0, surf->w, surf->h, SDL_MapRGB(surf->format,0,200,255));
for(i = 0; i < cloud_cnt; i++)
{
xr = floor((rand()*0.05)/RAND_MAX * surf->w); yr = floor((rand()*0.05)/RAND_MAX * surf->h); x0 = xr + floor((rand()*1.0)/RAND_MAX *
(surf->w - 2*xr));
y0 = yr + floor((rand()*1.0)/RAND_MAX * (surf->h - 2*yr));
cl = 220 + floor((rand()*1.0)/RAND_MAX * 35); Draw_FillEllipse(surf, x0, y0, xr, yr,
SDL_MapRGB(surf->format, cl, cl, cl));
}
}
int main ( int argc, char** argv )
{
41
SDL_Surface *background, *temp, *sprites; int frame_num;
SDL_Rect frame_src, frame_dst, frame_tmp; if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
printf( "Unable to init SDL: %s\n", SDL_GetError() ); return 1;
}
SDL_Surface* screen = SDL_SetVideoMode(scrwidth, scrheight, scrdepth, SDL_HWSURFACE|SDL_DOUBLEBUF);
if ( !screen )
{
printf("Unable to set 640x480 video: %s\n", SDL_GetError());
return 1;
}
background = SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_DOUBLEBUF, scrwidth, scrheight, scrdepth, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask);
temp = SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_DOUBLEBUF, 200, 80, scrdepth, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask);
sprites = SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_DOUBLEBUF, 200, 80*36, scrdepth, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask);
if( !(background && temp && sprites) )
{
printf("Unable to create temporary surfaces: %s\n", SDL_GetError());
return 1;
}
SDL_SetColorKey(sprites, SDL_SRCCOLORKEY, SDL_MapRGB(sprites->format, 0, 255, 0));
for(frame_num = 0; frame_num < 36; frame_num ++) draw_heli(sprites, 100, 40+80*frame_num,
10*frame_num, 10*frame_num); draw_sky(background, 250); SDL_BlitSurface(background, NULL, screen, NULL); int step = 0;
while (step < (50*60)) /* Примерно 1 минута */
{
frame_tmp.x = 0; frame_tmp.y = 0; frame_tmp.w = 200; frame_tmp.h = 80;
frame_dst.x = -200 + step % (scrwidth +200); frame_dst.y = scrheight/2 - 100; frame_dst.w = 200;
frame_dst.h = 80;
SDL_BlitSurface(screen, &frame_dst, temp, &frame_tmp); frame_src.x = 0;
frame_src.y = 80*(step % 36); frame_src.w = 200;
frame_src.h = 80;
SDL_BlitSurface(sprites, &frame_src, screen, &frame_dst); step++;
SDL_Flip(screen); SDL_Delay(20); /* 50 кадров/с */
SDL_BlitSurface(temp, &frame_tmp, screen, &frame_dst);
}
SDL_Quit();
42