graphics101

=Построение графика функции на языке С= toc
 * с использованием библиотеки WinBGIm**

Начало работы
Мы используем библиотеку WinBGIm (Borland Graphics Interface (BGI) for Windows) (Откуда: [|Using the WinBGIm Graphics Library with Visual Studio 2005/2008]) вместе с Microsoft Visual C 2008. Цель использования библиотеки, как всегда, облегчение себе жизни. Библиотека может быть другой, но основные принципы останутся. А чем проще будет то, с чем нам придется работать, тем лучше. У нас есть архив BGI2008.zip, содержащий пустой проект – решение Microsoft Visual C с подключенной библиотекой (поинтересуйтесь у преподавателя, где он). Для работы нужно распаковать этот архив в свою рабочую папку, зайти в полученный каталог bgi и двойным щелчком открыть файл bgi.sln (решение Microsoft Visual Studio 2008). Если программа спросит, чем открыть – С++, С#, Visual BASIC – выберите С++. В обозревателе решений правой кнопкой щелкаем на Source Files (Исходные тексты), Добавить – Создать элемент , в открывшемся окне выбираем – файл С++. В появившемся пустом окне будем вводить нашу программу.

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

Не-цель
Это руководство никак не затрагивает программирование графического интерфейса. Графический интерфейс требует другого подхода (и других библиотек).

Что вы (надеюсь) уже знаете
Надеюсь, что консольные программы вы уже умеете писать (и читать написанное). Вы должны уметь писать расчетные программы, с условиями, циклами и функциями.

На чем рисовать?
До сих пор мы работали с консольными приложениями. Белый текст, черный фон и совсем никаких картиночек. Для того чтобы рисовать, нужно открыть	графическое окно. Библиотека WINBGIm предоставляет две функции – и. Мы будем пользоваться последней (так проще). code format="c"
 * 1) include "graphics.h"

int main(void) {  /* открытие графического окна 400 x 300 */ initwindow(400, 300);

/* приборка */ getch; closegraph; return 0; } code Проще не бывает. Ну, на С не бывает :) Первая строка подключает заголовочный файл библиотеки graphics.h. Строка  открывает окно для рисования, 400х300 это размер рабочей области. Если этого вам не хватит, подставьте другие цифры. После этого можно рисовать. По окончании программы – рисования идет кусок "приборка". Сначала программа ждет нажатия любой клавиши на функции getch, после нажатия выполняется функция закрытия окна и оператор завершения программы. Кстати, у нас открывается два окна. Одно – для рисования, и нажатие клавиши для закрытия ожидается именно в нем. Второе – обычное консольное окно, куда можно выводить информацию функцией  (например, отладочная печать).

Пиксели и координаты
Кто же такие пиксели? Что-то из английской мифологии? Пиксели – это точки на мониторе. Все, что отображается на мониторе, на самом деле состоит из цветных точек. Пиксель – наименьший элемент картинки. У монитора компьютера есть "текущее разрешение", например 1280x1024 ("текущее", потому что его можно поменять). Это – размер экрана в пикселях, и он определяет максимальный размер нашего окна (и, соответственно, нашей картинки). У каждого пикселя есть координаты. Более конкретно, они пронумерованы от нуля, слева направо и сверху вниз. Координаты пикселей целые – пикселей между 0 и 1 нет. На уроках математики система координат была другая – центр где-то посередине, оси слева направо и снизу вверх, и между целыми точками сколько угодно промежуточных (непрерывные, а не дискретные координаты). При построении графиков функций нам придется это обеспечить.

Поставим точку
Итак, мы выяснили, что экран состоит из точек, и у точки есть координаты. Так как же нам поставить на экране точку? Нужна всего одна команда (вызов функции ): code format="c"
 * 1) include "graphics.h"

int main(void) {  initwindow(400, 300);

/* ставим точку */ putpixel(100,100,15);

getch; closegraph; return 0; } code Первые два параметра – координаты, X и Y. Третий параметр – цвет. 15 соответствует белому, и его хорошо видно на черном фоне. Получаем одинокую белую точку на черном окне с координатами 100, 100 (координаты отсчитываются от верхнего левого угла клиентской области – "рабочей зоны" – окна).

Нет, поставим МНОГО точек
Одну точку почти не видно. Давайте поставим много точек. Но как это сделать? В цикле. Пусть X будет меняться от 100 до 200, а Y будет равным X. Вот что у нас получится: code format="c"
 * 1) include "graphics.h"

int main(void) {  initwindow(400, 300);

/* куча точек, в цикле */ int x, y;  for(x=100;x<=200;x++) {     y=x; putpixel(x,y,15); }

getch; closegraph; return 0; } code

Но это же прямая!
Однако. У нас получился отрезок прямой. Наверно, для того чтобы нарисовать прямую линию, есть способ попроще? Есть, и несколько. Самый простой – > В вызове функции стоят координаты начальной и конечной точек отрезка. Есть и второй вариант. Можно отдельно передвинуть курсор в начальную точку, а потом провести линию из текущей точки в конечную. Это может пригодиться, если мы хотим нарисовать цепочку линий: > >

Но это же окружность!
Однако, наша первая куча точек повела себя как-то странно. Попробуем что-нибудь похитрее, с синусом и косинусом. Для этого нам нужно подключить математическую библиотеку. В Visual C++ перед этим нужно определить, чтобы можно было воспользоваться константой Пи: code format="c"
 * 1) define _USE_MATH_DEFINES
 * 2) include 
 * 3) include "graphics.h"

int main(void) {  initwindow(400, 300);

/* куча точек, в цикле */ int x, y;  double t;   for(t=0; t<2*M_PI; t+=0.01) {     x=100*cos(t)+200; y=100*sin(t)+150; putpixel(x,y,15); }  getch; closegraph; return 0; } code Что? У нас получилась окружность? Разберемся почему. Никакой магии, чистая математика ;) У нас есть число Пи, равное примерно 3.14... (Ах да, у нас же есть константа, 16 знаков после запятой.) Если мы будем менять угол (t), то точка (cos(t), sin(t)) пойдет вдоль единичной окружности (окружности радиусом 1 и центром (0,0)). От 0 до 2Пи – это полная окружность, если мерить в радианах. Компьютер считает в радианах, но если домножить радианы на 180/Пи, мы получим обычную полную окружность в градусах, от 0 до 360. Все, что мы добавили – это масштабирование до радиуса 100 (умножением) и сдвиг центра из (0,0) в (200, 150) (сложением). И конечно, есть более простой способ нарисовать окружность. В вызове функции первые два параметра – координаты центра, последний – радиус: >

Что-нибудь поинтереснее? (спираль Архимеда)
Как-то странно у нас получается. Программа сложная, результат – простой, и потом всегда можно сделать то же одной строкой. Нужны ли построения по точкам вообще? Иногда нужны. Посмотрите на измененный вариант последней программы. То, что получается, называется "спираль Архимеда". code format="c"
 * 1) define _USE_MATH_DEFINES
 * 2) include 
 * 3) include "graphics.h"

int main(void) {  initwindow(400, 300); /* куча точек, в цикле - Архимедова спираль*/ int x, y;  double t;   int nLoops=10; for(t=0; t<=2*M_PI*nLoops; t+=0.01) {     x=100*t/(2*M_PI*nLoops)*cos(t)+200; y=100*t/(2*M_PI*nLoops)*sin(t)+150; putpixel(x,y,15); }  getch; closegraph; return 0; } code

Мы собирались построить график функции. Как насчет синуса?
Вспомним математику. Допустим, мы хотим построить синус от –Пи до Пи (полный период). Мы знаем, что синус меняется в диапазоне от -1 до 1.­ Таким образом, у нас есть "логические" (математические) координаты: по X в диапазоне [-3.14, 3.14], по Y в диапазоне [-1,1]. Их нужно отобразить на "физические" координаты – пиксели, начинающиеся с (0,0) и идущие до (400,300). В некоторых языках программирования есть способы сделать это автоматически; нам придется делать это самим. Не беспокойтесь, это просто :) В нашем случае, для X мы должны сдвинуть диапазон к 0: X+3.14, а потом растянуть его (2Пи где-то около 6-ти) до 400 пикселей: (X+3.14)*65 (примерно). То же самое проделаем с Y: Y+1, (Y+1)*150. Но ось Y на компьютере перевернута, поэтому нужно сделать так: 300-(Y+1)*150. Пробуем. code format="c"
 * 1) define _USE_MATH_DEFINES
 * 2) include 
 * 3) include "graphics.h"

int main(void) {  initwindow(400, 300); /* график синуса */ int x, y;  double t, f;   for(t=-M_PI; t Правда, со всеми этими "примерно" расположить на рисунке оси *точно* будет трудновато...

Нет, имелась в виду любая функция
Давайте простроим график произвольной функции. Пусть "любая" функция будет math f(x)=1.5x^2 - 2sin(5x) math , x в диапазоне [-2,3]. Для того, чтобы легко строить график и добавить оси, нужно разобраться с "примерно". Выведем формулу для преобразования логических координат в физические. В общем виде: Чтобы отобразить X с интервала[a,b] на [0,1], нужно выполнить (X-a)/(b-a). Чтобы отобразить X с интервала [0,1] на [c,d], нужно выполнить X*(d-c)+c. Объединяя, получим универсальную формулу: Мы будем использовать это соотношение для преобразования логических координат в экранные, соответственно [a,b] будет логический диапазон, а [c,d] – экранный. Но можно пересчитывать и обратно, например, если мы соберемся преобразовать координаты курсора мышки (пиксели) в логические координаты. Для удобства мы создадим две функции, sx(x) и sy(y), которые будут преобразовывать логические X, Y в экранные, соответственно. Но в нашей формуле есть еще параметры, что с ними? Все эти числа (a,b,c,d) – границы диапазонов, и меняться не будут. Мы сделаем их глобальными и сэкономим на этом уйму передачи параметров. Размеры области рисования мы добудем непосредственно в функциях – библиотека предоставляет функции,. Для логического диапазона X заведем переменные xmin, xmax, для Y – ymin, ymax. Диапазон X нам известен. Где взять границы диапазона по Y? Чтобы получить диапазон изменения Y, мы пройдем по диапазону X с тем же шагом, с каким будем строить график, и посчитаем минимум и максимум функции. Это означает, что нам придется вычислять f(x) дважды – для определения диапазона и собственно для построения – но компьютеры сейчас очень быстрые ;) И последнее. Оси координат – это просто две прямые, проходящие через начало координат (0,0). code format="c"
 * Для отображения X с интервала [a,b] на [c,d], нужно выполнить (X-a)/(b-a)*(d-c)+c.**
 * 1) define _USE_MATH_DEFINES
 * 2) include 
 * 3) include "graphics.h"

double xmin, xmax, ymin, ymax;  //глобальные - диапазон логических координат

//Для преобразования X из интервала [a,b] -> [c,d] делаем (X-a)/(b-a)*(d-c)+c. //Создаем пару функций: sx(x) и sy(y) double sx(double x) { return (x-xmin)/(xmax-xmin)*getmaxx; } double sy(double y) { return getmaxy-(y-ymin)/(ymax-ymin)*getmaxy;  //'Y вверх ногами, поэтому getmaxy-... }

//"любая" функция. Можете подставить любую другую double f(double x) { return 1.5*x*x-2*sin(5*x); }

int main(void) {  initwindow(400, 300); /* график */ //f(x)=1.5*x*x-2*sin(5*x), x в [-2,3] double x, y;  int nPoints; double dx; xmin=-2; xmax=3;

nPoints=getmaxx;  //Максимальное число точек по X    dx=(xmax-xmin)/nPoints;   //шаг в логических координатах

//Для определения ymin, ymax нужен цикл ymin=f(xmin); ymax=ymin; for (x=xmin; x<=xmax; x+=dx) {     y=f(x); if (ymin > y) ymin = y;     if (ymax < y) ymax = y;   } //Рисуем график. Цикл такой же y=f(xmin); moveto(sx(xmin), sy(y));  //установка первой точки for (x=xmin; x<=xmax; x+=dx) {     y=f(x); lineto(sx(x), sy(y)); //соединяем точки }

//Добавим оси line(sx(xmin), sy(0), sx(xmax), sy(0)); line(sx(0), sy(ymin), sx(0), sy(ymax)); getch; closegraph; return 0; } code Получилось немножко длинновато, но, надеюсь, все еще понятно.

Добавляем подписи
Это просто. Есть функция с тремя параметрами: куда вывести (x, y), и что вывести. В точку (x, y) помещается верхний левый угол текста. Просто попробуйте. code format="c"
 * 1) include "graphics.h"

int main(void) {  initwindow(400, 300);

/* вывод текста */ outtextxy(100,100,"Привет"); outtextxy(100,120,"И еще раз привет");

getch; closegraph; return 0; } code Для того, чтоб попасть в нужную точку на графике, можно использовать наши функции, для перевода из логических координат в физические. Для нашего графика это может выглядеть так: code format="c" //подписи outtextxy(sx(0)+5, sy(0)+2, "0,0"); outtextxy(sx(xmax)-20, sy(0)+2, "X"); outtextxy(sx(0)+5, sy(ymax)+10, "Y"); code Если покопаться в справке, можно найти, как поменять шрифт и размер.

Добавляем цвет и толщину линий
С цветами - проще не бывает. Просто вызываете функцию, и указываете цвет: > в данном случае – константа. Заданы 16 цветов, от 0 (черный, Black) до 15 (белый, ). Названия можете посмотреть в справке. Или просто попробуйте. С толщиной хуже. Имеется возможность установить толщину 3 пикселя, и все; делается это следующим вызовом: > 	Зато доступны всякие пунктиры, за которые отвечает первый параметр функции. После этих двух строчек все точки и линии, которые вы будете рисовать, будут красными и 3 пикселя толщиной. Естественно, все можно сделать "как было": > > {{setlinestyle(SOLID_LINE, 0, NORM_WIDTH);}

Вот что у нас получилось после всего этого:

Что почитать еще
Ну, если вам правда интересно, можете посмотреть справку по библиотеке WinBGIm. Найдете много нового. И надеюсь, понимать справку после этого руководства станет немного легче ;)

Приложение
Вся программа целиком, на случай если вы что-нибудь пропустили: prog99funcFull.cpp