Программирование в СИ
Кафедра: Автоматика и Информационные Технологии
ПРОГРАММИРОВАНИЕ В СИ РАБОТА В ГРАФИЧЕСКОМ РЕЖИМЕ
ОГЛАВЛЕНИЕ
1. ОБЗОР ГРАФИЧЕСКИХ ФУНКЦИЙ3
1.1. Общие сведения
1.2. Инициализация графического драйвера и режима
1.3. Работа с растром точек
1.4. Управление цветом
1.5. Базовые функции доступа к видеопамяти
1.6. Графические примитивы
1.7. Вывод графического текста
2. ПРИЕМЫ ПРОГРАММИРОВАНИЯ ГРАФИЧЕСКОГО РЕЖИМА
2.1. Подключение графической библиотеки
2.2. Инициализация графического режима
2.3. Включение драйвера и шрифтов в исполняемый файл
2.4. Рисование геометрических фигур
2.5. Выделение памяти под большие одномерные массивы
2.6. Вывод числовой информации
2.7. Задержка экрана
2.8. Реакция программы на нажатие конкретной клавиши
2.9. Организация ввода числовой информации
2.10.Проверка выхода аргумента функции из ОДЗ
2.11.Графическая и математическая системы координат
2.12.Использование двух видеостраниц
2.13.Рисование изображений в bmp-формате
2.14.Работа с мышью
3. ЗАДАНИЯ ДЛЯ ЛАБОРАТОРНОЙ РАБОТЫ
3.1. Звездное небо
3.2. Снегопад
3.3. Рисование графика функции
3.4. Вращение звезды
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
1.ОБЗОР ГРАФИЧЕСКИХ ФУНКЦИЙ
1.1.Общие сведения
Графический режим персональных компьютеров является более привлекательным, чем текстовый режим. Серьезные программные приложения, написанные под DOS, как правило, имеют графическую оболочку.
В самых общих чертах работа с дисплеем ПК в графическом режиме может быть представлена следующим образом. Экран дисплейного монитора представляется как набор отдельных точек – пикселей (pixels, от английского picture elements), образующий прямоугольный растр. Число пикселей определяет разрешающую способность (разрешение) графической системы и обычно отражается парой чисел, первое из которых показывает количество пикселей в строке, а второе – число строк. Каждому пикселю экрана ставится в соответствие фиксированное количество битов (атрибут пикселя) в некоторой области адресного пространства центрального микропроцессора ПК. Эта область, называемая видеопамятью, как правило, является частью дисплейного адаптера (видеоадаптера) – специального устройства, управляющего работой монитора. Видеоадаптер, в частности, осуществляет циклическое воспроизведение содержимого видеопамяти на экране монитора. Причем изображение каждого пикселя определяется текущим значением его атрибута. Такой подход получил название битовой карты – bit-mapped graphics. Программе, выполняющейся на ПК в графическом режиме, доступны для чтения и записи все пиксели видеопамяти.
В ряде случаев возможно одновременное существование в видеопамяти двух или более областей одинаковой структуры, каждая из которых содержит атрибуты всех пикселей экрана. Такие области называются страницами. В определенный момент времени любая из страниц может отображаться видеоадаптером на дисплее, занимая при этом весь экран. Наличие страниц позволяет программе быстро менять изображение на экране, просто переключаясь с одной страницы на другую. В частности, это дает возможность проводить всю “черновую работу” по подготовке графического изображения на неотображаемой в настоящий момент времени странице, избегая появления на экране побочных графических эффектов.
Графическое отображение, возникающее на экране монитора, является результатом выполнения следующих действий:
атрибуты пикселей изображения должны быть загружены в память. Обычно эту работу осуществляют специальные функции DOS или BIOS, однако возможна и прямая работа с видеопамятью;
специальная схема видеоадаптера обеспечивает периодическое считывание видеопамяти и преобразование значений атрибутов пикселей в последовательность сигналов, управляющих монитором.
В персональных компьютерах используются различные типы дисплейных адаптеров (CGA, EGA, VGA и др.), большинство из которых может работать в различных режимах (текстовых и графических), называемых также видеорежимами (video modes). Графические режимы одного адаптера различаются разрешающей способностью, количеством цветов, количеством страниц видеопамяти и способом их адресации. Установкой графического режима управляет прерывание BIOS с номером 10h.
Поскольку объем страницы видеопамяти ограничен, то количество битов, приходящиеся на один пиксель, находится в обратной зависимости от общего количества пикселей на экране. Обычно атрибут пикселя состоит из 1, 2, 4 или 8 бит, в зависимости от графического режима. Все пиксели, имеющие одинаковое значение атрибута, отображаются на экране одинаковым образом.
Если атрибуту каждого пикселя в видеопамяти отводится только один бит, то графика будет двухцветной, например черно-белой (конкретные цвета зависят от типа монитора). Если каждый пиксель в графическом режиме представляется n битами, то в таком режиме имеется возможность одновременно представить на экране N_pallette=2n оттенков цвета (палитра режима). В некоторых графических системах принято в этом случае говорить о наличии n плоскостей цветов (color planes).
В дисплейных адаптерах с монохромным монитором значение атрибута управляет интенсивностью одного электронного луча, т. е. яркостью точки на экране, а с цветным монитором – интенсивностью трех лучей, составляющих цветовые компоненты изображения пикселя. Как правило, используется разделение цвета на RGB-компоненты – красную, зеленую и синюю. Если каждая компонента имеет N градаций, то общее число цветовых оттенков для такого адаптера составляет N_colors=N3, при этом в число цветовых оттенков включаются черный, белый и градации серого.
Цветной видеоадаптер имеет схему, которая осуществляет во время развертки кадра преобразование значения атрибута каждого пикселя в сигналы управления интенсивностью электронных лучей монитора, отвечающих за RGB-компоненты. Работу схемы можно представить таблицей, по каждому входу которой хранится описание цвета, используемого адаптером при выводе на экран всех пикселей, значения атрибутов которых равны номеру этого входа. Такая схема называется картой или таблицей цветов. Если максимальный размер пикселя в графических режимах, поддерживаемых данным адаптером, представляется m битами, то таблица цветов такого адаптера содержит N_table=2m строк (входов). Все три цветовые компоненты в карте цветов представлены, как правило, двоичными числами одинаковой разрядности. Способ кодирования цвета зависит от типа видеоадаптера.
Программное управление цветами пикселей на экране дисплея может осуществляться без изменений значений атрибутов пикселей в видеопамяти. Для этого нужно загрузить соответствующие значения RGB-компонент в таблицу цветов по входу с номером, равным значению нужного атрибута. Изменения карты цветов немедленно отображаются на экране изменением цвета пикселей.
1.2.Инициализация графического драйвера и режима
Графическая система состоит из ядра и обширной библиотеки графических функций graphics.lib (ее нужно подключать при компоновке программного модуля). При создании программы, обращающейся к графическим функциям, в текст программы нужно включить файл, содержащий прототипы функций, константы, типы данных и различные перечислимые типы:
#include <graphics.h>
Графический интерфейс фирмы Borland International (BGI – Borland Graphics Interface) состоит из двух компонент: постоянного ядра графической системы и набора графических драйверов. Ядро графической системы воспринимает все запросы прикладной программы на выполнение графических функций. Оно не зависит от типа подключенного дисплейного адаптера. Аппаратно-зависимой частью являются графические драйверы, осуществляющие интерфейс между ядром системы и конкретным дисплейным адаптером.
Графические драйверы содержатся в отдельных файлах с расширением .bgi. Каждый файл содержит бинарный образ (binary image) драйвера для одного или нескольких близких по типу адаптеров. Для использования в программе за каждым драйвером закреплен постоянный номер, которому соответствует макроподстановка, например, EGA или VGA.
Графические драйверы поддерживают многие (но не все) графические режимы дисплейных адаптеров, предусмотренные системой BIOS. Для указания в программах графических режимов, как и для драйверов, предусмотрены макроподстановки (EGALO,EGAHI, VGAHI и другие).
Прежде чем обращаться к графическим функциям, программа должна выбрать графический драйвер, соответствующий дисплейному адаптеру, и подключить его к ядру графической системы. Графическая библиотека содержит функцию detectgraph, предназначенную для тестирования аппаратуры и автоматического выбора подходящего драйвера и графического режима. Прототип этой функции –
void far detectgraph(int far *graph_driver,int far *graph_mode);
Данная функция через свои аргументы возвращает номер графического драйвера и номер графического режима, обеспечивающего максимальное для адаптера разрешение. Возвращенные этой функцией значения в дальнейшем могут передаваться функции инициализации графической системы initgraph (но можно для инициализации выбрать и другой режим, поддерживаемый данным драйвером).
Если при тестировании аппаратуры дисплейного адаптера не обнаружено, то функция graphresult возвращает значение -2. Функция graphresult возвращает текущее значение внутренней переменной, содержащей код завершения работы графических функций. Ее прототип
int far graphresult(void);
Отрицательное значение кода завершения, установленное какой-либо графической функцией, хранится во внутренней переменной вплоть до вызова функции graphresult, после чего оно обнуляется.
Имеется возможность получить строку, содержащую описание любого из допустимых кодов завершения графических функций. Для чего существует функция grapherrormsg. Прототип функции –
char far *grapherrormsg(int error_code);
Если этой функции передать значение, возвращаемое функцией graphresult, то можно получить сообщение о коде завершения последней графической функции.
Графический драйвер должен быть помещен в оперативную память до того, как произойдет обращение к какой-нибудь функции графической библиотеки.
Простейшим способом включения драйвера в программу является его автоматическая загрузка при помощи функции initgraph с прототипом
void initgraph(int far *graph_driver,int far *graph_mode,char far *path_to_bgi);
Аргументами данной функции являются указатели на переменные, содержащие номер графического драйвера, номер графического режима и путь к BGI-файлу драйвера. Функция initgraph ищет на диске BGI-файл, содержащий требуемый драйвер, загружает файл целиком в динамически выделяемую память и настраивает ядро системы на работу с этим драйвером. Если инициализация прошла успешно, функция graphresult возвратит нулевое значение GR_Ok, в противном случае – одно из отрицательных значений, определенных в файле graphics.h.
Функция
void far closegraph(void)
прекращает работу графической системы. Она освобождает всю память, выделенную по запросам графических функций, очищает буфер видеоадаптера и восстанавливает текстовый режим, существовавший перед инициализацией графической системы.
Пример
автоматической загрузки
драйвера и инициализации
системы:
int main(void)
{
int gd,gm,err;
detectgraph(&gd,&gm); /* определение номера драйвера и режима */
err=graphresult(); /* получение кода завершения */
if(err) { printf(“\n%s”,grapherrormsg(err)); return 1; }
initgraph(&gd,&gm,”c:\\borlandc\\bgi”); /* инициализация системы */
err=graphresult(); /* получение кода завершения */
if(err) { printf(“\n%s”,grapherrormsg(err)); return 2; }
/* ................различные операторы..............*/
closegraph(); /* завершение работы системы */
return 0;
}
Можно не определять номера драйвера и режима тестированием аппаратуры, а задавать их по желанию, главное при этом соблюдать соответствие выбираемого драйвера и имеющегося в распоряжении видеоадаптера. Приведенный выше пример можно изменить тогда следующим способом:
int main(void)
{
int gd=VGA,gm=VGAHI,err;
initgraph(&gd,&gm,”c:\\borlandc\\bgi”);
err=graphresult();
if(err)
{
printf(“\n%s”,grapherrormsg(err));
return 1;
}
/* ................различные операторы..............*/
closegraph();
return 0;
}
Главным недостатком автоматической загрузки драйвера при помощи функции initgraph является то, что она обращается к диску для чтения BGI-файла во время выполнения программы. Альтернативой автоматической загрузке графического драйвера является его статическое включение на этапе построения программы. Предварительно бинарный файл драйвера .bgi должен быть превращен в обычный объектный файл типа .obj специальной утилитой bgiobj.exe. Кстати, данная утилита используется также для конвертирования chr-файлов с графическими шрифтами в объектные модули. Полученные объектные модули подключаются на этапе компоновки.
В программе, прежде чем инициализировать графическую систему, необходимо нужные драйверы зарегистрировать, т. е. сообщить графической системе, что данный драйвер уже находится в оперативной памяти. Для этого существует функция registerbgidriver, которой нужно сообщить местоположение драйвера:
int registerbgidriver(void (*driver)(void));
Аргумент функции – имя указателя на место в памяти, содержащее регистрируемый драйвер. Имена подобных указателей уже определены в объектных файлах, созданных при помощи утилиты bgiobj.exe. Ниже приведены прототипы функций, имена которых нужно употреблять для стандартных драйверов:
void CGA_driver(void);
void EGAVGA_driver(void);
void IBM8514_driver(void);
void Herc_driver(void);
void ATT_driver(void);
void PC3270_driver(void).
Если регистрация прошла успешно, функция graphresult возвратит нулевое значение, в противном случае -4. После регистрации драйвера можно инициализировать графическую систему при помощи функции initfgraph, при этом ее третий параметр не используется (передается нулевая строка “”).
Пример статической загрузки драйвера и инициализации системы:
int main(void)
{
int gd,gm,err;
detectgraph(&gd,&gm);
err=graphresult();
if(err)
{
printf(“\n%s”,grapherrormsg(err));
return 1;
}
registerbgidriver(EGAVGA_driver);
err=graphresult(); /* получение кода завершения */
if(err)
{
printf(“\n%s”,grapherrormsg(err));
return 1;
}
initgraph(&gd,&gm,""); /* инициализация системы */
if((err = graphresult()) != grOk)
{
printf(“\n%s”,grapherrormsg(err));
return 1;
}
/* ................различные операторы..............*/
closegraph(); /* завершение работы системы */
return 0;
}
Если графическая система активизирована и необходимо по ходу выполнения программы переключиться на использование другого графического драйвера, то до повторного вызова функции initgraph необходимо сбросить графическую систему функцией closegraph для освобождения всей памяти, которую занимала система.
Имя текущего графического драйвера можно узнать с помощью функции
char far *getdrivername(void);
Она возвращает указатель на строку, содержащую имя активного в данный момент драйвера.
Максимальное значение номера графического режима, допустимое для текущего графического драйвера, можно узнать с помощью функции
int far getmaxmode(void);
Текущее значение номера графического режима для активизированного драйвера возвращает функция
int far getgraphmode(void);
По номеру режима можно получить строку с описанием данного режима для текущего драйвера. Это делается с помощью функции
char far *getmodename(int mode_number);
В графической библиотеке есть функция определения минимально и максимально допустимых значений номера графического режима для графического драйвера, номер которого ей передается (необязательно активизированного)
void far getmoderange(int graph_driver,int far *min_mode,int far *max_mode);
Есть две функции, позволяющие изменять установленный графический режим без повторного обращения к функции initgraph и даже переходить временно в текстовый режим работы видеоадаптера. Если нужно перейти в другой графический режим активизированного в данный момент драйвера, то можно воспользоваться функцией
void far setgraphmode(int new_mode);
Аргумент new_mode передает желаемый номер режима для текущего драйвера и не должен превосходить максимально допустимое для этого драйвера значение.
Для
временного перехода в текстовый режим
предусмотрена
функция
void far restorecrtmode(void);
Эта функция переводит видеоадаптер в тот текстовый режим, в котором он находился в момент последней инициализации графической системы, т. е. непосредственно перед обращением к функции initgraph. Из текстового режима можно вернуться в графический при помощи функции setgraphmode.
При инициализации графической системы всевозможные параметры системы устанавливаются по умолчанию. Различные функции могут менять значения параметров. Для того чтобы в любой момент восстановить характеристики системы, установленные при ее инициализации, существует функция
void far graphdefaults(void);
Перечислим действия, выполняемые данной функцией:
для вывода и отображения на экране выбирается нулевая страница видеопамяти;
графическое окно устанавливается размером во всю страницу;
текущая графическая позиция перемещается в точку (0,0);
устанавливаются по умолчанию цвета палитры, текущий рисующий цвет (15) и цвет фона (0);
устанавливается сплошной шаблон для рисования линий и заполнения областей;
инициализируется встроенный матричный шрифт со стандартным расположением и позиционированием строки.
1.3.Работа с растром точек
Растром точек называется двумерная совокупность точек, представляющая экран дисплея.
Чтобы в прикладной программе иметь возможность отобразить на экране любую из имеющихся страниц видеопамяти, в графической библиотеке предусмотрена функция
void far setvisualpage(int page);
Функция немедленно отображает на экране ту страницу видеопамяти, номер которой был ей передан в качестве аргумента. Страницы нумеруются с нуля. Функция graphresult не реагирует на попытку установить недопустимый номер страницы. Вся ответственность за правильность указанного номера лежит на программисте. Функция
void far setactivepage(int page);
не вызывает перерисовки страницы на экране дисплея, но зато направляет весь последующий графический вывод на ту страницу, которая указана ее аргументом. Как и при вызове предыдущей функции, ответственность за допустимость номера страницы остается на программисте.
Страницу видеопамяти (и экран дисплея) можно представить как двумерный прямоугольный массив точек (пикселей). На этом массиве точек вводится система координат X, Y. Начало системы лежит в левом верхнем углу страницы (экрана). Ось X проходит по верхнему краю страницы слева направо, а ось Y – по левому краю сверху вниз. Левая верхняя точка страницы имеет координаты (0, 0), правая нижняя – координаты (M-1, N-1), где M и N – размеры страниц по горизонтали и вертикали.
Определить максимальные значения координат точек можно с помощью функций
int far getmaxx(void);
int far getmaxy(void);
Значения, возвращаемые этими функциями, зависят только от текущего режима, установленного функциями initgraph или setgraphmode.
В распоряжении программиста кроме страницы как целого имеется еще одна двумерная структура. Внутри основного массива точек страницы всегда выделен некоторый его подмассив, который называется графическим окном (viewport). Графическое окно является прямоугольным массивом точек со своей системой координат. Начало этой системы координат находится в левом верхнем углу графического окна, а оси X и Y параллельны соответствующим осям координат страницы. Само окно имеет переменные размеры и может располагаться в любом месте экрана. Замена страницы никак не влияет на характеристики окна.
Многие функции, использующие координаты точек, подразумевают именно систему координат графического окна. Благодаря этому появляется возможность использовать одну и ту же программу для выполнения некоторой графической работы в окне независимо от того, в каком месте страницы и даже на какой именно странице окно находится в данный момент. В дальнейшем при описании таких функций всегда будет указываться, какая система координат (страницы или окна) имеется в виду.
При установке графического режима при помощи функций initgraph и setgraphmode сразу же создается и графическое окно, совпадающее по размерам со всей страницей. Однако есть возможность управлять размерами и расположением графического окна динамически. Делается это с помощью функции
void far setviewport(int left.int top,int right,int bottom,int clip);
Первые четыре аргумента – это координаты левой верхней и правой нижней границ графического окна в системе координат страницы. Ни одна из границ окна не может лежать за пределами страницы. Последний аргумент устанавливает режим отсечения: если он не нулевой, то всякий графический вывод будет обрезаться на границах графического окна. Если при вызове фунции setviewport были неверно заданы аргументы, то функция graphresult возвратит -11 и сохранится предыдущая установка графического окна. Функция setviewport не меняет содержимое страницы видеопамяти.
Для того чтобы можно было в любой момент узнать текущую установку графического окна, существует функция
void far getviewsettings(struct viewporttype far *viewport);
Данная функция помещает параметры текущего окна в структуру *viewport. Тип этой структуры определен в файле graphics.h:
struct viewporttype
{
int left,top,right,bottom;
int clip;
}
Возвращаемые координаты представлены в системе координат страницы.
С графическим окном связано понятие текущей графической позиции CP (current graphics position). Это графический эквивалент курсора в текстовом режиме. Текущая графическая позиция сама собой никак не отображается на экране и идентифицирует выбранный пиксель графического окна, к которому привязывается действие некоторых функций, таких как вычерчивание прямолинейных отрезков или вывод графических текстов.
При установке нового графического окна текущая позиция автоматически помещается в его начало координат. Для явного изменения положения текущей позиции используются функции
void far moveto(int x, int y);
void far moverel(int dx, int dy);
Первая помещает CP по указанным координатам графического окна. Вторая перемещает CP на вектор (dx, dy).
Координаты текущей графической позиции в системе координат графического окна возвращают функции:
int far getx(void);
int far gety(void);
При переустановке графического окна функцией setviewport содержимое страницы видеопамяти не изменяется. Для очистки графического окна на активной в данный момент странице используется функция
void far clearviewport(void);
CP при этом перемещается в начало координат графического окна.
Похожая функция
void far cleardevice(void);
очищает всю активную страницу. Установка графического окна при этом не изменяется, а CP перемещается в его начало координат.
На многих мониторах пиксель, который высвечивается на экране, имеет форму прямоугольника, вытянутого по вертикали. Это объясняется тем, что на дисплее страница видеобуфера отображается на весь экран, а пропорции экрана и страницы в режиме максимального разрешения, как правило, не совпадают (исключение – мониторы с адаптерами VGA, где в режиме с разрешением 640 х 480 точек все пиксели квадратные). Такое несоответствие приводит к возникновению анизотропности растра пикселей: горизонтальный и вертикальный отрезки, содержащие одинаковое число пикселей, на экране будут выглядеть, как отрезки разной длины.
Тем не менее, окружности и их дуги рисуются функциями библиотеки правильно, так как эти функции используют хранящийся в графической системе корректирующий коэффициент пропорциональности (aspect ratio), учитывающий “степень неквадратности” пикселя. Чтобы правильно нарисовать квадрат, необходимо произвести корректировку количества пикселей по его горизонтальной и вертикальной сторонам. Истинные пропорции пикселя на данном дисплее можно узнать с помощью функции
void far getaspectratio(int far *x_asp,int far *y_asp);
Данная функция через свои аргументы возвращает искомое значение, причем *y_asp всегда устанавливается равным 10 000, а величина *x_asp ≤ *y_asp. Отношение *x_asp к *y_asp как раз и есть отношение горизонтального и вертикального размеров пикселя. Теперь, если горизонтальная сторона квадрата представлена отрезком длиной X пикселей, то длина вертикальной стороны должна быть равна значению выражения
Y = (int)(X ∙ (float)(*x_asp) / (*y_asp)).
Заметим, что коэффициент “неквадратности” автоматически учитывается только функциями, которые рисуют окружности и их дуги, но никак не влияет на функции рисования эллипсов и их дуг.
1.4.Управление цветом
Цветовые возможности функций графической библиотеки Borland C описываются в терминах цветовой палитры режима – закона, по которому каждому допустимому значению атрибута пикселя ставится в соответствие цвет, которым этот пиксель будет отображаться на экране.
Палитру режима можно представить как таблицу, содержащую столько строк (входов), сколько значений допускается для атрибута пикселя в данном графическом режиме. Строки палитры режима нумеруются от нуля до N_palette-1. В строке с номером k содержится код цвета, которым аппаратура видеоадаптера отображает на экране все пиксели страницы, атрибуты которых равны k. Нулевой вход палитры режима, кроме того, определяет цвет фона экрана.
Все графические режимы можно разделить на три группы:
монохромные режимы, в которых все пиксели могут быть двух цветов – основного и фонового. Палитру таких режимов изменить невозможно;
цветные режимы с фиксированной палитрой. Для изменения палитры режима нужно менять графический режим, что приводит к потере содержимого видеопамяти;
графические режимы, позволяющие динамически (без потери содержимого видеопамяти) изменять код цвета по любому входу палитры режима. К данным режимам относятся режимы драйверов EGA, VGA, IBM8514.
Механизм управления цветом в Borland C включает в себя важный элемент – структуру данных, называемую внутренней палитрой. При работе в графических режимах на всех дисплейных адаптерах, кроме VGA и IBM8514, она содержит коды цветов из таблицы цветов адаптера. Роль внутренней палитры при работе с адаптером VGA несколько иная и будет рассмотрена ниже. Драйвер IMB8514 вообще не пользуется внутренней палитрой, поэтому в дальнейшем все, что будет говориться о работе с внутренней палитрой, не относится к этому драйверу. Не следует путать внутреннюю палитру (обычную переменную структурного типа) графической системы с палитрой режима с законом преобразования значения пикселя в цвет.
Определение внутренней палитры как структуры данных имеет вид:
#define MAXCOLOR 15
struct palettetype
{
unsigned char size;
signed char colors[MAX_COLORS+1];
};
где size – это число строк внутренней палитры, разрешенное для использования в текущем графическом режиме, а colors – массив закодированных описаний цвета.
Для всех монохромных режимов и режимов с фиксированными палитрами допускается использование во внутренней палитре 16 цветов, которые в точности совпадают со стандартными цветами текстового режима. Каждый цвет палитры кодируется числом от 0 до 15. При инициализации таких графических режимов значение каждого элемента массива colors совпадает с его индексом.
В
режимах с динамическим управлением
внутренняя палитра содержит 16 цветов,
коды которых могут лежать в диапазоне
от 0 до 63
(т. е. имеется возможность
менять содержание внутренней палитры).
Для 16 кодов цветов стандартного набора,
устанавливаемых при инициализации этих
режимов (как для драйвера EGA, так и VGA),
определен перечислимый тип EGA_COLORS, т. е.
стандартные коды цветов имеют символические
имена:
enum EGA_COLORS
{
EGA_BLACK=0, EGA_BLUE=1, EGA_GREEN=2, EGA_CYAN=3, EGA_RED=4, EGA_MAGENTA=5, EGA_BROWN=20, EGA_LIGHTGRAY=7, EGA_DARKGRAY=56, EGA_LIGHTBLUE=57, EGA_LIGHTGREEN=58, ЕGA_LIGHTCYAN=59, EGA_LIGHTRED=60, EGA_LIGHTMAGENTA=61, EGA_YELLOW=62, EGA_WHITE=63
};
Количество динамически управляемых строк таблицы цветов для текущего графического режима можно определить при помощи функции:
int far getpalettesize(void);
При определении реального цвета пикселя на экране значение его атрибута используется как индекс в таблице цветов дисплейного адаптера. Максимальное значение атрибута пикселя, которое разрешается использовать для записи в видеопамять такими функциями, как графические примитивы, возвращается функцией
int far getmaxcolor(void);
Непосредственное изменение внутренней палитры осуществляется с помощью функций setpalette или setallpalette. Первая изменяет содержание только одного входа палитры, а вторая всех сразу. Синтаксис первой функции:
void far setpalette(int num_color_pallete,int num_color);
Аргумент num_color_palette задает номер изменяемого входа внутренней палитры и должен лежать в пределах от нуля до size-1 (size – элемент структуры данных palettetype). Аргумент color задает новое значение кода цвета для этого входа в диапазоне от 0 до 63 (реально используются только шесть младших бит аргумента num_color).
Другая функция, изменяющая внутреннюю палитру, имеет синтаксис:
void far setallpalette(struct palettetype far *palette);
Аргумент ее является указателем на структуру, содержащую вариант желаемой палитры. Все изменения в текущей внутренней палитре, произведенные функциями setpalette и setallpalette немедленно отображаются на экране дисплея.
Цветом фона можно управлять с помощью функции
void far setbkcolor(int num_color_palette);
Действие функции заключается в том, что нулевой вход внутренней палитры связывается со входом, имеющим номер num_color_palette. Это означает, что в нулевой элемент внутренней палитры записывается значение, которое в данный момент содержится в элементе с номером num_color_palette. Кроме того, все последующие изменения содержимого элемента с номером num_color_palette будут немедленно дублироваться в нулевой элемент. Заметим, что функция setpalette(0, color) просто изменяет цвет фона, но не связывает нулевой вход ни с каким другим.
Обратной для функции установки цвета фона является функция
int far getbkcolor(void);
которая возвращает текущее назначение для фонового цвета, т.е. номер того входа внутренней палитры, с которым связан в настоящий момент нулевой вход. Если ранее при помощи функции setbkcolor не произошло связывания ни с каким входом, то getbkcolor возвращает 0.
Для получения информации о текущей внутренней палитре существует две функции:
void far getpalette(struct palettetype far *palette);
struct palettetype far *getdefaultpalette(void);
Первая копирует в область памяти, на которую указывает аргумент, текущее состояние внутренней палитры. Другая возвращает указатель на хранящуюся в графической системе структуру, содержащую эталон внутренней палитры. Получив указатель на эту структуру, можно изменить сам эталон палитры. Такие изменения будут сохраняться до следующей инициализации какого-либо режима.
Все вышеописанные способы динамического управления цветом рассчитаны на адаптеры, в которых пиксель может быть представлен не более чем четырьмя битами. Однако некоторые адаптеры допускают восьмибитовую величину атрибута пикселя (IBM8414 и VGA, имеющие в таблице цветов 256 входов). Для них существует расширенная палитра, которая содержит 256 входов. Каждый вход соответствует коду цвета. Для того чтобы какому-либо коду соответствовал другой оттенок цвета, используется функция
void far setrgbpalette(int num_color,int red,int green,int blue);
Аргумент num_color – номер входа расширенной палитры (0–255), по которому хранятся три цветовые компоненты – красная, зеленая и синяя – составляющие желаемый цвет. Функция позволяет записать по входу расширенной палитры с номером color цветовые компоненты red, green и blue. При этом реально используются только младшие шесть байт каждой компоненты. Таким образом, общее количество цветовых оттенков равно N_colors=64*64*64=256К.
Отметим некоторые особенности использования драйвера VGA. Значения элементов массива colors (кодов цветов) обычной внутренней палитры служат индексами для поиска нужного цвета в расширенной таблице, откуда происходит выбор нужного оттенка цвета.
Заметим, что функция setrgbpalette правильно работает только с дисплейными адаптерами VGA и IBM8514. На других конфигурациях ПК она игнорируется. Кстати, изменить палитру при работе с адаптером IBM8514 можно только при помощи функции setrgbpalette.
Рассмотрим теперь понятие текущего значения рисующего цвета (current drawing color). Рисующий цвет – это то значение, которое используется функциями рисования для записи в атрибуты пикселей. Значение рисующего цвета неотрицательно и не должно превышать значения, возвращаемого функцией getmaxcolor.
Существует функция, позволяющая явно изменять текущее значение рисующего цвета:
void far setcolor(int num_color_palette);
При рисовании используется цвет, чей код записан по входу внутренней палитры с номером num_color_palette. Узнать текущее значение рисующего цвета можно при помощи функции
int far getcolor(void);
1.5.Базовые функции доступа к видеопамяти
Практически вся работа с графикой сводится к обмену данными между программой и видеобуфером дисплейного адаптера.
Доступ к отдельным пикселям активной страницы осуществляют две функции:
unsigned far getpixel(int x,int y);
void far putpixel(int x,int y,int num_color_palette);
Функция getpixel возвращает атрибут (вход во внутреннюю палитру) пикселя с координатами (x, y). А функция putpixel рисует пиксель в точке с координатами (x, y) цветом, чей код содержится по входу во внутреннюю палитру с номером num_color_palette.
Несмотря на то, что функциям getpixel и pupixel координаты пикселя задаются в системе координат графического окна, ничто не мешает указать координаты любого пикселя страницы, лежащего вне окна. При этом getpixel возвращает правильное значение атрибута указанного пикселя. Поведение функции putpixel зависит от режима отсечения, установленного с помощью последнего аргумента при вызове функции setviewport. Результаты работы функций getpixel и pupixel с выходящими за пределы страницы координатами точек непредсказуемы.
Кроме обмена с видеопамятью отдельными пикселями графическая библиотека предоставляет возможности для обмена целыми массивами пикселей. Это бывает полезно, например, когда нужно временно сохранить какой-то фрагмент страницы, чтобы в дальнейшем восстановить его на прежнем или новом месте. Обмен производится прямоугольными массивами, которые задаются координатами своих углов в системе координат текущего графического окна.
Прежде чем сохранить фрагмент страницы, необходимо определить требуемый для этого объем оперативной памяти. Все необходимые вычисления производит функция
unsigned far imagesize(int left,int top,int right,int bottom);
Она получает в качестве аргументов координаты левого верхнего и правого нижнего угла сохраняемой области. Возвращаемое значение можно передавать одной из функций выделения памяти для резервирования необходимого пространства. Если объем памяти, требуемый для сохранения изображения, превосходит 64К-1, то функция все равно возвращает значение 0xFFFF, но при этом graphresult выдает значение -11.
После резервирования требуемого объема оперативной памяти, он может сохранить массив пикселей с помощью функции
void far getimage(int left,int top,int right,int bottom,void far *bitmap);
Последний аргумент – указатель на область памяти, где будет сохраняться массив.
Для того чтобы массив пикселей, сохраненный с помощью функции getimage, снова записать на активную страницу, используется функция
void far putimage(int left, int top, void far *bitmap, int op);
При этом можно не только указать новое местоположение массива пикселей (через координаты левого верхнего угла), но и вывести его на другую страницу видеопамяти, предварительно сделав ее активной при помощи функции setactivepage. Последний аргумент функции putimage указывает способ, которым атрибуты пикселей массива, выводимого из оперативной памяти на активную страницу, будут замещать уже находящиеся в видеобуфере значения. В простейшем случае, когда op = 0, происходит простое копирование атрибутов пикселей из памяти в видеопамять. Однако возможно выполнение одной из побитовых логических операций над содержимым оперативной памяти и видеобуфера для каждого пикселя массива. Полный набор таких операций задается перечислимым типом putimage_ops:
enum putimage_ops
{COPY_PUT, XOR_PUT, OR_PUT, AND_PUT, NOT_PUT};
При работе с функциями getimage и putimage действуют примерно те же правила относительно задаваемых координат, что и для функций getpixel и putpixel: хотя координаты задаются в системе графического окна, можно указать массив пикселей, расположенный в любом месте в пределах активной страницы. Различие между двумя функциями состоит в том, что getimage “схватит” этот массив, а функция putimage выведет массив на страницу так, как если бы никакого графического окна не существовало (т. е. игнорируется режим отсечения).
Если координаты задаваемого массива пикселей (весь массив или его часть) выходят за пределы страницы, то результат работы функций getimage и putimage непредсказуем.
1.6.Графические примитивы
Основное значение графических примитивов – обеспечить наличие программных средств для рисования всевозможных геометрических объектов. Условно можно разбить все графические примитивы по типу рисуемых ими графических объектов на две группы: контурные и площадные. Функции первой группы рисуют всевозможные контурные линии. Ко второй группе относятся функции, предназначенные для рисования геометрических фигур с закрашиванием ограничиваемых ими областей.
К группе контурных графических примитивов относятся функции:
void far line(int x1,int y1,int x2,int y2);
void far linerel(int dx,int dy);
void far lineto(int x,int y);
void far rectangle(int left,int top,int right,intbottom);
void far drawpoly(int num_points,int far *poly_points);
void far circle(int x,int y,int radius);
void far arc(int x,int y,int start_angle,int end_angle,int radius);
void far ellipse(int x,int y, int start_angle,int end_angle,int x_radius,int y_radius);
Первые пять рисуют кусочно-линейные объекты (в том числе и фигуры, составленные из отрезков прямых), остальные – кривые второго порядка (дуги окружностей и эллипсов).
Функции line, linerel и lineto соединяют две точки плоскости отрезком прямой. Для первой обе соединяемые точки указываются явно своими координатами. Функции linerel и lineto в качестве первой точки используют текущую графическую позицию CP, а вторую выбирают аналогично функциям moverel и moveto, т. е. через приращения координат или по явному указанию. Все три функции пользуются системой координат графического окна. Однако им можно передать координаты любых точек, даже лежащих за пределами страницы. Функция graphresult не сообщает об ошибке, а соединяющая линия проводится правильно. Если для окна установлен режим отсечения, то части линии, выходящие за пределы окна, не заносятся в видеопамять.
Функция rectangle рисует на странице видеопамяти контур прямоугольника по указанным координатам левого верхнего и правого нижнего угла.
Функция drawpoly рисует ломаную линию, соединяя точки на плоскости. В первом аргументе передается количество таких точек, а второй указывает на массив целых чисел. Каждая пара чисел из этого массива интерпретируется как пара координат (x, y) очередной точки. Для того чтобы нарисовать замкнутую ломаную линию (многоугольник), первая и последняя пары элементов массива должны быть одинаковыми.
Функция circle рисует окружность радиусом radius с центром в точке с координатами (x, y). Функция arc и ellipse вычерчивают дуги окружности и эллипса с центром в точке (x, y), соответственно ограниченные углами start_angle и end_engle. Для дуги окружности задается ее радиус radius, а для дуги эллипса радиусы по осям x_radius и y_radius. Оси эллипса всегда предполагаются параллельными осям координат страницы.
Углы, ограничивающие дуги, выражаются в градусах и отмеряются против часовой стрелки от направления, задаваемого осью X страницы. Дуга всегда проводится от угла start_angle к углу end_angle также против часовой стрелки.
Для функций rectangle, drawpoly, circle, arc и ellipse справедливы замечания относительно системы координат и режима отсечения, которые были сделаны относительно функций рисования линий.
С дугами окружностей связана функция
void far getarccoords(struct arccoordstype far *arccoords);
Эта функция возвращает характеристики дуги окружности, построенной при последнем вызове функции arc. Характеристики дуги записываются в переменную *arccoords. Тип этой переменной определен следующим способом:
struct arccoordstype
{
int x, y;
int x_start, y_start, x_end, y_end;
};
Первая пара чисел – это координаты центра окружности, вторая и третья – координаты начала и конца дуги. Значения координат привязаны к системе координат графического окна.
Существуют функции, которые позволяют варьировать внешний вид объектов, например толщину и тип линии, цвет и другие.
Все контурные графические примитивы прописывают пиксели в видеопамяти рисующим цветом, который можно изменить функцией setcolor.
Для кусочно-линейных графических примитивов, и только для них, имеется возможность указать способ, которым код рисующего цвета будет взаимодействовать с атрибутами пикселей, уже находящимися в видеопамяти на месте рисуемого объекта. Здесь действует механизм, описанный для функции putimage. Выбор способа осуществляется функцией
void far setwritemode(int mode);
Аргумент этой функции должен принимать значение 0 (простое копирование кода атрибута пикселя в видеопамять) и 1 (операция “исключающее или”).
Функция
void far setlinestyle(int line_style,unsigned user_pattern,int thickness);
устанавливает характер и толщину линий геометрических объектов. Аргумент thickness воздействует на контурные графические примитивы, а аргументы line_style и user_pattern – только на кусочно-линейные.
Аргумент thickness принимает значения NORM_WIDTH (толщина равна 1 пиксель) и THICK_WIDTH (толщина равна 3 пикселям). Аргумент linestyle задает характер рисуемой линии. Значения аргумента должны выбираться из констант перечислимого типа line_styles (например SOLID_LINE означает сплошную линию):
enum line_styles
{
SOLID_LINE=0,DOTTED_LINE,CENTER_LINE,
DASHED_LINE,USERBIT_LINE
};
Если значение аргумента line_style равно USERBIT_LINE, то это значит, что при построении кусочно-линейных примитивов будет использоваться шаблон, заданный программистом и переданный функции setlinestyle при помощи аргумента user_pattern. С помощью шаблона можно задать периодически повторяющийся рисунок линии с периодом до 16 пикселей. Если некоторый бит шаблона user_pattern равен 1, то соответствующий пиксель линии рисуется, в противном случае – нет.
Установки, сделанные при помощи функции setlinestyle, сохраняются до нового ее вызова. Для выяснения текущей установки характеристик линий предусмотрена функция
void far getlinesettingstype(struct linesettingstype far *line_info);
Данная функция заносит информацию в структуру, имеющую описание
struct linesettingstype
{
int linestyle;
unsigned upattern;
int thickness;
}
Группа площадных графических функций имеет прототипы:
void far bar(int left, int top, int right, int bottom);
void far bar3d(int left, int top, int right, int bottom, int depth, int top_flag);
void far fillpoly(int num_points, int far *poly_points);
void far fillellipse(int x, int y, int x_radius, int y_radius);
void far pieslice(int x, int y, int start_angle, int end_angle, int radius);
void far sector(int x, int y, int start_angle, int end_angle, int x_ radius, int y_radius);
void far floodfill(int x, int y, int num_color_palette);
Функции bar и bar3d строят прямоугольники, координаты которых заданы первыми четырьмя аргументами функций и закрашивают его внутреннюю область. Функция bar не выводит внешний контур прямоугольника, а функция bar3d дополнительно обрамляет прямоугольник контура и тем же контуром дорисовывает проекцию параллелепипеда, построенного на базе этого прямоугольника. Глубина проекции задается аргументом depth, аргумент top_flag указывает, рисовать (если не нуль) или не рисовать (если нуль) верхние ребра параллелепипеда.
Функция fillpoly получает аргументы аналогично функции drawpoly, рисует контур и заполняет его внутренность. Однако если функция drawpoly допускает незамкнутые контуры, то функция fillpoly всегда соединяет последнюю точку в полученном списке с первой, автоматически замыкая контур.
Функция fillellipse заполняет эллипс с центром в точке (x, y) и радиусами x_radius и y_radius. Кроме того, она рисует контур эллипса.
Функции pieslice и sector похожи тем, что обе закрашивают указанные сектора, только pieslice делает это для кругового сектора, а sector для эллиптического. Аргументы, которые им передаются, те же, что и для функций arc и ellipse соответственно. После того как сектор закрашен, рисуется его контур. В отличие от функций arc и ellipse, функции pieslice и sector строят сектор от меньшего значения угла к большему (а не от start_angle к end_angle). Из-за этого невозможно заставить функции pieslice и sector изобразить сектор, пересекающий положительное направление оси X.
Функция floodfill используется для закрашивания областей, уже существующих на странице. Для правильной работы функции необходимо, чтобы контур был замкнут и состоял из пикселей, имеющих значение атрибута, совпадающее с аргументом num_color_palette (номер входа внутренней палитры). Кроме кода контура, функция floodfill получает точку, от которой начинается заполнение области. Эта точка должна находиться внутри контура.
Все контуры в видеопамяти прописываются пикселями рисующего цвета, который можно изменить функцией setcolor. Желаемый режим изображения контура (например толщина линии) устанавливается так, как это делается для контурных примитивов.
Для управления видом заполнения внутренней области служит функция
void far setfillstyle(int pattern,int num_color_palette);
Данная функция одновременно устанавливает тип двумерного шаблона заполнения и код заполнения (номер входа во внутреннюю палитру). Нужно отметить, что при заполнении области нет возможности выполнять побитовые логические операции между кодами шаблона и атрибутами пикселей области, т. е. всегда происходит копирование в атрибуты пикселей области соответствующих кодов шаблона. Аргумент pattern указывает на тип устанавливаемого шаблона. Существует несколько заранее определенных типов, их символические имена определяются перечислимым типом fill_patterns:
enum fill_pattern
{
EMPTY_FILL=0,SOLID_FILL,LINE_FILL,LTSLASH_FILL,
SLASH_FILL,BKSLASH_FILL,LTBKSLASH_FILL,
HATCH_FILL,XHATCH_FILL,INTERLEAVE_FILL,
WIDE_DOT_FILL, CLOSE_DOT_FILL,USER_FILL
};
Аргумент pattern может принимать любые значения, кроме USER_FILL. Значение этой константы используется только при обработке информации, получаемой функцией getfillsettings (см. далее).
Шаблон условно можно представить в виде матрицы размером 8 х 8 элементов. Элементы матрицы шаблона имеют значения 0 или 1. Если элемент равен 1, то атрибуту соответствующего пикселя области будет присвоено значение num_color_palette, в противном случае атрибут пикселя получит значение кода фона.
Возможно создание дополнительных шаблонов непосредственно в прикладной программе. Для этого следует воспользоваться функцией
void far setfillpattern(char far *user_pattern,int num_color_palette);
Параметр user_pattern указывает на область, содержащую шаблон заполнения, описанный в программе. Второй параметр этой функции аналогичен такому же параметру функции setfillstyle. Данная область состоит из восьми последовательных байтов, цепочка битов каждого байта является соответствующей строкой матрицы шаблона.
Площадные графические примитивы используют параметры заполнения, установленные при последнем вызове функции функций setfillstyle или setfillpatern. Для того чтобы узнать текущие назначения для параметров заполнения областей, предусмотрены две функции
void far getfillsettings(struct fillsettingstype far *fill_info);
void far getfillpattern(char far *user_pattern);
Первая возвращает в область памяти по указателю fill_info информацию о текущем заполнении. Информация заносится в структуру следующего типа:
struct fillsettingstype
{
int pattern;
int color;
};
Элемент структуры pattern указывает тип шаблона (если он равен USER_FILL, то это значит, что шаблон задан пользователем), элемент color содержит код заполнения (номер входа во внутреннюю палитру). Вторая функция по переданному ей адресу записывает матрицу шаблона, установленную пользователем.
1.7.Вывод графического текста
Следующий набор функций предназначен для формирования на страницах видеопамяти текстовых сообщений с использованием специальных шрифтов.
Чтобы воспользоваться предоставляемыми шрифтами в программе, прежде всего необходимо инициализировать знакогенератор графической системы. Инициализацию графического знакогенератора выполняет функция settextstyle.
В Borland C предусмотрена работа в графическом режиме с двумя принципиально различными типами графических шрифтов: матричными (bit-mapped) и векторными (stroked). Первый тип представляется только одним шрифтом, изначально встроенным в BIOS компьютера. Шрифтов второго типа может быть несколько.
Набор символов матричного шрифта включает все 256 допустимых ASCII-кода. Каждый символ шрифта представлен в виде матрицы (битовой карты) размером 8 х 8 пикселей. Так как этот набор символов встроен в BIOS, то не требуется загрузки никакой дополнительной информации для его инициализации.
Другой принципиально отличный тип графических шрифтов подразумевает рисование символов тонкими линиями. Поэтому шрифты данного типа можно назвать векторными. Каждый символ представлен последовательностью управляющих кодов, которые заставляют генератор рисовать отрезки прямых линий, соединяющих характеристические точки символа. Таблицы символов векторных шрифтов поставляются в закодированном виде в специальных файлах шрифтов, имеющих расширение .chr. Далеко не все векторные шрифты имеют полный набор 256-ти кодов. Общими для всех шрифтов являются символы с кодами от 32 до 126. Разные символы одного и того же шрифта могут иметь разные размеры в зависимости от ширины конкретного символа.
Существует перечислимый тип font_names, задающий символические имена для номеров пяти графических шрифтов (один матричный и четыре векторных из минимального набора):
enum font_names
{
DEFAULT_FONT=0, TRIPLEX_FONT, SMALL_FONT, SANS_SERIF_FONT, GOTHIC_FONT
};
Наиболее простым способом инициализации знакогенератора (загрузки определенной таблицы символов) является автоматическая загрузка с использованием функции
void far settextstyle(int font,int directon,int charsize);
Функция устанавливает основные параметры вывода графических текстов (шрифт, направление строк и размер символа). Эти параметры остаются неизменными до следующего вызова функции settextstyle.
Первый параметр font задает номер инициализируемого шрифта. Ему можно присвоить значение одной из констант перечислимого типа font_names. Набор символов матричного шрифта встроен в BIOS, и от функции не требуется дополнительных действий при его загрузке. Для векторных шрифтов при автоматической загрузке производится поиск соответствующего chr-файла. Сначала просматривается директория, указанная аргументом path_to_bgi функции initgraph, затем текущая директория. Если файл шрифта не найден, устанавливается отрицательный код ошибки. Найденный chr-файл считывается в автоматически выделяемую область памяти. При невозможности выделить необходимую память также устанавливается соответствующий код ошибки. При возникновении любой ошибочной ситуации функция settextstyle выполняется до конца, но инициализирует при этом встроенный матричный шрифт вместо заданного векторного шрифта.
Аргумент direction может принимать одно из значений, определенное константами HORIZ_DIR и VERT_DIR. Он задает направление выводимой строки текста: HORIZ_DIR определяет обычное направление (горизонтальная строка, символы идут слева направо), VERT_DIR разворачивает строку на 90 градусов против часовой стрелки. Использование вертикального расположения строки может привести к изменению пропорций выводимых символов – они становятся ниже и шире, чем в горизонтальной строке, что связано с эффектом “неквадратности” пикселя в некоторых дисплеях и никак неустранимо.
Последний аргумент charsize управляет масштабом выводимых символов. Диапазон допустимых значений: от 1 до 10 для всех графических шрифтов и дополнительное значение 0 только для векторных. Положительное значение charsize указывает на одинаковое увеличение символов по обеим осям. Если для векторных шрифтов задать charsize, равный USER_CHAR_SIZE (определена как 0), то при этом определяются два независимых коэффициента масштабирования – для ширины и высоты символа. По умолчанию в этом режиме знакогенератор рисует символы векторного шрифта в базовом варианте в соответствии с описанием из chr-файла. Однако есть возможность изменить масштаб с помощью функции
void far setusercharsize(int mult_x,int div_x,int mult_y,int div_y);
Данная функция устанавливает новые размеры символа, умножая ширину и высоту базового варианта каждого символа на значения выражения ((double)mult_x / div_x) и ((double)mult_y / div_y) соответственно.
Главным недостатком автоматической инициализации знакогенератора при помощи функции settextstyle является то, что она обращается к диску для чтения chr-файла во время выполнения программы. Альтернативой автоматической инициализации знакогенератора является статическое включение в программу таблицы символов векторных шрифтов. Для этого сначала нужно при помощи утилиты bgiobj.exe превратить chr-файлы в объектные файлы c расширением .obj. Затем их можно включить в программу как обычные объектные файлы.
Перед статической инициализацией знакогенератора необходима регистрация нужной таблицы векторных шрифтов, для чего предусмотрена функция, которой нужно передать местоположение в оперативной памяти таблицы символов шрифта:
int registerbgifont(void (*font)(void));
В качестве указателей используются имена, уже определенные в объектных файлах шрифтов. Ниже приведены описания тех имен, которые нужно употреблять для шрифтов из минимального набора:
void triplex_font(void);
void small_font(void);
void sansserif_font(void);
void gothic_font(void);
При успешной регистрации шрифта возвращаемое значение неотрицательно. Далее для инициализации знакогенератора используется функция settextstyle с необходимыми установками для шрифта. Тогда в присутствии на диске chr-файла отпадает всякая необходимость.
Графическая система позволяет позиционировать на странице видеопамяти выводимый текст с точностью до пикселя, для этого функциям вывода текста нужно некоторую опорную точку. Расположением выводимой строки относительно опорной точки управляет функция
void far settextjustify(int horiz,int vert);
Ее аргументы могут принимать значения констант перечислимого типа text_just:
enum text_just
{
LEFT_TEXT=0, CENTER_TEXT, RIGHT_TEXT,
BOTTOM_TEXT, TOP_TEXT
};
В случае ошибки функция settextjustify устанавливает код ошибки, равный -11, и сохраняет предыдущий режим позиционирования.
Позиционирование горизонтальных строк происходит следующим образом. Если представить прямоугольник, в который вписана выводимая строка, то пара аргументов horiz и vert описывают положение опорной точки в прямоугольнике.
При инициализации графического режима опорная точка всех строк располагается в левом верхнем углу прямоугольника, в который вписывается горизонтальная строка.
В позиционировании вертикальных строк имеются некоторые особенности. Нужно помнить, что функция settextjustify управляет размещением относительно опорной точки именно прямоугольника, содержащего текст. Из этого следует, что по отношению к самому тексту (поскольку он повернут на 90 градусов) позиционирование справа и слева от опорной точки определяется аргументом vert, а сверху или снизу – аргументом horiz. Аргумент vert обрабатывается так же, как и для горизонтальной строки. Аргумент horiz ведет себя по-другому. При значениях LEFT_TEXT и RIGHT_TEXT он дает одинаковый результат – текстовый прямоугольник располагается слева от опорной точки.
В графической библиотеке существует функция, позволяющая получить информацию о текущем режиме вывода графических текстовых сообщений:
void far gettextsettings(struct textsettingstype far *texttypeinfo);
Информация заносится в область памяти, выделенную в программе и содержащую следующую структуру:
struct textsettingstype
{
int font,direction,charsize;
int horiz,vert;
};
Для правильного размещения текста на странице необходимо знать размеры занимаемого им пространства, которое зависит от типа шрифта и его масштабирования. Эту информацию выдают функции:
int far textheight(char far *text_string);
int far textwidth(char far *text_string);
Они возвращают высоту и ширину (в пикселях) прямоугольника, в который был бы вписан текст при выводе строки text_string знакогенератором текущего шрифта с учетом установленного в данный момент масштаба шрифта. Сам вывод текста при этом не производится.
При своей работе функции вывода текста создают в видеопамяти рисунок символов строки, используя таблицу выбранного шрифта. Код, который при этом заносится в атрибуты пикселей, есть текущее значение рисующего цвета (номер входа во внутреннюю палитру), устанавливаемое функцией setcolor. Никаких битовых операций с атрибутами пикселей видеопамяти не предусмотрено. Линии, которыми рисуются символы векторных шрифтов, всегда сплошные тонкие, они не могут модифицироваться функцией setlinestyle.
В отличие от текстового режима, фон выводимых символов (цвет прямоугольника, в который вписана строка) не изменяется. Если нужно вывести строку на каком-либо фоне, то фон нужно нарисовать отдельно, например с помощью функции bar. Другим важным отличием графического режима от текстового является отсутствие режима мигания символа. Можно имитировать процесс мигания, управляя внутренней палитрой с помощью функции setpalette.
Функций вывода текста две:
void far outtext(char far *text_string);
void far outtextxy(int x,int y,char far *text_string);
Обе в качестве аргумента получают указатель на выводимую строку символов. Отличие между этими функциями состоит в выборе опорной точки при позиционировании сообщения на странице. Функция outtext использует для этого текущую графическую позицию. Функция outtextxy получает координаты опорной точки через аргументы x и y. Координаты задаются в системе координат текущего графического окна. При установке режима отсечения изображения за пределами окна обе функции также обеспечивают отсечение фрагментов текста, выходящего за пределы графического окна.
2.ПРИЕМЫ ПРОГРАММИРОВАНИЯ ГРАФИЧЕСКОГО РЕЖИМА
2.1.Подключение графической библиотеки
Для подключения графической библиотеки можно использовать один из двух приемов. Во-первых, включить в меню оболочки BC++3.1 опцию Options-Linker-Libraries-Graphics library. Во-вторых, создать проект и включить в него исходные cpp-файлы и библиотеку LIB\graphics.lib.
Графический режим может не работать в оболочке, если не хватает места в динамической памяти. Следует увеличить ее размер в пункте Options-Debugger-Heap size.
2.2.Инициализация графического режима
Простейшую инициализацию осуществляет следующий код
#include <graphics.h>
void main()
{
int gd = DETECT, gm;
initgraph( &gd, &gm, “c:\\bc31\\bgi” );
// работаем в графическом режиме
putpixel(100, 200, WHITE);
// ……….
If(!getch())
getch();
closegraph();
}
2.3.Включение драйвера и шрифтов в исполняемый файл
Для включения графического драйвер egavga.bgi в исполняемый файл необходимо:
а) в командной строке выполнить
C:\BC31\BGI\bgiobj.exe egavga
и получить файл egavga.obj;
б) создать проект в оболочке с помощью команды меню Project-Open и включить в него файл egavga.obj и исходные cpp-файлы;
в) затем в тексте программы записать код
registerbgidriver(EGAVGA_driver);
initgraph(&gd, &gm, “”);
Для включения графических шрифтов (файлов с расширением chr) в исполняемый файл необходимо:
а) в командной строке выполнить
C:\BC31\BGI\bgiobj.exe goth
и получить файл goth.obj;
б) создать проект в оболочке с помощью команды меню Project-Open и включить в него файл goth.obj и исходные cpp-файлы;
в) затем в тексте программы записать код
registerbgifont(GOTHIC_font);
initgraph(&gd, &gm, “c:\\borlandc\\bgi”);
//…………………
settextstyle(GOTHIC_FONT, HORIZ_DIR, 10);
outtext(“Hello”);
//…………………
2.4.Рисование геометрических фигур
а) Нарисуем красное солнце
setcolor(RED);
circle(100, 100, 50); //красная окружность
setfillstyle(SOLID_FILL, RED);
floodfill(100, 100, RED); /* рисуем красный круг, указывая внутреннюю точку области и цвет контура */
б) Нарисуем в центре экрана квадрат со стороной a, повернутый относительно центра C на угол против часовой стрелки. Пусть С(x>0>, y>0>) – математические координаты точки. Тогда вершины неповернутого квадрата имеют координаты
(x>0 >+ (sqrt(2) / 2) · a · cos( /4 + /2 · k),
y>0 >+ (sqrt(2) / 2) · a · sin( /4 + /2 · k)), где k = 0, 1, 2, 3.
При повороте на угол получаем математические вершины
(f>k>, g>k>) = (x>0 >+ (sqrt(2) / 2) · a · cos( /4 + /2 · k + ),
y>0 >+ (sqrt(2) / 2) · a · sin( /4 + /2 · k + )),
где k = 0, 1, 2, 3.
В соответствии с данными, приведенными п.2.11, переводим математические координаты (f>k>, g>k>) в координаты пикселей (u>k>, v>k>). Затем определяем целочисленный массив A координат вершин замкнутого многоуольника
{u[0], v[0], u[1], v[1] , u[2], v[2], u[3], v[3], u[0], v[0] }.
Теперь рисуем квадрат с помощью одной функции
drawpoly(5, A);
2.5.Выделение памяти под большие одномерные массивы
Элементами массивов могут быть структуры большого размера. Если и размер массива значителен, то выделить динамическую память под такой массив одним вызовом функции malloc нельзя. Прототип этой функции
void *malloc( unsigntd int T);
Поэтому максимальный размер блока памяти, который она выделяет, не превосходит 64К.
Предлагается
эмулировать одномерный динамический
массив двумерным динамическим массивом
с использованием массива
указателей.
Например, выделим память под long T=50000l структур
struct comp {
float re, im;
};
Общий размер такого массива равен 50 000 * 8 = 400 000 байт. Выберем размеры двумерного массива
int m = 1000; // количество столбцов выбираем произвольно
int
n =
(int)((T
– 1)/ m)
+1; // количество строк, нумерация строк
с нуля
В последней (m-1)-й строке прямоугольной динамической матрицы m n могут содержаться элементы, не принадлежащие исходному одномерному массиву. Поэтому при переборе элементов массива необходима соответствующая проверка.
comp ** A = NULL;
A = (comp **) malloc( m * sizeof(comp *));
// проверка выделения памяти
for(int i=0; i < m; i++)
A[i] = (comp *) malloc(k * sizeof(comp));
// проверка выделения памяти
for( i =0; i < m; i++)
for( int j =0; j < n; j++)
if( (long) i*k+j < T) // работаем только с элементами
// исходного одномерного массива {
A[i][j].re = 0;
A[i][j].im = 0;
}
// работа программы
// освобождение памяти
for( i =0; i < m; i++)
free(A[i]);
free(A);
2.6.Вывод числовой информации
Для вывода форматированных данных используется двухэтапный алгоритм. Вначале данные записываются в строку или, как иногда говорят, в память. Затем строка выводится на графический экран.
Например выведем вещественное число с двумя знаками после запятой.
char buf[5];
float x = 3.1415;
sprintf(buf, “%4.2”, x);
outtextxy(100, 100, buf);
Цифра
4 в форматной строке задает количество
байтов в памяти, которые будет занимать
выводимая форматированная информация
без учета признака конца строки. Прием
используется для предотвращения выхода
за диапазон массива. Подобная ошибка
возникла бы в случае
x
= 13.1415.
2.7.Задержка экрана
Организация задержки экрана не зависит от выбора графического или текстового режима, и реализуется следующим образом
if (!getch())
getch();
Данный код корректно обрабатывает два возможных случая. Во-первых, при нажатии обычной клавиши. При этом с клавиатуры в буфер ввода помещается ненулевой ascii-код символа, соответствующего нажатой клавише. Тогда условие !getch() ложно и второй getch() не вызывается.
Во-вторых, при нажатии клавиши с расширенным кодом (например, функциональные клавиши, стрелки) с клавиатуры в буфер ввода поступают два числа: ноль и scan-код этой клавиши. Тогда условие !getch() истинно, вызывается второй getch(), который считывает scan-код и тем самым очищает буфер.
Другим способом задержки экрана является код
while(!getch())
;
Отметим, что стандартным способом задержки (в учебниках и примерах из справок по функциям) является вызов функции
getch();
Однако этот способ некорректно обрабатывает нажатие клавиши с расширенным кодом. В данном случае в буфере останется один непрочитанный символ, который будет считываться при следующем вводе информации.
2.8.Реакция программы на нажатие конкретной клавиши
В следующем фрагменте выходим из цикла по нажатию клавиши Escape
#define ESC 27
while(1){
if(kbhit()){
char c=getch();
if(c == 0)
{
getch();
continue;
}
else if( c == ESC)
break;
}
//работа цикла
}
2.9.Организация ввода числовой информации
В графических приложениях ввод текстовой и числовой информации реализуется также в графическом режиме. Для этого нельзя использовать функцию scanf, так как она предполагает текстовый режим.
Предлагается следующий алгоритм для ввода целого положительного числа. Данные считываются с клавиатуры посимвольно, сохраняются в памяти и затем распознаются с помощью функции sscanf форматированного ввода из строки. Строка выводится в графическом режиме посимвольно.
#define ENTER 13
main(){
//инициализация графического режима
char c, buf[2], *str=(char *)malloc(1);
int number;
str[0] = ’\0’;
buf[1] = ‘\0’;
while( (c = getch()) != ENTER){
if( c < ’0’ || c> ’9’)
continue;
buf[0] = c; // введенный символ оформляем в виде строки
outtext(buf); // и выводим на экран в графическом режиме
str = realloc(str, strlen(str) + 2);
str[strlen(str) + 1] = ‘\0’;
str[strlen(str)] = c; // запоминаем символ в памяти
}
sscanf(str, “%d”, &number);
}
2.10.Проверка выхода аргумента функции из ОДЗ
Проверку выхода переменной x из области допустимых значений функции f(x) можно реализовать в самом теле функции. В случае выхода из ОДЗ функция взводит глобальный флаг, который проверяется в вызывающей функции.
Например,
int flag=0; //глобальный флаг
// работаем с функцией y = x1/2
float f(float x){
if(x<0){
flag=1;
return 0;
}
else{
flag=0;
return sqrt(x);
}
}
void main(){
float x, y;
scanf(“%f”, &x);
y=f(x);
if(flag==1)
printf(“Выход из ОДЗ. ”);
else
printf(“Нет выхода из ОДЗ. Продолжаем вычисления”);
}
2.11.Графическая и математическая системы координат
Для рисования графика функции программисту удобнее использовать математическую систему координат (МСК), расположенную по центру экрана. Графические функции работают в графической системе координат (ГСК).
а б
Рис. 1. Системы координат: а – математическая; б – графическая
Инициализируем переменные.
int maxx = getmaxx(),
maxy = getmaxy(),
px = 30, // количество пикселей в одной математической единице по оси Ох
py = px * ((float)maxx/maxy); // количество пикселей в одной математической единице по оси Оy.
Соответствие
между двумя системами координат
представлено
ниже
Соответствие между МСК и ГСК
МСК |
ГСК |
(0, 0) ………………………………. |
(maxx / 2, maxy / 2) |
(1, 0) ………………………………. |
(maxx / 2 + px, maxy / 2) |
(0, 1) ………………………………. |
(maxx / 2, maxy / 2 – py) |
(x, y) ….……………………………. |
(maxx / 2 + x px, maxy / 2 – y py) |
(x, f(x)) ……………………………. |
(maxx / 2 + x px, maxy / 2 – f(x) py) |
Реализуем масштабирование по оси координат Ох так, чтобы график функции y = f(x) на заданном отрезке [a, b] размещался по всей ширине экрана. Для этого найдем коэффициенты u и v линейного отображения g(x) = u x + v, при котором отрезок [a, b] переходит в отрезок [0, maxx]. Из системы линейных уравнений
u a + v = 0
u b + v = maxx
находим u = maxx / (b-a), v = -a maxx / (b-a). Тогда точке (x, f(x)) будет соответствовать пиксель
( (maxx / (b-a)) * x - a * maxx / (b-a) , maxy / 2 - f(x) * py)),
где px = maxx/(b-a). Затем график можно нарисовать с помощью цикла, в котором счетчик является вещественной математической переменной. Например:
for(float x = a; x <= b; x += (b-a) / maxx)
putpixel((maxx / (b-a)) * x - a * maxx / (b-a), maxy / 2- f(x) * py));
Данный цикл при больших a и b может оказаться бесконечным. Это возможно в случае, если шаг цикла (b-a) / maxx будет меньше расстояния между числом a и его ближайшим соседом справа для типа float.
2.12.Использование двух видеостраниц
Для рисования вращающейся звезды (см. задание 3.4) лучше использовать две видеостраницы.
int page=0;
for(double f = 0; f < 2 M_PI-M_PI / 100; f += M_PI / 50) {
otrisovka(X0, Y0, R1, r2, fi0 + f, COLOR); /* рисуем новую звезду на активной странице, которая по умолчанию имеет нулевой номер*/
setvisualpage(page); /* показываем изображение новой звезды*/
page=abs(page-1); /* меняем номер страницы с 0 на 1 или наоборот*/
setactivepage(page); /* меняем активную страницу*/
otrisovka(X0, Y0, R1, r2, fi0 + f-M_PI / 50, getbkcolor()); /* стираем старую звезду на активной странице*/
}
2.13.Рисование изображений в bmp-формате
Для создания фона в задаче о снегопаде можно использовать 16-ти цветный bmp-файл, так как это устраняет проблему самостоятельного рисования фонового изображения средствами языка C++. Для загрузки изображения из файла надо выполнить действия:
с позиции 22 в файле прочитать высоту рисунка;
вычислить ширину записанного изображения
ширина = (размер_файла - 118) / высота;
3) загрузить сам рисунок, начиная с позиции 118, учитывая, что в одном байте содержится 2 пикселя и то, что изображение в файле записано построчно, причем первая строка записана в конец файла, а последняя – с позиции 118.
// карта замещения цветов для создания визуального эффекта
char map[] = {0,12,2,6,9,5,3,8,7,4,10,14,1,13,11,15};
int y0 = getmaxy();
// Открываем картинку
FILE *f = fopen(fon, "rb");
if(f==NULL)
return 2;
// читаем ширину картинки
fseek(f, 0, 2);
long l = ftell(f)-118;
fseek(f, 22, 0);
int w,h;
fread(&h, 2, 1, f);
w = int(l / h);
// читаем и рисуем картинку
fseek(f, 118, 0);
int x=0;
int y=0;
while(1) {
c = fgetc(f);
if(feof(f))
break;
ch = map[c/16];
cl = map[c%16];
putpixel(2*x+0, y0-y, ch);
putpixel(2*x+1, y0-y, cl);
if(++x==w) {
x=0;
y++;
}
}
fclose(f);
2.14.Работа с мышью
Вызовы BIOS используют программные прерывания. BIOS имеет несколько различных прерываний для разных целей. Для работы с мышью используют прерывание 0x33. Для доступа к этим прерываниям используется функция Си с прототипом в файле <dos.h>
int int86(int num, REGS *in, REGS *out);
где num – номер прерывания. Объединение REGS имеет вид
union REGS{
struct WORDREGS x;
struct BYTEREGS y;
};
struct WORDREGS{
unsigned int ax, bx, cx, dx, si, di, cflags, flags;
};
struct BYTEREGS{
unsigned char al, ah, bl, bh, cl, ch, dl, dh;
};
//Определим глобальную переменную
REGS regs;
//показать курсор
void showcursor(void){
regs.x.ax = 0x01;
int86(0x33,®s,®s);
}
//спрятать курсор
void hidecursor(void){
regs.x.ax = 0x02;
int86(0x33,®s,®s);
}
//получение статуса мыши
void get_mouse_status(int& button,int& x,int& y){
regs.x.ax = 0x03;
int86(0x33,®s,®s);
button = regs.h.bl;
x = regs.x.cx;
y = regs.x.dx;
}
//пример использования мыши
main(){
//инициализация графического режима
int button, x, y;
char str[20];
showcursor();
while(1){
get_mouse_status(button, x, y);
if(x == 0 || y == 0)
break;
sprintf(str, “%d %d”,x, y);
outtext(30, 30, str);
}
hidecursor();
}
3.ЗАДАНИЯ ДЛЯ ЛАБОРАТОРНОЙ РАБОТЫ
3.1.Звездное небо
На экране в непрерывном режиме рисуются звезды (пиксели) в случайном месте и случайным цветом. Распределение случайных величин равномерное. При наложении новой звезды на другую видимую звезду обе стираются. Рисование прекращается нажатием клавиши Escape. Затем происходит подсчет числа видимых звезд, и процент заполнения неба выводится по центру графического экрана с точностью до сотых долей процента. Использовать готический шрифт размером 1 см.
Рисование звезд и их подсчет реализовать в виде отдельных функций. Получить ответ для двух режимов VGAHI и VGALO.
3.2.Снегопад
С верхней части и с боковых сторон экрана в непрерывном режиме падают белые снежинки в форме одного пикселя. Выход из программы по нажатию клавиши Escape. Снежинки пролетают экран и снова появляются. Создать на экране графический фон в виде голубого полумесяца, красивого текста и пр. Фон не содержит белого цвета и не должен перерисовываться.
Плотность снегопада вводится в начале программы в текстовом режиме. Максимальное количество снежинок равно 50 000. Параметры снежинки находятся в пользовательской структуре данных. Для хранения информации о снежинках следует использовать двумерный динамический массив структур.
Смещения снежинок содержат хаотическую составляющую по горизонтали и вертикали. Организовать управление ветром с помощью клавиш-стрелок.
3.3.Рисование графика функции
Нарисовать график функции y = f(x) на отрезке [a, b]. Вещественные границы интервала вводятся с клавиатуры в графическом режиме с возможностью редактирования. График необходимо масштабировать по ширине экрана так, чтобы отрезок [a, b] полностью вписался в экран.
Функция f(x) задается исходным кодом на языке Си. Осуществить проверку выхода переменной x из ОДЗ.
3.4.Вращение звезды
Написать функцию, которая рисует правильную пятиконечную цветную звезду, со следующими параметрами:
x, y – математические координаты центра;
R, r – математические радиусы внутренней и внешней окружностей;
fi – угол между осью x и одним из больших лучей звезды, в радианах;
col – цвет контура звезды;
colfill – цвет заливки.
Написать также программу, в которой вращается красная звезда в центре экрана.
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
Керниган Б. Язык программирования Си / Б. Керниган, Д. Ритчи. М.: Финансы и статистика, 1992. 272 с.
Керниган Б. Язык программирования Си. Задачи по курсу Си / Б. Керниган, Д. Ритчи. М.: Финансы и статистика, 1985. 192 с.
Юркин А.Г. Задачник по программированию / А.Г. Юркин. СПб.: Питер, 2002. 192 с.
Подбельский В.В. Программирование на языке Си: учеб. пособие / В.В. Подбельский, С.С. Фомин. М.: Финансы и статистика, 2005. 600 с.
Трофимов С.П. Программирование в Си. Организация ввода-вывода: метод. указания / С.П. Трофимов. Екатеринбург: УГТУ,1998. 20 с.
Трофимов С.П. Программирование в Си. Динамически распределяемая память: метод. указания / С.П. Трофимов. Екатеринбург: МИДО, 1998. 14 с.