CodeNet / Языки программирования / C / C++ / Руководства и справочные материалы по C/C++ / C для профессиональных программистов
C для профессиональных программистов
ГЛАВА 4 -- 1 -- ------- ГРАФИКА ----------------------------------------------------------------- В этой главе приводится базовый набор функций графики, которые позволяют рисовать (отображать на экране) точки, линии, прямоугольники, окружности, используя возможности графических адаптеров CGA или EGA. Эти программы используются как основа, на которой строятся функции графики более высокого уровня. Для более детального изучения этих базовых графических функций предлагается книга "С: The Complete Reference" Herb Schild (Osborn / McGrow-Hall, 1987). Помимо краткого представления базовых функций графики, в этой главе приводятся следующие программы: - сохранение графических изображений в файле на диске; - загрузка графических изображений из файла; - вращение объектов в двумерном пространстве; - копирование или пересылка графических изображений. В конце главы привевен текст "программы-художника", позволяющей рисовать на экране терминала, с использованием клавиш перемещения курсора. По мере возрастания вашего опыта по использованию функций графики, вы сможете самостоятельно разрабатывать хорошие программы. Например, используя функции сохранения и загрузки графических изображений, вы сможете создавать графики или диаграммы и успешно использовать их в случае необходимости. Используя функции вращения изображений объектов, вы сможете разрабатывать программы "звездных войн" - образец "живой" графики, которые будут представлять большой интерес для вас. Вы сможете также, использовать эти функции как основу для использования систем автоматизированного проектирования (CAD/CAM system). Для корректного использования программ, описаных в этой главе, вам необходимы компьютеры типа IBM PC XT, IBM PC AT или другие совместимые с ними модели, снабженные графическими адаптерами CGA или EGA. Все программы, приведенные в данной главе, кроме программ изображения точки, аппаратно-независимы, и вы можете с минимальными усилиями сделать их рабочими на других типах графических машин. Глава IV -- 2 -- ВИДЕОРЕЖИМЫ И ЦВЕТОВАЯ ПАЛИТРА ----------------------------------------------------------------- Перед использованием каких-либо функций графики компьютер должен быть переведен в соответствующий видеорежим. Для компьютеров типа IBM PC это означает, что должны быть выбраны подходящие режим и палитра. В таблице 4-1 приведены различные видеорежимы, в которых могут работать компьютеры IBM PC. Для функций, приведенных в этой главе, требуется 4 видеорежим, предполагающий использование цветного графического дисплея с размерностью экрана 320 на 200. Хотя адаптер EGA поддерживает и режимы с расширенной разрешающей способностью дисплея, 4 видеорежим выбран в качестве базового для разработки и использования функций графики в связи с тем, что он поддерживается как адаптером EGA, так и CGA. Использование особых режимов EGA требует только изменения функций записи точки (смотри книгу по использованию графики в EGA "Advance Grafics in C" Nelson Jobson Osborn/McGrow-Hall, 1987). Вам необходимо запомнить, что во всех кодах верхний левый угол имеет координаты 0,0. BIOS-прерывание 16, функция 0, устанавливает видеорежим и используется в функции mode(), текст которой приведен ниже. Но прежде чем вы ознакомитесь с ней, предлагаем вам внимательно изучить все видеорежимы, поддерживаемые соответствующими адаптерами. Для этого обратитесь к таблице 4-1. Таблица 4-1 Режимы терминала для машин IBM PC ----------------------------------------------------------------- Режим Тип Размерность Адаптер экрана ----------------------------------------------------------------- 0 алфавитно-цифровой, ч/б 40х25 CGA, EGA 1 алфавитно-цифровой, 16 цв. 40х25 CGA, EGA 2 алфавитно-цифровой, ч/б 80х25 CGA, EGA 3 алфавитно-цифровой, 16 цв. 80х25 CGA, EGA 4 графический, 4 цвета 320х200 CGA, EGA 5 графический, 4 серых тона 320х200 CGA, EGA 6 графический, ч/б 640х200 CGA, EGA 7 алфавитно-цифровой, ч/б 80х25 монохромный 8 графический, 16 цветов 160х200 PCjr 9 графический, 16 цветов 320х200 PCjr 10 графический, 4 цвета-PCjr 640х200 PCjr, EGA 16 цветов-EGA 13 графический, 16 цветов 320х200 EGA 14 графический, 16 цветов 640х200 EGA 15 графический, 4 цвета 640х350 EGA ----------------------------------------------------------------- А теперь приведем текст функции mode(). Глава IV -- 3 -- /* Установка видеорежима */ void mode(mode_code) int mode_code; { union REGS r; r.h.al = mode_code; r.h.ah = 0; int86(0x10,&r,&r); } В 4-ом режиме доступны две палитры (набора цветов). Каждая палитра определяет четыре цвета, отображаемые на экране терминала. В IBM PC палитра 0 определяет желтый, зеленый и красный цвета, палитра 1 определяет белый, ярко-красный (пурпурный) и голубой цвета. Для каждой палитры четвертым цветом является цвет фона, который обычно черный. BIOS-прерывание 16, функция 11, устанавливает палитру. Функция pallet(), приведенная ниже, устанавливает номер палитры, который задается в качестве значения ее аргумента: /* Установка палитры */ void palette(pnum) int pnum; { union REGS r; r.h.bh=1; /* код 4 графического режима */ r.h.bl=pnum; /* номер палитры */ r.h.ah=11; /* устанавливается для вызова палитры */ int86(0x10,&r,&r); } Глава IV -- 4 -- ЗАПИСЬ ТОЧКИ РАСТРА ----------------------------------------------------------------- Одной из основных программ графики является программа записи точки растра - наименьшей адресуемой точки на экране дисплея. В данном случае термин "точка растра" будет использоваться для описания наименьшей адресуемой точки в отдельных графических режимах. Так как функция записи точки растра используется в других программах более высокого уровня, ее эффективность очень важна для быстродействия программ верхних уровней, которые непосредственно реализуют функции графики. На IBM PC и совместимых с ними компьютерах существуют два способа вывода информации с использованием точек растра. Первый способ, использующий прерывания ROM-BIOS, является наиболее простым, но и наименее быстродействующим (слишком медленным для наших целей). Вторым и более быстродействующими способом является размещение информации непосредственно в видеопамяти дисплея (ROM). Именно этот метод и рассматривается ниже. Глава IV -- 5 -- Работа адаптеров CGA/EGA в графическеом режиме ---------------------------------------------- Адаптер CGA всегда располагается в видеопамяти по адресу 8000000h. Адаптер EGA имеет аналогичное расположение для тех режимов, которые совместимы с режимами CGA (более полную информацию о аппаратных средствах поддержки графики вы можете получить в руководстве "IBM Technical Reference"). В 4 графическом режиме каждый байт содержит информацию о цвете для 4 точек растра (для каждой точки растра по 2 бита). Следовательно, для работы с экраном размерностью 320 на 200 требуется 16К памяти. Так как два бита могут содержать только 4 различных значения, в 4 видеорежиме поддерживаются только 4 цвета. Значение каждого двухбитового блока определяет цвет в соответствии с таблицей, приведенной ниже: --------------------------------------------------- Значение Цвет в палитре 0 Цвет в палитре 1 --------------------------------------------------- фон фон 1 желтый голубой 2 красный пурпурный 3 зеленый булый --------------------------------------------------- Особенность адаптера CGA заключается в том, что четные точки растра будут располагаться по адресу B8000000h, а нечетные - на 2000h (8152 - в десятичном виде) байтов выше, т.е. по адресу B8002000h. Следовательно, каждая строка точек растра требует 80 байтов (40 для четных точек растра, 40 - для нечетных). Внутри каждого байта точки располагаются слева направо, как они появляются на экране терминала. Это означает, что точка растра с номером 0 занимает 6 и 7 биты, в то время, как точка растра с номером 3 - 0 и 1 биты. Так как каждый байт кодирует значение четырех точек растра, вы должны сохранять значение трех точек при изменении одной из них. Лучший способ сделать это - создание битовой маски со всеми битами, установленными в 1, кроме тех, которые будут изменяться. Значение битовой маски складывается по схеме "И" с действительным значением байта, а затем полученное значение складывается по схеме "ИЛИ" с новым значением. Однако ситуация несколько меняется, если вы хотите сложить по схеме "НЕ-ИЛИ" новое и старое значения. В этом случае вы просто складываете по схеме "ИЛИ" старое значение байта с 0, а затем складываете по схеме "НЕ-ИЛИ" новое двоичное представление цвета и получаете результат. Адрес требуемого байта определяется путем умножения значения координаты X на 40, а затем добавляется значение координаты Y, деленное на 4. Для того,чтобы определить, находится ли точка растра в четном или нечетном блоке памяти, используется остаток от деления значения координаты Х на 2. Если остаток равен 0, блок является четным (используется первый блок), в противном случае - блок нечетный (используется второй блок). Требуемые биты внутри байта вычисляются путем выполнения деления по модулю 4. Остаток Глава IV -- 6 -- определяет номер двухбитового пакета, который содержит информацию о требуемых точках растра. Для установки байта режима цвета и битовой маски используются операторы побитового сдвига. Хотя манипулирование битами в функции mempoint() несколько запутано, вы, однако, без труда разберетесь в ней, если тщательно изучите что именно и как она делает. /* Запись точки в CGA/EGA */ void mempoint(x,y,color_code) int x,y,color_code; { union mask { char c[2]; int i; } bit_mask; int i,index,bit_position; unsigned char t; char xor; /* "НЕ-ИЛИ" цвета в случае его изменения */ char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном коде */ if (x<0 || x>199 || y<0 || y>319) return; xor=color_code & 128; /* проверка, устанавливался ли режим "НЕ-ИЛИ" */ color_code=color_code & 127; /* маска старших битов */ /* установка битовой маски и битов режима цвета в правую позицию */ bit_position=y%4; /* вычисление нужной позиции в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в нужную позицию */ /* определение требуемого байта в памяти терминала */ index=x*40+(y%4); if (x%2) index+=8152; /* если нечетный, используется второй блок */ /* запись цвета */ if (!xor) { /* режим изменения цвета */ t=*(ptr+index) & bit_mask.c[0]; *(ptr+index)=t|color_code; } else { t=*(ptr+index) | (char)0; *(ptr+index)=t & color_code; } } Глава IV -- 7 -- Заметим, что тип указателя видеопамяти объявлен как far; это необходимо, если вы транслируете в модели маленькой (small) памяти. Вы так же должны заметить, что специальный маркер режима записи XOR, определенный в функциях ROM-BIOS, используется и в функции mempoint(). Глава IV -- 8 -- ВЫЧЕРЧИВАНИЕ ЛИНИЙ ----------------------------------------------------------------- Функции вычерчивания линий являются основными подпрограммами графики и используются для отображения линий в заданном цвете путем задания ее начальных и конечных координат. В то время, как изображение вертикальных и горизонтальных линий не представляет особого труда, более трудной задачей является создание функций, которые рисуют линии вдоль диагоналей. Например, какие точки составляют линию, вычерчиваемую от точки с координатами 0,0 до точки с координатами 80,120? Один из подходов к разработке функций вычерчивания линий использует отношение между смещением по координатам X и Y. Чтобы показать этот подход в действии, проведем линию между точками с координатами 0,0 и 5,10. Смещение по X равно 5, а по Y - 10. Отношение равно 1/2. Оно будет использоватся при определении коэффициента зависимости, по которому должны меняться координаты X и Y при изображении линий. В данном случае это означает, что приращение координаты X составляет половину величины изменения координаты Y. Начинающий программист часто использует этот метод при разработке функций вычерчивания линий. Хотя такой подход математически верен и прост для понимания, для его правильной работы и для того, чтобы избежать серьезных ошибок округления, необходимо использовать математические операции с числами с плавающей точкой. Это означает, что функции вычерчивания линий будут работать довольно медленно, если в систему не будет включен математический сопроцессор (например - Intel 8087). По этой причине этот метод используется довольно редко. Наиболее общий метод изображения линий включает использование алгоритма Брезенхама. Хотя основой в нем служит также отношение между расстояниями по координатам X и Y, в данном случае не требуется выполнять деление или вычисление чисел с плавающей точкой. Вместо этого, отношение между значениями координат X и Y представляется косвенным образом через серии сложений и вычитаний. Основной идеей алгоритма Брезенхама, является регистрация средних значений погрешностей между идеальным положением каждой точки и той позицией на экране дисплея, в которой она действительно отображается. Погрешность между идеальным и действительным положением точки возникает ввиду ограниченных возможностей технических средств. Фактически не существует дисплеев с бесконечно большой разрешающей способностью, и, следовательно, действительное положение каждой точки на линии требует наилучшей аппроксимации. В каждой итерации цикла вычерчивания линии вызываются две переменные xerr и yerr, которые увеличиваются в зависимости от изменения величин координат X и Y соответственно. Когда значение погрешности достигает определенного значения, оно вновь устанавливается в исходное положение, а соответствующий счетчик координат увеличивается. Этот процесс продолжается до тех пор, пока линия не будет полностью вычерчена. Функция line(), приведенная ниже, реализует этот метод. Вы должны изучать ее до тех пор, пока не поймете механизма выполнения всех ее операций. Заметим, что в ней Глава IV -- 9 -- используется функция mempoint(), разработанная ранее для отображения точки на экране терминала. /* Вычерчивание линии заданного цвета с использованием алгоритма Брезенхама */ void line(startx,starty,endx,endy,color) int startx,starty,endx,endy,color; { register int t,distаnce; int xerr=0,yerr=0,delta_x,delta_y; int incx,incy; /* вычисление расстояния в обоих направлениях */ delta_x=endx-startx; delta_y=endy-starty; /* определение направления шага, шаг вычисляется либо по вертикальной, либо горизонтальной линии */ if (delta_x>0) incx=1; else if (delta_x==0) incx=0; else incx= -1; if (delta_y>0) incy=1; else if (delta_y==0) incy=0; else incy= -1; /* определение какое расстояние больше */ delta_x=abs(delta_x); delta_y=abs(delta_y); if (delta_x>delta_y) distance=delta_x; else distance=delta_y; /* вычерчивание линии */ for (t=0; t<=distance+1; t++) { mempoint(startx,starty,color); xerr+=delta_x; yerr+=delta_y; if (xerr>distance) { xerr-=distance; startx+=incx; } if (yerr>distance) { yerr-=distance; starty+=incy; } } } Глава IV -- 10 -- ИЗОБРАЖЕНИЕ И ЗАКРАШИВАНИЕ ПРЯМОУГОЛЬНИКОВ ----------------------------------------------------------------- Если у вас есть функции вычерчивания линий, то не составит особого труда создать функции вычерчивания прямоугольников. Пример, приведенный здесь, вычерчивает прямоугольники в заданном цвете путем задания координат двух противоположных углов. /* Вычерчивание прямоугольника */ void box(startx,starty,endx,endy,color_code) int startx,starty,endx,endy,color_code; { line(startx,starty,endx,starty,color_code); line(startx,starty,startx,endy,color_code); line(startx,endy,endx,endy,color_code); line(endx,starty,endx,endy,color_code); } Для того, чтобы закрасить прямоугольник, требуется выполнить запись в каждую точку растра внутри прямоугольника. Программа fill_box(), приведенная ниже, закрашивает прямоугольник, определенный координатами двух противоположных углов, заданным цветом. В ней используется функция line(), задающая цвет внутри прямоугольника. /* Закрашивание прямоугольника в заданный цвет */ void fill_box(startx,starty,endx,endy,color_code) int startx,starty,endx,endy,color_code; { register int i,begin,end; begin=startx<endx ? startx:endx; end=startx>endx ? startx:endx; for (i=begin;i<=end;++i) line(i,starty,i,endy,color_code); } Глава IV -- 11 -- ВЫЧЕРЧИВАНИЕ ОКРУЖНОСТЕЙ ----------------------------------------------------------------- Самым быстрым и легким способом вычерчивания окружностей является способ, основанный на использовании опять таки алгоритма Брезенхама, похожего на одноименный алгоритм вычерчивания линий. Данный метод также не требует вычислений чисел с плавающей точкой, кроме вычисления коэффициента сжатия, поэтому он обеспечивает достаточное быстродействие. По существу, алгоритм основан на приращении координат X и Y на величину погрешности между ними. Значение погрешности содержится в переменой delta. Функция plot_circle() выполняет запись точек по окружности. Переменная asp_ratio является глобальной, т.к. она используется как в функции circle(), так и в функции plot_circle(). Эта переменная может быть полезна с точки зрения возможности установления ее значения вне функции circle() и дальнейшего использования внутри функции. Путем изменения значения этой переменной вы можете рисовать эллипсы. Параметрами функции circle() является центр окружности, ее радиус и цвет. Тексты функций приведены ниже. double asp_ratio; /* Вычерчивание окружности с использованием алгоритма Брезенхама */ void circle(x_center,y_center,radius,color_code) int x_center,y_center,radius,color_code; { register x,y,delta; asp_ratio=1.0; /* это число может меняется в различных случаях */ y=radius; delta=3-2*radius; for (x=0;x<y; ) { plot_circle(x,y,x_center,y_center,color_code); if (delta<0) delta+=4*x+6; else { delta+=4*(x-y)+10; y--; } x++; } x=y; if (y) plot_circle(x,y,x_center,y_center,color_code); } /* Функция печатает точки, определяющие окружность */ void plot_circle(x,y,x_center,y_center,color_code) int x,y,x_center,y_center,color_code; { int startx,starty,endx,endy,x1,y1; starty=y*asp_ratio; Глава IV -- 12 -- endy=(y+1)*asp_ratio; startx=x*asp_ratio; endx=(x+1)*asp_ratio; for (x1=startx;x1<endx;++x1) { mempoint(x1+x_center,y+y_center,color_code); mempoint(x1+x_center,y_center-y,color_code); mempoint(x_center-x1,y+y_center,color_code); mempoint(x_center-x1,y_center-y,color_code); } for (y1=starty;y1<endy;++y1) { mempoint(y1+x_center,x+y_center,color_code); mempoint(y1+x_center,y_center-x,color_code); mempoint(x_center-y1,x+y_center,color_code); mempoint(x_center-y1,y_center-x,color_code); } } Закрашивать окружность можно путем повторного вызова функции circle() с заданием все более и более меньшего радиуса. Этот способ используется в функции fill_circle(), текст которой приведен ниже. /* Закрашивание окружности путем повторного вызова circle() с уменьшением радиуса */ void fill_circle(x,y,r,c) int x,y,r,c; { while (r) { circle(x,y,r,c); r--; } } Глава IV -- 13 -- ПРОСТЕЙШАЯ ТЕСТОВАЯ ПРОГРАММЫ ----------------------------------------------------------------- Приведенные здесь программа иллюстрируют применение и возможности ранее описанных функций поддержки графики. /* Программа, иллюстрирующая работу графических функций */ #include "dos.h" #include "stdio.h" void mode(),line(),box(),fill_box(); void mempoint(),palette(),xhairs(); void circle(),plot_circle(),fill_circle(); double asp_ratio; main() { mode(4); palette(0); line(0,0,100,100,1); box(50,50,80,90,2); fill_box(100,0,120,40,3); circle(100,160,30,2); fill_circle(150,250,20,1); getchar(); mode(2); } /* установка палитры */ void palette(pnum) int pnum; { union REGS r; r.h.bh=1; /* код 4 режима графики */ r.h.bl=pnum; /* номер палитры */ r.h.ah=11; /* устанавливаетса для вызова палитры */ int86(0x10,&r,&r); } /* Установка видеорежима */ void mode(mode_code) int mode_code; { union REGS r; r.h.al = mode_code; r.h.ah = 0; int86(0x10,&r,&r); } /* Вычерчивание прямоугольника */ void box(startx,starty,endx,endy,color_code) Глава IV -- 14 -- int startx,starty,endx,endy,color_code; { line(startx,starty,endx,starty,color_code); line(startx,starty,startx,endy,color_code); line(startx,endy,endx,endy,color_code); line(endx,starty,endx,endy,color_code); } /* Вычерчивание линии заданного цвета с использованием алгоритма Брезенхама */ void line(startx,starty,endx,endy,color) int startx,starty,endx,endy,color; { register int t,distance; int xerr=0,yerr=0,delta_x,delta_y; int incx,incy; /* Вычисление расстояния в обоих направлениях */ delta_x=endx-startx; delta_y=endy-starty; /* Определение направления шага, шаг вычисляется либо по вертикальной, либо по горизонтальной линии */ if (delta_x>0) incx=1; else if (delta_x==0) incx=0; else incx= -1; if (delta_y>0) incy=1; else if (delta_y==0) incy=0; else incy= -1; /* Определение какое расстояние больше */ delta_x=abs(delta_x); delta_y=abs(delta_y); if (delta_x>delta_y) distance=delta_x; else distance=delta_y; /* Вычерчивание линии */ for (t=0; t<=distance+1; t++) { mempoint(startx,starty,color); xerr+=delta_x; yerr+=delta_y; if (xerr>distance) { xerr-=distance; startx+=incx; } if (yerr>distance) { yerr-=distance; starty+=incy; } } } Глава IV -- 15 -- /* Закрашивание прямоугольника заданным цветом */ void fill_box(startx,starty,endx,endy,color_code) int startx,starty,endx,endy,color_code; { register int i,begin,end; begin=startx<endx ? startx:endx; end=startx>endx ? startx:endx; for (i=begin;i<=end;++i) line(i,starty,i,endy,color_code); } /* Вычерчивание окружности с использованием алгоритма Брезенхама */ void circle(x_center,y_center,radius,color_code) int x_center,y_center,radius,color_code; { register x,y,delta; asp_ratio=1.0; /* это число может меняется в различных случаях */ y=radius; delta=3-2*radius; for (x=0;x<y; ) { plot_circle(x,y,x_center,y_center,color_code); if (delta<0) delta+=4*x+6; else { delta+=4*(x-y)+10; y--; } x++; } x=y; if (y) plot_circle(x,y,x_center,y_center,color_code); } /* Функция изображает точки, определяющие окружность */ void plot_circle(x,y,x_center,y_center,color_code) int x,y,x_center,y_center,color_code; { int startx,starty,endx,endy,x1,y1; starty=y*asp_ratio; endy=(y+1)*asp_ratio; startx=x*asp_ratio; endx=(x+1)*asp_ratio; for (x1=startx;x1<endx;++x1) { mempoint(x1+x_center,y+y_center,color_code); mempoint(x1+x_center,y_center-y,color_code); mempoint(x_center-x1,y+y_center,color_code); mempoint(x_center-x1,y_center-y,color_code); Глава IV -- 16 -- } for (y1=starty;y1<endy;++y1) { mempoint(y1+x_center,x+y_center,color_code); mempoint(y1+x_center,y_center-x,color_code); mempoint(x_center-y1,x+y_center,color_code); mempoint(x_center-y1,y_center-x,color_code); } } /* Закрашивание окружности путем повторного вызова circle() с уменьшением радиуса */ void fill_circle(x,y,r,c) int x,y,r,c; { while (r) { circle(x,y,r,c); r--; } } /* Запись точки в CGA/EGA */ void mempoint(x,y,color_code) int x,y,color_code; { union mask { char c[2]; int i; } bit_mask; int i,index,bit_position; unsigned char t; char xor; /* "исключающее ИЛИ" цвета в случае его изменения */ char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном виде */ if (x<0 || x>199 || y<0 || y>319) return; xor=color_code & 128; /* проверка, устанавливался ли режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */ /* Установка маски битов и битов режима цвета в правую позицию */ bit_position=y%4; /* вычисление нужной позиции в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в нужную позицию */ /* определение требуемого байта в памяти терминала */ Глава IV -- 17 -- index=x*40+(y%4); if (x%2) index+=8152; /* если нечетный, используется второй блок */ /* Запись цвета */ if (!xor) { /* режим изменения цвета */ t=*(ptr+index) & bit_mask.c[0]; *(ptr+index)=t|color_code; } else { t=*(ptr+index) | (char)0; *(ptr+index)=t & color_code; } } Глава IV -- 18 -- СОХРАНЕНИЕ И ЗАГРУЗКА ГРАФИЧЕСКИХ ИЗОБРАЖЕНИЙ ----------------------------------------------------------------- Сохранение и загрузка графических изображений является довольно простым делом, т.к. образ изображений находится в видеопамяти дисплея, а ее содержимое легко копировать на дисковый файл. Главной проблемой является необходимость введения пользователем имени файла, ввиду того, что запрос о вводе и введенное имя файла сотрут часть информации на экране. Для того, чтобы избежать этого, разработаны функции save_pic() и load_pic(), тексты которых приводятся в данном разделе. Первая функция сохраняет 14 верхних строк изображения, чистит эту область, запрашивает имя файла и, после того, как оно будет введено, восстанавливает изображение. /* сохранение графического изображения */ void save_pic() { char fname[80]; FILE *fp; register int i,j; char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ char far *temp; unsigned char buf[14][80]; /* содержит образ экрана */ temp=ptr; /* сохранение верхних строк текущего содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;++j) { buf[i][j]=*temp; /* четный байт */ buf[i][j+1]=*(temp+8152); /* нечетный байт */ *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } goto_xy(0,0); printf("Имя файла:"); gets(fname); if (!(fp=fopen(fname,"wb"))) { printf("Фвайл не может быть открыт\n"); return; } temp=ptr; /* восстановление содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;++j) { *temp= buf[i][j]; /* четный байт */ *(temp+8125)=buf[i][j+1]; /* нечетный байт */ *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } /* копирование изображения в файл */ Глава IV -- 19 -- for (i=0;i<8152;i++) { putc(*ptr,fp); /* четный байт */ putc(*(ptr+8125),fp); /* нечетный байт */ ptr++; } fclose(fp); } /* загрузка изображения */ void load_pic() { char fname[80]; FILE *fp; register int i,j; char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ char far *temp; unsigned char buf[14][80]; /* содержит образ экрана */ temp=ptr; /* сохранение верхних строк текущего содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { buf[i][j]=*temp; buf[i][j+1]=*(temp+8152); *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } goto_xy(0,0); printf("Имя файла:"); gets(fname); if (!(fp=fopen(fname,"rb"))) { goto_xy(0,0); printf("Файл не может быть открыт\n"); temp=ptr; /* восстановление содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { *temp= buf[i][j]; *(temp+8125)=buf[i][j+1]; temp++; } return; } /* загрузка изображения из файла */ for (i=0;i<8152;i++) { *ptr=getc(fp); /* четный байт */ *(ptr+8125)=getc(fp); /* нечетный байт */ ptr++; } fclose(fp); Глава IV -- 20 -- } Подпрограммы начинают обработку видеопамяти, начиная с адреса, содержащегося в указателе temp считывая или записывая каждый четный и нечетный байты в порядке возрастания их адресов. Такой подход позволяет добиться простоты и наглядности функционирования рассмотренных выше подпрограмм. В случае обработки видеопамяти в порядке возрастания адресов, вначале будет отображаться четная точка растра, а затем - нечетная. Глава IV -- 21 -- ДУБЛИРОВАНИЕ ЧАСТИ ЭКРАНА ----------------------------------------------------------------- Иногда бывает полезным скопировать часть экрана в другую область. Это легко выполнить используя функцию copy(), текст которой приводится ниже. /* копирование части экрана в другую область */ void copy(startx,starty,endx,endy,x,y) int startx,starty; /* верхняя левая координата */ int endx,endy; /* нижняя правая координата области копирования */ int x,y; /* верхняя левая координата области, куда будет проводится копирование */ { int i,j; unsigned char c; for (;startx<endx;startx++,x++) for (i=starty,j=y;i<endy;i++,j++) { c=read_point(startx,i); /* чтение точки */ mempoint(x,j,c); /* запись ее в новую область */ } } Как вы могли убедиться, при обращении к функциим в качестве ее аргументов указываются верхняя левая и нижняя правая координаты углов области, которая будет копироваться, и верхняя левая координаты, куда делается копия. Вы также можете убедится, что с небольшими изменениями функцию copy() можно преобразовать в функцию move(). Функция move() пересылает указанную область в другую и чистит исходное место. Текст функции приводится ниже. /* Пересылка части экрана в другую область */ void move(startx,starty,endx,endy,x,y) int startx,starty; /* верхняя левая координата */ int endx,endy; /* нижняя правая координата области пересылки */ int x,y; /* верхняя левая координата области, куда будет проводится пересылка */ { int i,j; unsigned char c; for (;startx<endx;startx++,x++) for (i=starty;j=y;i<endy;i++,j++) { c=read_point(startx,i); /* чтение точки */ mempoint(startx,i,0); /* стирание старого изображения */ Глава IV -- 22 -- mempoint(x,j,c); /* запись точки в новую область */ } } Глава IV -- 23 -- ВРАЩЕНИЕ ТОЧКИ В ПЛОСКОСТИ ЭКРАНА ----------------------------------------------------------------- Вращение точки в плоскости экрана (двумерном пространстве) представляет собой довольно простую задачу в декартовой системе координат. Вы можете вспомнить из курса аналитической геометрии, что вращение точки вокруг центра на угол theta, описывается формулой: new_x = old_x * cos(theta) - old_y * sin(theta) new_y = old_x * sin(theta) - old_y * cos(theta) Единственной сложностью при употреблении этих формул для графических дисплеев будет являтся тот факт, что экран дисплея не является декартовым пространством. Декартовы оси определяют 4 квадранта, как это показано на рисунке 4-2. Однако экран терминала представляет собой один из квадрантов, оси X и Y в котором перевернуты. Для решения этой проблемы необходимо определить новый центр и привести в соответствие координаты X и Y экрана и координаты осей декартова пространства. Любая точка на экране может быть использована в качестве центра, но обычно центр определяется как можно ближе к центру объекта, который мы собираемся вращать. Функция rotate_point(), приведенная ниже, вычисляет величину новых значений X и Y для заданного угла вращения. /* Вращение точки вокруг центра с координатами x_org и y_org на угол theta */ void rotate_point(theta,x,y,x_org,y_org) double theta,*x,*y; int x_org,y_org; { double tx,ty; /* нормализация X и Y к начальному адресу */ tx=*x-x_org; ty=*y-y_org; /* вращение */ *x=tx*cos(theta)-ty*sin(theta); *y=tx*sin(theta)-ty*cos(theta); /* возвращение значений координат */ *x+=x_org; *y+=y_org; } Заметим, что rotate_point() изменяет параметры X и Y путем присвоения им требуемого значение для получения угла вращения, заданного переменной theta. Угол вращения задается в радианах. Глава IV -- 24 -- ---------------------------------------------------------------¬ ¦ ¦ ¦ Декартова система координат Y ¦ ¦ II ^ I ¦ ¦ +,- ¦ +,+ ¦ ¦ ¦ ¦ ¦ ¦0.0 ¦ ¦ ---------+--------- X ¦ ¦ ¦ ¦ ¦ -,- ¦ -,+ ¦ ¦ ¦ ¦ ¦ III • IV ¦ ¦ ¦ ¦ ¦ ¦ 0.0 ¦ ¦ Графический экран ----------- Y ¦ ¦ ¦ ¦ ¦ ¦ +,+ ¦ ¦ ¦ ¦ ¦ • ¦ ¦ X ¦ L--------------------------------------------------------------- Рис. 4-2. Декартовы координаты на графическом экране. Глава IV -- 25 -- Вращение обьекта ---------------- Хотя функция rotate_point(), вычисляющая требуемые значения координат X и Y при вращении точки, уже была нами рассмотрена. Однако, она не может быть использована для вращения (поворотов) объектов. Для этого необходима другая функция. Под объектом здесь и далее будем понимать набор сегментов прямых отрезков. Координаты крайних точек каждого отрезка содержатся в двумерном массиве чисел с плавающей точкой. Каждая строка массива содержит начальные и конечные координаты данного отрезка. Это означает, что первая размерность массива представляет собой количество отрезков, входящих в состав объекта, а вторая размерность будет равна 4 (число координат крайних точек отрезка). Например, массив, приведенный ниже double object [10][4]; определяет объект, состоящий из 10 отрезков. Как правило, массив организуется так, как показано на рисунке 4-3. ----------------------------------------------------------------¬ ¦ ¦ ¦ Первый Второй --------> ¦ ¦ индекс индекс ¦ ¦ ¦ ¦ ¦ 0 1 2 3 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ V ¦ ¦ ¦ ¦ 0 start_X1 start_Y1 end_X1 end_Y1 ¦ ¦ ¦ ¦ 1 start_X2 start_Y2 end_X2 end_Y2 ¦ ¦ ¦ ¦ 2 start_X3 start_Y3 end_X3 end_Y3 ¦ ¦ ¦ ¦ 3 start_X4 start_Y4 end_X4 end_Y4 ¦ ¦ . . ¦ ¦ . . ¦ ¦ . . ¦ ¦ n start_Xn start_Yn end_Xn end_Yn ¦ L---------------------------------------------------------------- Рис. 4-3. Условная организация массива. Определить объект - это значит разместить в массиве координаты начальных и конечных точек отрезков, составляющих объект. Например, если объект представляет собой прямоугольник вида: Глава IV -- 26 -- 0.0------------------¬0.10 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ 10.0L------------------10.10 то в массив, определяющий данный прямоугольник, заносятся следующие числа: object[0][0] = 0; object[0][1] = 0; object[0][0] = 0; object[0][3] = 10; object[1][0] = 0; object[1][1] = 10; object[1][0] = 10; object[1][3] = 10; object[2][0] = 10; object[2][1] = 10; object[2][0] = 10; object[2][3] = 0; object[3][0] = 10; object[3][1] = 0; object[3][0] = 0; object[3][3] = 0; После того, как объект определен, вы можете вращать его, используя функцию rotate_object(), приведенную ниже, по часовой стрелке (клавиша <R>) или в противоположную сторону (клавиша <L>). /* Вращение заданных объектов */ void rotate_object(ob, theta, x, y, sides) double ob[][4]; /* описание объекта */ double theta; /* угол поворота в радианах */ int x, y; int sides; { register int i, j; double tempx, tempy; char ch; for(;;) { ch = getch(); /* ввод признака направления вращения */ switch(tolower(ch)) { case 'l': /* вращение против часовой стрелки */ theta = theta < 0 ? -theta : theta; break; case 'r': /* вращение по часовой стрелке */ theta = theta > 0 ? -theta : theta; break; default: return; } for(j=0; j<=sides; j++) /* стирание старых линий */ Глава IV -- 27 -- { line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0], &ob[j][1], x, y); rotate_point(theta, &ob[j][2], &ob[j][3], x, y); line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 2); } } } Как показано в описании параметров функции rotate_object(), вращение осуществляется вокруг центра, заданного координатами X и Y, на угол, величина которого задана параметром theta в радианах. Минимальное значение параметра theta равно 0.01 радиан. Заметим, что объект сначала стирается из старой области размещения, а затем перерисовывается вновь. Если это условие не может быть выполнено, то экран окрашивается в голубой цвет. Необходимым условием выполнения программы rotate_object() является обязательное задание параметра sides. Приведенная ниже функция display_object() не имеет отношения к вращению объектов, но она может быть полезна при работе с объектами. Она рисует на экране объекты, определенные в массиве ob. /* отображение объекта на экране */ void display_object(ob, sides) double ob[][4]; int sides; { register int i; for(i=0; i<sides; i++) line((int)ob[i][0], (int)ob[i][1], (int)ob[i][2], (int)ob[i][3], 2); } В качестве иллюстрации удобства использования функций вращения объектов ниже приводятся программы вращения изображения дома. На рисунке 4-4 показано изображение на экране терминала дома при различных углах поворота вокруг собственного центра. Прямоугольник, обрамляющий изображение вращаемого дома, поможет вам правильно оценить масштаб и перспективу. _________________________________________________________________ Прим. пер. Рисунок 4-4 не может быть воспроизведен имеющимися средствами. _________________________________________________________________ Рис. 4-4. Вращение объекта. Глава IV -- 28 -- /* Пример вращения изображения объекта с использованием адаптера CGA/EGA в 4 графическом режиме */ #include "dos.h" #include "stdio.h" #include "math.h" void mode(), line(), mempoint(), palette(); void rotate_point(), rotate_object(), display_object(); /* массив house определяет изображение дома */ double house[][4] = { /* startx, starty, endx, endy */ 120, 120, 120, 200, /* дом */ 120, 200, 80, 200, 80, 120, 80, 200, 80, 120, 120, 120, 60, 160, 80, 120, /* крышa*/ 60, 160, 80, 200, 120, 155, 100, 155, /* двери*/ 100, 155, 100, 165, 100, 165, 120, 165, 90, 130, 100, 130, /* окна */ 90, 130, 90, 140, 100, 130, 100, 140, 90, 140, 100, 140, 90, 180, 100, 180, 90, 180, 90, 190, 100, 180, 100, 190 }; main() { union k { char c[2]; int i; } key; mode(4); /* режим = 4 */ palette(0); /* палитра = 0 */ /* рисунок рамки,обрамляющей дом */ line (30, 70, 30, 260, 2); line (160, 70, 160, 260, 2); line (30, 70, 160, 70, 2); line (30, 260, 160, 260, 2); display_object(house, 17); getchar(); rotate_object(house, 0.025, 90, 160, 17); mode(3); Глава IV -- 29 -- } /* Выбор палитры */ void palette(pnum) int pnum; { union REGS r; r.h.bh = 1; /* код 4 графического режима */ r.h.bl = pnum; r.h.ah = 11; int86(0x10, &r, &r); } /* Выбор режима */ void mode(mode_code) int mode_code; { union REGS r; r.h.al = mode_code; r.h.ah = 0; int86(0x10, &r, &r); } /* Рисунок отрезка прямой заданного цвета */ void line(start_x, start_y, endx, endy, color) int start_x, start_y, endx, endy, color; { register int t, distance; int x=0, y=0, delta_x, delta_y; int incx, incy; /* вычисление приращений по x и по y */ delta_x = endx-start_x; delta_y = endy-start_y; /* вычисление признаков направлений отрезка */ if(delta_x>0) incx=1; else if(delta_x==0) incx=0; else incx= -1; if(delta_y>0) incy=1; else if(delta_y==0) incy=0; else incy= -1; delta_x=abs(delta_x); delta_y=abs(delta_y); Глава IV -- 30 -- if(delta_x>delta_y) distance=delta_x; else distance=delta_y; /* рисунок отрезка */ for(t=0; t<=distance; t++) { mempoint(start_x, start_y, color); x+=delta_x; y+=delta_y; if(x>distance) { x-=distance; start_x+=incx; } if(y>distance) { y-=distance; start_y+=incy; } } } /* запись точки в CGA/EGA */ void mempoint(x,y,color_code) int x,y,color_code; { union mask { char c[2]; int i; } bit_mask; int i,index,bit_position; unsigned char t; char xor; /* "НЕ-ИЛИ" цвета в случае его изменения */ char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном виде */ if (x<0 || x>199 || y<0 || y>319) return; xor=color_code & 128; /* проверка, устанавливался ли режим "НЕ-ИЛИ" */ color_code=color_code & 127; /* маска старших битов */ /* установка битовой маски и битов режима цвета в правую позицию */ bit_position=y%4; /* вычисление нужной позиции в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в Глава IV -- 31 -- нужную позицию */ /* определение требуемого байта в памяти терминала */ index=x*40+(y%4); if (x%2) index+=8152; /* если нечетный, используется второй блок */ /* запись цвета */ if (!xor) { /* режим изменения цвета */ t=*(ptr+index) & bit_mask.c[0]; *(ptr+index)=t|color_code; } else { t=*(ptr+index) | (char)0; *(ptr+index)=t & color_code; } } /* вращение точки вокруг центра с координатами в x_org и y_org, на угол theta */ void rotate_point(theta,x,y,x_org,y_org) double theta,*x,*y; int x_org,y_org; { double tx,ty; /* нормализация X и Y к начальному адресу */ tx=*x-x_org; ty=*y-y_org; /* вращение */ *x=tx*cos(theta)-ty*sin(theta); *y=tx*sin(theta)-ty*cos(theta); /* возвращение значений координат */ *x+=x_org; *y+=y_org; } /* Вращение заданных объектов */ void rotate_object(ob, theta, x, y, sides) double ob[][4]; /* описание объекта */ double theta; /* угол поворота в радианах */ int x, y; int sides; { register int i, j; double tempx, tempy; Глава IV -- 32 -- char ch; for(;;) { ch = getch(); /* ввод признака направления вращения */ switch(tolower(ch)) { case 'l': /* вращение против часовой стрелки */ theta = theta < 0 ? -theta : theta; break; case 'r': /* вращение по часовой стрелке */ theta = theta > 0 ? -theta : theta; break; default: return; } for(j=0; j<=sides; j++) /* стирание старых линий */ { line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0], &ob[j][1], x, y); rotate_point(theta, &ob[j][2], &ob[j][3], x, y); line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 2); } } } /* отображение объекта на экране */ void display_object(ob, sides) double ob[][4]; int sides; { register int i; for(i=0; i<sides; i++) line((int) ob[i][0], (int) ob[i][1], (int) ob[i][2], (int) ob[i][3], 2); } Глава IV -- 33 -- Сборка подпрограмм ----------------------------------------------------------------- В этом, последнем, параграфе, описывается простая программа рисования, использующая подпрограммы графики. Программы рисования часто используют "мышь", позволяющую пользователю удобным способом отображать линии на экране терминала. Однако, "мышью" комплектуются пока не все компьютеры поэтому, описанная здесь "программа-художник" ориентирована на операции с клавишами перемещения курсора. В "рисующих" программах вам необходимо контролировать текущее положение координат X и Y (в графическом режиме текущие координаты индицируются курсором не совсем обычной формы). Для простоты дальнейшего изложения материала будем называть "графический" курсор "перекрестьем", принимая во внимание и тот факт, что в графическом режиме его форма действительно напоминает крест. Функция xhairs() размещает графический курсор в позиции, специфицированной значениями ее аргументов X и Y. Напоминаем, что двоичный код цвета складывается по схеме "ИЛИ" со 128 с целью установки 7 бита в 1. Это позволяет функции mempoint() складывать по схеме "исключающего ИЛИ" двоичные коды старого и нового цвета на экране вместо его изменения. Такая возможность позволяет достичь двух важных моментов. Во-первых, графический курсор всегда видим, т.к. всегда имеет цвет, отличный от окружающего. Во-вторых, значительно упрощается процесс возврата точки растра, занимаемой курсором, в исходное положение. Эта операция выполняется путем повторного обращения к этим точкам (напомним, что последовательное выполнение двух операций по схеме "исключающего ИЛИ" всегда приводит к первоначальному значению). Ниже приведен текст функции отображения графического курсора. /* отображение графического курсора */ void xhairs(x,y) int x,y; { line(x-4,y,x+3,y,1|128); line(x,y+4,x,y-3,1|128); } Программа рисования, описанная в данном разделе, позволит вам: - рисовать линии; - рисовать прямоугольники; - закрашивать прямоугольники; - рисовать окружности; - закрашивать окружности; - выбирать цвет; - выбирать палитру; - устанавливать скорость изменения параметров; Глава IV -- 34 -- - сохранять графические изображения; - загружать графические изображения; - вращать объекты вокруг любой точки; - копировать и пересылать графические изображения. Приведем ниже текст главной программы : main() { union k{ char c[2]; int i; } key ; int x=10, y=10; /* текущая позиция экрана */ int cc=2; /* текущий цвет */ int on_flag=1; /* признак использования карандаша */ int pal_num=1; /* номер палитры */ /* конечная точка определения линий, прямоугольников, окружностей */ int startx=0, starty=0, endx=0, endy=0; int first_point=1; int inc=1; /* шаг пересылки */ int sides=0; /* количество сторон выбранного объекта */ int i; mode(4); /* переключатель режима CGA/EGA */ palette(0); /* палитра 0 */ xhairs(x, y); /* указатель курсора */ do { key.i = bioskey(0); xhairs(x, y); /* графический курсор */ if(!key.c[0]) switch(key.c[1]) { case 75: /* влево */ if(on_flag) line(x, y, x, y-inc, cc); y -= inc; break; case 77: /* вправо */ if(on_flag) line(x, y, x, y+inc, cc); y += inc; break; case 72: /* вверх */ if(on_flag) line(x, y, x-inc, y, cc); x -= inc; break; case 80: /* вниз */ if(on_flag) line(x, y, x+inc, y, cc); x += inc; break; case 71: /* вверх и влево */ Глава IV -- 35 -- if(on_flag) line(x, y, x-inc, y-inc, cc); x -= inc; y -= inc; break; case 73: /* вверх и вправо */ if(on_flag) line(x, y, x-inc, y+inc, cc); x -= inc; y += inc; break; case 79: /* вниз и влево */ if(on_flag) line(x, y, x+inc, y-inc, cc); x += inc; y -= inc; break; case 81: /* вниз и вправо */ if(on_flag) line(x, y, x+inc, y+inc, cc); x += inc; y += inc; break; case 59: /* F1 - медленно */ inc=1; break; case 60: /* F2 - быстро */ inc=5; break; } else switch(tolower(key.c[0])) { case 'o': /* переключение шаблона */ on_flag = !on_flag; break; case '1': cc=1; /* цвет 1 */ break; case '2': cc=2; /* цвет 2 */ break; case '3': cc=3; /* цвет 3 */ break; case '0': cc=0; /* цвет 0 */ break; case 'b': box(startx, starty, endx, endy, cc); break; case 'f': fill_box(startx, starty, endx, endy, cc); break; case 'l': line(startx, starty, endx, endy, cc); break; case 'c': circle(startx, starty, endy-starty, cc); break; case 'h': fill_circle(startx, starty, endy-starty, cc); break; case 's': save_pic(); break; case 'r': load_pic(); break; case 'm': /* пересылка фрагмента */ move(startx, starty, endx, endy, x, y); break; Глава IV -- 36 -- case 'x': /* копирование фрагмента */ copy(startx, starty, endx, endy, x, y); break; case 'd': /* определить поворот(cдвиг) объекта */ /* Внимание!! Во время трансляции программы идентификатор object был помечен как "неопределенный". Его описание действительно отсутствует в этой программе. (Ред. пер. И.Бычковский) */ sides = define_objekt(object, x, y); break; case 'a': /* поворот(сдвиг) объекта */ rotate_objekt(object, 0.05, x, y, sides); break; case '\r': /* набор конечных точек для линий, кругов или прямоугольников */ if(first_point) { startx = x, starty = y; } else { endx = x, endy = y; } first_point = !first_point;break; case 'p': pal_num = pal_num==1 ? 2:1; palette(pal_num); } xhairs(x, y); } while (key.c[0]!='q'); getchar(); mode(2); } Опишем кратко алгоритм работы программы рисования. Вначале экран терминала устанавливается в 4 графический режим. Затем устанавливается палитра 0, и графический курсор перемещается в верхний левый угол. Шаблон цвета по умолчанию устанавливается в соответствии с кодом 2 (красный в палитре 0). При перемещении графического курсора на экране остается след, который окрашивается в соответствии с текущим цветом шаблона. Если нажимать клавиши перемещения курсора, графический курсор перемещается на одну точку растра в заданном направлении. Такая скорость перемещения может не удовлетворять пользователя, поэтому в программе предусмотрена возможность смещения на 5 точек растра путем нажатия клавиши F2. Отменить режим ускоренного перемещения можно путем нажатия клавиши F1. Изменение цвета осуществляется при нажатии цифровых клавиш от 0 до 3. В палитре 0 цифра 0 зарезервирована, 1 определяет зеленый цвет, 2 - красный, 3 - желтый. Шаблон цвета может быть изменен путем нажатия клавиши 0. Клавиши <Курсор в левый верхний угол> (<HOME>), <Страница вверх> (<PGUP>), <Страница вниз> (<PGDN>) и <Кон> (<END>) перемещают графический курсор в указанном направлении и под углом в 45 градусов. Для анализа кодов операций чтения в программах используется функция bioskey(). Порядок подключения этой функции к программе Глава IV -- 37 -- при компиляции описан в главе 1. В программу включены обращения к функциям, позволяющим вам рисовать и закрашивать прямоугольники и окружности, рисовать линии, копировать и перемещать изображение на экране, сохранять на диске и загружать с него содержимое экрана, отображать и вращать объекты. При изображении линий, прямоугольников и окружностей вам необходимо определить координаты двух точек. Для прямоугольников - это координаты двух противоположных углов. Для линий задается начальная и конечная точки, а для окружности - координаты центра и точки, через которую она будет проходить. Процесс выбора этих точек выполняется путем нажатия клавиши <ВВОД> в момент, когда графический курсор находится в требуемой области. Например, для изображения линии вы перемещаете графический курсор в точку, где она должна начинаться и нажимаете клавишу <ВВОД> . Затем вы устанавливаете курсор в точку, где линия заканчивается, и нажимаете <ВВОД> снова. При нажатии клавиши <ВВОД> выполняется загрузка переменных startx, starty, endx и endy, которые потом используются в качестве параметров вызываемых функций. После того, как координаты точек будут определены, при нажатии клавиши <В> рисуется квадрат, а <F> - квадрат закрашивается, при нажатии <L> рисуется линия, при нажатии <С> рисуется окружность, а <Н> - окружность закрашивается. Для копирования или перемещения части экрана вы должны определить верхний левый и нижний правый углы области, которую вы хотите переместить (нажатием клавиши <ВВОД> ). Затем вы перемещаете курсор в верхний левый угол области, куда вы хотите переместить изображение. Для пересылки изображения требуется нажать клавишу <М>, а для копирования - <Х>. Запомните, что старое изображение в области, куда осуществляется копирование, будет уничтожено. Для вращения объекта вам необходимо определить сам объект, путем нажатия клавиши <D>. Затем, используя клавишу <ВВОД>, вы должны определить начальные и конечные координаты точек для отрезков по периметру выбранного объекта. Процесс выбора объекта вращения и определения его границ реализуется функцией define_object(). Вращение объекта начинается после нажатия клавиши <А>. Для определения направления вращения используются клавиши <L> (по часовой стрелке) или <R> (против часовой стрелки). Остановить процесс вращения можно нажатием любой клавиши, кроме <L> или <А>. Для остановки работы программы используется клавиша <Q>. При желании вы можете включить в программу функции для работы с "мышью". Пример выходных данных программы показан на рисунке 4-5. _________________________________________________________________ Рисунок 4-5 на стр. 163 не может быть воспроизведен имеющимися средствами. (Ред. пер. И.Бычковский) Глава IV -- 38 -- _________________________________________________________________ Рис. 4-5. Простейшие результаты работы программы рисования. А теперь приведем всю программу рисования целиком. /* Программа для CGA/EGA, позволяющая рисовать линии, прямоугольники и окружности. Вы можете нарисовать какой-либо объект и вращать его по часовой или против часовой стрелки. Вы так же можете копировать графическое изображение на диск и загружать его с диска. */ #define NUM_SIDES 20 /* число сторон объекта; при необходимости увеличивается */ #include "dos.h" #include "stdio.h" #include "math.h" void mode(), line(), box(), fill_box(); void mempoint(), palette(), xhairs(); void circle(), plot_circle(), fill_circle(); void rotate_point(), rotate_object(), goto_xy(); void display_object(), copy(), move(); void save_pic(), load_pic(); unsigned char read_point(); /* Этот массив содержит динамически меняющиеся координаты объекта. */ double object[NUM_SIDES][4]; double asp_ratio; /* содержит коэффициент сжатия для окружностей */ main() { union k{ char c[2]; int i; } key ; int x=10, y=10; /* текущая позиция экрана */ int cc=2; /* текущий цвет */ int on_flag=1; /* признак использования карандаша */ int pal_num=1; /* номер палитры */ int startx=0, starty=0, endx=0, endy=0; int first_point=1; int inc=1; /* шаг пересылки */ int sides=0; /* количество сторoн выбранного объекта */ int i; mode(4); /* переключатель режима CGA/EGA */ Глава IV -- 39 -- palette(0); /* палитра 0 */ xhairs(x, y); /* указатель курсора */ do { key.i = bioskey(0); xhairs(x, y); if(!key.c[0]) switch(key.c[1]) { case 75: /* влево */ if(on_flag) line(x, y, x, y-inc, cc); y -= inc; break; case 77: /* вправо */ if(on_flag) line(x, y, x, y+inc, cc); y += inc; break; case 72: /* вверх */ if(on_flag) line(x, y, x-inc, y, cc); x -= inc; break; case 80: /* вниз */ if(on_flag) line(x, y, x+inc, y, cc); x += inc; break; case 71: /* вверх и влево */ if(on_flag) line(x, y, x-inc, y-inc, cc); x -= inc; y -= inc; break; case 73: /* вверх и вправо */ if(on_flag) line(x, y, x-inc, y+inc, cc); x -= inc; y += inc; break; case 79: /* вниз и влево */ if(on_flag) line(x, y, x+inc, y-inc, cc); x += inc; y -= inc; break; case 81: /* вниз и вправо */ if(on_flag) line(x, y, x+inc, y+inc, cc); x += inc; y += inc; break; case 59: /* F1 - медленно */ inc=1; break; case 60: /* F2 - быстро */ inc=5; break; } else switch(tolower(key.c[0])) { Глава IV -- 40 -- case 'o': /* переключение шаблона */ on_flag = !on_flag; break; case '1': cc=1; /* цвет 1 */ break; case '2': cc=2; /* цвет 2 */ break; case '3': cc=3; /* цвет 3 */ break; case '0': cc=0; /* цвет 0 */ break; case 'b': box(startx, starty, endx, endy, cc); break; case 'f': fill_box(startx, starty, endx, endy, cc); break; case 'l': line(startx, starty, endx, endy, cc); break; case 'c': circle(startx, starty, endy-starty, cc); break; case 'h': fill_circle(startx, starty, endy-starty, cc); break; case 's': save_pic(); break; case 'r': load_pic(); break; case 'm': /* пересылка фрагмента */ move(startx, starty, endx, endy, x, y); break; case 'x': /* копирование фрагмента */ copy(startx, starty, endx, endy, x, y); break; case 'd': /* определить объект вращения */ sides = define_objekt(object, x, y); break; case 'a': /* вращение объекта */ rotate_objekt(object, 0.05, x, y, sides); break; case '\r': /* набор конечных точек для линий, кругов или прямоугольников */ if(first_point) { startx = x, starty = y; } else { endx = x, endy = y; } first_point = !first_point; break; case 'p': pal_num = pal_num==1 ? 2:1; palette(pal_num); } xhairs(x, y); } while (key.c[0]!='q'); getchar(); mode(2); } /* установка палитры */ void palette(pnum) int pnum; Глава IV -- 41 -- { union REGS r; r.h.bh = 1; /* код 4 режима графики */ r.h.bl = pnum; r.h.ah = 11; /* установка палитры */ int86(0x10, &r, &r); } /* установка видео-режима */ void mode (mode_code) int mode_code; { union REGS r; r.h.al = mode_code; r.h.ah = 0; int86(0x10,&r, &r); } /* изображение прямоугольника */ void box(sx, sy, ex, ey, c) int sx, sy, ex, ey, c; { line(sx, sy, ex, sy, c); line(sx, sy, sx, ey, c); line(sx, ey, ex, ey, c); line(ex, sy, ex, ey, c); } /* изображение линии заданного цвета с использованием алгоритма Брезенхама */ void line(startx,starty,endx,endy,color) int startx,starty,endx,endy,color; { register int t,distance; int xerr=0,yerr=0,delta_x,delta_y; int incx,incy; /* вычисление расстояния в обоих направлениях */ delta_x=endx-startx; delta_y=endy-starty; /* определение направления шага, шаг вычисляется либо по вертикальной, либо горизонтальной линии */ if (delta_x>0) incx=1; else if (delta_x==0) incx=0; else incx= -1; if (delta_y>0) incy=1; else if (delta_y==0) incy=0; else incy= -1; /* определение какое расстояние больше */ Глава IV -- 42 -- delta_x=abs(delta_x); delta_y=abs(delta_y); if (delta_x>delta_y) distance=delta_x; else distance=delta_y; /* изображение линии */ for (t=0; t<=distance+1; t++) { mempoint(startx,starty,color); xerr+=delta_x; yerr+=delta_y; if (xerr>distance) { xerr-=distance; startx+=incx; } if (yerr>distance) { yerr-=distance; starty+=incy; } } } /* закрашивание прямоугольника в заданный цвет */ void fill_box(startx,starty,endx,endy,color_code) int startx,starty,endx,endy,color_code; { register int i,begin,end; begin=startx<endx ? startx:endx; end=startx>endx ? startx:endx; for (i=begin;i<=end;++i) line(i,starty,i,endy,color_code); } /* изображение окружности с использованием алгоритма Брезенхама */ void circle(x_center,y_center,radius,color_code) int x_center,y_center,radius,color_code; { register x,y,delta; asp_ratio=1.0; /* это число меняется в различных случаях */ y=radius; delta=3-2*radius; for (x=0;x<y; ) { plot_circle(x,y,x_center,y_center,color_code); if (delta<0) delta+=4*x+6; else { delta+=4*(x-y)+10; Глава IV -- 43 -- y--; } x++; } x=y; if (y) plot_circle(x,y,x_center,y_center,color_code); } /* plot_circle печатает точки, определяющие окружность */ void plot_circle(x, y, x_center, y_center, color_code) int x_center,y_center,radius,color_code; { int x, y, startx, starty, endx, endy, x1, y1; starty=y*asp_ratio; endy=(y+1)*asp_ratio; startx=x*asp_ratio; endx=(x+1)*asp_ratio; for (x1=startx;x1<endx;++x1) { mempoint(x1+x_center,y+y_center,color_code); mempoint(x1+x_center,y_center-y,color_code); mempoint(x_center-x1,y+y_center,color_code); mempoint(x_center-x1,y_center-y,color_code); } for (y1=starty;y1<endy;++y1) { mempoint(y1+x_center,x+y_center,color_code); mempoint(y1+x_center,y_center-x,color_code); mempoint(x_center-y1,x+y_center,color_code); mempoint(x_center-y1,y_center-x,color_code); } } } /* закрашивание окружности путем повторного вызова circle() с уменьшением радиуса */ void fill_circle(x,y,r,c) int x,y,r,c; { while (r) { circle(x,y,r,c); r--; } } /* запись точки в CGA/EGA */ void mempoint(x,y,color_code) int x,y,color_code; { union mask { char c[2]; int i; } bit_mask; Глава IV -- 44 -- int i,index,bit_position; unsigned char t; char xor; /* "исключающее ИЛИ" цвета в случае его изменения */ char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном виде */ if (x<0 || x>199 || y<0 || y>319) return; xor=color_code & 128; /* проверка, устанавливался ли режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */ /* установка битовой маски и битов режима цвета в правую позицию */ bit_position=y%4; /* вычисление нужной позиции в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в нужную позицию */ /* определение требуемого байта в памяти терминала */ index=x*40+(y%4); if (x%2) index+=8152; /* если нечетный, используется второй блок */ /* запись цвета */ if (!xor) { /* режим изменения цвета */ t=*(ptr+index) & bit_mask.c[0]; *(ptr+index)=t|color_code; } else { t=*(ptr+index) | (char)0; *(ptr+index)=t & color_code; } } /* отображение графического курсора */ void xhairs(x,y) int x,y; { line(x-4,y,x+3,y,1|128); line(x,y+4,x,y-3,1|128); } /* чтение байта из оперативной памяти CGA/EGA */ unsigned char read_point(x,y) int x,y; { Глава IV -- 45 -- union mask { char c[2]; int i; } bit_mask; int i,index,bit_position; unsigned char t; char xor; /* "исключающее ИЛИ" цвета в случае его изменения */ char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в двоичном виде */ if (x<0 || x>199 || y<0 || y>319) return 0; /* установка битовой маски и битов режима цвета в правую позицию */ bit_position=y%4; /* вычисление нужной позиции в байте */ bit_mask.i<<=2*(3-bit_position); /* определение требуемого байта в памяти терминала */ index=x*40+(y>>4); if (x%2) index+=8152; /* если нечетный, используется второй блок */ /* запись цвета */ t=*(ptr+index) & bit_mask.c[0]; t>>=2*(3-bit_position); return t; } /* сохранение графического изображения */ void save_pic() { char fname[80]; FILE *fp; register int i,j; char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ char far *temp; unsigned char buf[14][80]; /* содержит образ экрана */ temp=ptr; /* сохранение верхних строк текущего содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;++j) { buf[i][j]=*temp; /* четный байт */ buf[i][j+1]=*(temp+8152); /* нечетный байт */ *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } goto_xy(0,0); printf("Имя файла:"); Глава IV -- 46 -- gets(fname); if (!(fp=fopen(fname,"wb"))) { printf("Файл не может быть открыт\n"); return; } temp=ptr; /* восстановление содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;++j) { *temp= buf[i][j]; /* четный байт */ *(temp+8125)=buf[i][j+1]; /* нечетный байт */ *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } /* копирование изображения в файл */ for (i=0;i<8152;i++) { putc(*ptr,fp); /* четный байт */ putc(*(ptr+8125),fp); /* нечетный байт */ ptr++; } fclose(fp); } /* загрузка изображения */ void load_pic() { char fname[80]; FILE *fp; register int i,j; char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ char far *temp; unsigned char buf[14][80]; /* содержит образ экрана */ temp=ptr; /* сохранение верхних строк текущего содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { buf[i][j]=*temp; buf[i][j+1]=*(temp+8152); *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } goto_xy(0,0); printf("Имя файла:"); gets(fname); if (!(fp=fopen(fname,"rb"))) { goto_xy(0,0); printf("Файл не может быть открыт\n"); Глава IV -- 47 -- temp=ptr; /* восстановление содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { *temp= buf[i][j]; *(temp+8125)=buf[i][j+1]; temp++; } return; } /* загрузка изображения из файла */ for (i=0;i<8152;i++) { *ptr=getc(fp); /* четный байт */ *(ptr+8125)=getc(fp); /* нечетный байт */ ptr++; } fclose(fp); } /* поместить курсор в заданное положение */ void goto_xy(x,y) int x,y; { r.h.ah=2; /* адресация курсора */ r.h.dl=y; /* координата столбца */ r.h.dh=x; /* координата строки */ r.h.bh=0; /* видеостраница */ int86(0x10,&r,&r); } /* копирование части экрана в другую область */ void copy(startx,starty,endx,endy,x,y) int startx,starty; /* верхняя левая координата */ int endx,endy; /* нижняя правая координата области копирования */ int x,y; /* верхняя левая координата области, куда будет проводится копирование */ { int i,j; unsigned char c; for (;startx<endx;startx++,x++) for (i=starty,j=y;i<endy;i++,j++) { c=read_point(startx,i); /* чтение точки */ mempoint(x,j,c); /* запись ее в новую область */ } } /* пересылка части экрана в другую область */ Глава IV -- 48 -- void move(startx,starty,endx,endy,x,y) int startx,starty; /* верхняя левая координата */ int endx,endy; /* нижняя правая координата области пересылки */ int x,y; /* верхняя левая координата области, куда будет проводиться пересылка */ { int i,j; unsigned char c; for (;startx<endx;startx++,x++) for (i=starty,j=y;i<endy;i++,j++) { c=read_point(startx,i); /* чтение точки */ mempoint(startx,i,0); /* стирание старого изображения */ mempoint(x,j,c); /* запись точки в новую область */ } } /* вращение точки вокруг центра с координатами x_org и y_org на угол theta */ void rotate_point(theta,x,y,x_org,y_org) double theta,*x,*y; int x_org,y_org; { double tx,ty; /* нормализация X и Y к начальному адресу */ tx=*x-x_org; ty=*y-y_org; /* вращение */ *x=tx*cos(theta)-ty*sin(theta); *y=tx*sin(theta)-ty*cos(theta); /* возвращение значений координат */ *x+=x_org; *y+=y_org; } /* Вращение заданных объектов */ void rotate_object(ob, theta, x, y, sides) double ob[][4]; /* описание объекта */ double theta; /* угол поворота в радианах */ int x, y; int sides; { register int i, j; double tempx, tempy; char ch; Глава IV -- 49 -- for(;;) { ch = getch(); /* ввод признака направления вращения */ switch(tolower(ch)) { case 'l': /* вращение против часовой стрелки */ theta = theta < 0 ? -theta : theta; break; case 'r': /* вращение по часовой стрелке */ theta = theta > 0 ? -theta : theta; break; default: return; } for(j=0; j<=sides; j++) /* стирание старых линий */ { line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0], &ob[j][1], x, y); rotate_point(theta, &ob[j][2], &ob[j][3], x, y); line((int) ob[j][0], (int) ob[j][1], (int) ob[j][2], (int) ob[j][3], 2); } } } /* отображение объекта на экране */ void display_object(ob, sides) double ob[][4]; int sides; { register int i; for(i=0; i<sides; i++) line((int)ob[i][0], (int)ob[i][1], (int)ob[i][2], (int)ob[i][3], 2); } /* определение объекта по заданным точкам */ define_object(ob,x,y) double ob[][4]; int x,y; { union k{ char c[2]; int i; } key ; register int i,j; char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */ char far *temp; Глава IV -- 50 -- unsigned char buf[14][80]; /* содержит образ экрана */ int sides=0; temp=ptr; /* сохранение верхних строк текущего содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { buf[i][j]=*temp; buf[i][j+1]=*(temp+8152); *temp=0; *(temp+8152)=0; /* чистка позиций экрана */ temp++; } i=0; xhairs(x,y); do { goto_xy(0,0); printf("Определите сторону %d,",sides+1); if (i==0) printf("Введите первую габаритную точку "); else printf("Введите вторую габаритную точку "); key.i=bioskey(0); xhairs(x,y); if (key.c[0]=13) { ob[sides][++i]=(double) x; ob[sides][++i]=(double) y; if (i==4) { i=0; sides++; } } /* перемещение графического курсора */ if (rey.c[0]) switch (key.c[1]) { case 75: /* влево */ y-=1; break; case 77: /* вправо */ y += 1 break; case 72: /* вверх */ x -= 1; break; case 80: /* вниз */ x += 1; break; case 71: /* вверх и влево */ x -= 1; y -= 1; break; case 73: /* вверх и вправо */ x -= 1; Глава IV -- 51 -- y += 1; break; case 79: /* вниз и влево */ x += 1; y -= 1; break; case 81: /* вниз и вправо */ x += 1; y += 1; break; } if (key.c[1]!=59) xhairs(x,y); } while (key.c[1]!=59); /* нажата клавиша F1 */ /* восстановление содержимого экрана */ for (i=0;i<14;++i) for (j=0;j<80;j+=2) { *temp= buf[i][j]; *(temp+8125)=buf[i][j+1]; temp++; } return sides; } Хотя в этой программе довольно много операторов, вам надо ввести ее в свой компьютер, так как она довольно интересна. Также она будет являться хорошим графическим инструментальным средством, которое вы сможете использовать в любой момент времени.
Оставить комментарий
Комментарии
1.
8 ноября 2004, 17:29:56
ТУТ ошибка!!! В части 23 в формулах вращения точки нужна сумма синуса и косинуса, а не разность!!! Я на этом 2 дня угробил!