Text
                    МАШИННАЯ ГРАФИКА
НА ПЕРСОНАЛЬНЫХ КОМПЬЮТЕРАХ


COMPUTER GRAPHICS FOR THE IBM PC L Ammeraal Hogeschool Utrecht The Netherlands JOHN WILEY & SONS Chichester • New York • Brisbane • Toronto . Singapore
Серия МАШИННАЯ ГРАФИКА НА ЯЗЫКЕ СИ Л. Аммерал МАШИННАЯ ГРАФИКА НА ПЕРСОНАЛЬНЫХ КОМПЬЮТЕРАХ Перевод с английского В.А. Львова Москва "Сол Систем" 1992
ББК 32.97 А 62 УДК 681.3 Аммерал Л. А62 Машинная графика на персональных компьютерах. Пер. с англ.— М.: «Сол Систем», 1992.— 232 стр.: ил. ISBN 5-85316-002-8 (рус.) Излагается концепция построения программного обеспечения ма- шинной графики нижнего уровня на языке Си для персональных компь- ютеров. Приводится пакет графических подпрограмм, реализующий ряд полезных функций, которые можно использовать в прикладных программах. В конце книги дается пример простой интерактивной про- граммы для эскизирования и черчения. Для широкого круга читателей, применяющих персональные ком- пьютеры IBM PC или совместимые с ними для работы с графической информацией. А2404090000-(К)6 (безобьявл) И68<03)—92 «язооъяши Издание подготовлено при участии МП "БИНОМ" © 1986, John Wiley 6r Sons © 1992, перевод на русский язык, оформление Sol System Ltd. © 1992, переработка программ Sol System Ltd. Подписано в печать 11.08.92. Формат 84Х 108/32. Тираж 30 000 экз. Зак. 275. «Сол Систем». Москва, 103104, ул. Остужева, д. 12/2, кв. 6. Отпечатано с диапозитивов на Книжной фабрике № 1 Министерства печати и информации России. 144003, г. Электросталь Московской области, ул. Тево- сяна, 25. ISBN 5-85316-002-8 (рус.) ISBN 0-471-91501-7 (англ.)
ПРЕДИСЛОВИЕ В предыдущей книге автора "Принципы программирования в машинной графике" были использованы четыре базовые под- программы initgr, move, draw, endgr и предполагалось, что все желающие могут найти соответствующие подпрограммы на сво- ем компьютере. В машинной графике следует различать, по крайней мере, два уровня абстракции или, другими словами, два уровня программного обеспечения. На нижнем уровне находятся несколько удобных, но элементарных программ, а на высоком уровне они применяются в качестве аппаратно-независимых функций. В ранее упомянутой книге по машинной графике ос- новное внимание было сфокусировано на более высоком уровне программного обеспечения и, вообще говоря, нижний уровень не очень интересовал автора. Но после выхода книги из печати из- датель попросил представить все описанные программы на дис- кете для ПК фирмы IBM. При реализации запроса пришлось иметь дело и с нижним уровнем программного обеспечения. Как оказалось, для многих пользователей ПК вопрос "Как получить вывод в графической форме при программировании на языке Си?" является совсем не тривиальным. Стало ясно, что тема "растровой графики" вполне заслуживает отдельной книги. В большинстве книг по машинной графике разница между "векторной графикой" и "растровой графикой" обсуждается с упором на характеристики технических средств. Из этого неко- торые пользователи могут сделать ошибочный вывод, что их ори- ентированные на векторную графику элементарные подпрог- раммы будут непригодны для таких растровых устройств, как видеодисплеи и матричные принтеры. По нашему мнению наи- лучшим способом развеять такое недопонимание будет обсужде-
6 ние конкретных примеров использования как технических, так и программных средств. Программное обеспечение, представленное и обсуждаемое в этой книге, было написано для ПК фирмы IBM и совместимых с ними компьютеров, использующих PC DOS или MS DOS, с обыч- ным адаптером цветной машинной графики (CGA), или с моно- хромным графическим адаптером — хорошо известной "платой Геркулес" фирмы Hercules Computer Technology. Автор выража- ет надежду, что эта книга будет полезной и для тех пользовате- лей, которые применяют другие технические средства. Тексты программ на языке Си содержат много сравнительно простых подпрограмм, их легко модифицировать при необходимости. Некоторые специфические особенности технических и про- граммных средств, обсуждаемые в этой книге, рано или поздно изменятся, поэтому может возникнуть вопрос о целесообразно- сти использования книги в качестве учебника. Правда, при обу- чении всегда возникает проблема о разделении фундаменталь- ных аспектов и технических деталей. Но фундаментальные ас- пекты лучше воспринимаются при иллюстрации конкретными данными. Студенты заинтересуются видеодисплеем с разреше- нием М * TV пикселов только в том случае, если им будут названы реальные значения М и N. С точки зрения преподавателя это вполне возможно и эти значения должны быть упомянуты . Ав- тор надеется, что эта книга окажется полезной при преподава- нии практических вопросов применения вычислительной техни- ки как в университетах, так и в технических институтах. Есть много профессионалов, которые пишут программы для получения средств к существованию. Если они имеют дело с ПК фирмы IBM, или совместимыми с ними, то купив эту книгу они получат значительную экономию своих затрат. Они могут либо скопировать графические функции из книги, либо использовать заложенные основные идеи в своих собственных разработках. И, наконец, книга может быть интересной для "квалифици- рованного любителя''. В настоящее время многие имеют дома компьютеры, совместимые с ПК фирмы IBM, и некоторые из них знакомы с развитыми языками программирования, например, с языком Си. В книге достаточно ясно показано, что язык Си впол- не пригоден для получения графических изображений. Л. Аммерал
Глава 1 ВВЕДЕНИЕ 1.1. ИСТОРИЯ ПОЯВЛЕНИЯ ЭТОЙ КНИГИ В предыдущей книге автора "Принципы программирования в машинной графике" (Издательство Дж. Вайли, Чичестер, 1986; перевод на русский язык — М.: "Сол Систем", 1992), был пред- ставлен ряд программ, написанных на языке Си, в которых ис- пользовались четыре базовые подпрограммы для графики: initgri) Инициализация графического вывода. mov€(xy у) Перенос реального или фиктивного пера в точку draw(x, у) Вычерчивание отрезка прямой линии из текущей позиции пера в заданную точку Ос, у). endgri) Выполнение некоторых операций завершения работы программы. Эти четыре функции автору было легко и удобно использо- вать на компьютере PRIME 750, поскольку их можно выразить, например, через подпрограмму PLOT, которая имеется в хорошо известной многим программистам машино-независимой библио- теке подпрограмм DIPLOT на языке Фортран для вывода на гра- фопостроители фирмы Calcomp. Поскольку эти подпрограммы были в наличии, то автор смог ограничиться только их примене- нием, не интересуясь внутренним устройством. Именно в этом и состоит причина введения двух уровней программного обеспече- ния, или уровней абстракции. На более высоком уровне приме- няются машино-независимые подпрограммы, а на нижнем уров- не обеспечивается их реализация. Эти соображения и привели к тому, что в основу написания предыдущей книги по машинной графике был положен высокий уровень абстракции.
8 Глава У. ВВЕДЕНИЕ Тогда зачем же потребовалось писать новую книгу, в которой рассматривается более низкий уровень? Самый простой ответ на этот вопрос заключается в том, что необходимые базовые под- программы не всегда доступны пользователям. В процессе подготовки к публикации предыдущей книги из- датель обратился к автору с просьбой подготовить тексты про- грамм, описанных в книге, на дискете для использования на пер- сональных компьютерах (ПК) фирмы IBM или совместимых с ними. Несколько обескураженно автор должен был признать, что, считая себя профессионалом в вычислительной технике, он лишь иногда использовал ПК фирмы IBM и не был уверен, что применение его графических программ могло быть успешным на таких компьютерах. (И сейчас еще есть очень много опытных программистов, работающих на больших и малых ЭВМ, которые еще не открыли для себя мир персональных компьютеров, но, к счастью, число таких программистов сокращается). Другие пользователи ПК фирмы IBM говорили автору, что в языке Си нет доступных функций графических примитивов (ко времени написания книги) и автору пришлось их разрабатывать самому. Однако было бы нецелесообразно отказываться от предложения издателя и, после некоторых размышлений был куплен ПК, сов- местимый с ПК фирмы IBM, что все равно раньше или позже должно было произойти. Воспользовавшись советами со стороны издателя и других помощников, оказалось сравнительно легко реализовать функции initgr, move, draw, endgr и заставить про- граммы из предыдущей книги работать на ПК фирмы IBM. Разработка этих четырех базовых функций дала возможность получить прекрасный опыт программирования на нижнем уров- не, а поскольку очень многие интересуются этим опытом, было решено расширить проект и написать об этом вторую книгу. По- пытка разделить описание программирования на высшем и низ- шем уровнях на две книги несколько необычна, но при этом ока- залось возможным избежать одновременного изложения мате- риала на двух разных уровнях. В этой книге обсуждаются программы, относящиеся к низшему уровню, поэтому первая за- дача заключается в разработке небольшого графического пакета, состоящего из четырех базовых и нескольких вспомогательных функций, необходимых на самом начальном этапе. Если только это необходимо читателю, то можно ограничиться главами 1 и 2.
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 9 Но поскольку было решено обратить внимание на "растровую графику", потребовалось рассмотреть и некоторые другие ее спе- циальные свойства. Например, на экране видеодисплея можно удалить вычерченные отрезки линий, чего нельзя сделать с чер- тежом на графопостроителе. Редактирование изображений будет предметом рассмотрения в главе 3. В главе 4 будет описан вывод графической информации на матричный принтер. Если такой принтер имеется в составе системы, то его можно будет приме- нить для получения твердой копии результатов работы графиче-' ской программы без необходимости приобретения перьевого гра- фопостроителя. В главе 5 рассматривается вывод текстовой ин- формации вместе с графической и показано, как можно добавить специальные символы, например, знак интеграла. И, наконец, в главе 6 описан пример простой чертежной системы. Поскольку для нее не требуется специальных технических средств, то нет каких-либо препятствий для практического применения систе- мы или, по крайней мере, для проведения с нею некоторых экс- периментов. В общем, содержание книги призывает читателя к активности. Она может несколько разочаровать тех, кто ожидает получить сведения о развитых исследовательских проектах или коммерческих программных продуктах. Изучение достижений других дает меньше удовлетворения, чем использование своих собственных интеллекта и рук и персонального компьютера. 1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ НА ЯЗЫКЕ СИ Предполагается, что читатель знаком с языком программиро- вания Си, по крайней мере на уровне изложения в книге автора "Язык Си для программистов". При использовании языка Си на компьютере с конкретным компилятором может потребоваться некоторая дополнительная информация. В данной книге обсуж- дается использование ПК фирмы IBM или совместимых с ними. Обсуждаемые программы и функции на языке Си были скомпи- лированы с помощью компилятора Lattice С, версии 3.0. Если на практике будет применяться другой компилятор, то следует учи- тывать некоторые характеристики компилятора Lattice С, чтобы при необходимости можно было адаптировать тексты программ к требованиям конкретного компилятора. Такая информация по мере возможности представлена ниже в следующих параграфах.
10 Глава 1. ВВЕДЕНИЕ 1.2.1. Тип данных unsigned char В различных частях программ можно заметить тип данных unsigned char. В компиляторе Lattice С, версия 3.0, наличие клю- чевого слова unsigned ("беззнаковое") перед словом char предот- вращает интерпретацию самого левого бита кода символа в качестве знакового бита. Такой знаковый бит был бы перенесен влево при преобразовании кода символа в тип int ("целый"). Например, после описания переменной unsigned char к ш ОдсСО; что в двоичном представлении соответствует к в 1100 0000, вы- полнение операции сдвига вправо к» А дает результат ОхОС (» 0... 1100 в двоичном представлении), это и необходимо получить в большинстве приложений. Но если бы переменная к имела тип char (вместо unsigned char) то "знако- вый бит" равный 1 был бы использован для "расширения" байта 1100 0000 до двухбайтового целого со знаком 1111 1111 1100 0000 и после сдвига вправо на четыре позиции будет получено в результате 0000 1111 1111 1100. Заметим, что теперь значение правых восьми бит равно OxFC. Если это нежелательно, то хорошим средством является использование ключевого слова unsigned: оно приводит к расши- рению байта 1100 0000 до целого 0000 0000 1100 0000, что после сдвига вправо дает правильное значение 0000 0000 0000 1100. 1.2.2. Непосредственный ввод с клавиатуры Когда необходимо прочитать что-либо, вводимое с клавиату- ры, в качестве "стандартного файла ввода" обычно используется указатель файла stdin. Например scanfC%d\ &n) эквивалентноfscanfistdin, "%d\ &n). getchari) эквивалентно getc(stdin). Эти функции используют буфер в предположении, что вводи- мые с клавиатуры символы используются только после нажатия клавиши Enter ("Ввод"), что дает возможность применять кла- вишу стирания для коррекции ошибок ввода. Но иногда необхо- димо использовать код символа в тот же момент, когда нажи- мается соответствующая клавиша, без обязательного нажатия на
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 11 клавишу "Ввод". В компиляторе Lattice С для этой цели имеются следующие две функции: getch() получить символ с клавиатуры без эхо. getchei) получить символ с клавиатуры с эхо. Здесь слово "эхо" означает, что вводимый символ отобража- ется на экране. При работе функции getche ввод символа проис- ходит таким же образом, как и для функции getchar. Далее уви- дим, что иногда машина может находиться в "графическом режиме" и отображение на экране вводимого символа нежела- тельно, поэтому в этом случае предпочтительнее использовать функцию getch. Другой полезной стандартной функцией является kbhit () проверка нажатия клавиши на клавиатуре. В отличие от обычных функций ввода функция kbhit не ожи- дает нажатия какой-либо клавиши. Она просто возвращает зна- чение 1, если была нажата одна из клавиш, или 0 в противном случае. Если символ был введен, то функция kbhit не пропускает этот символ, другими словами, этот символ должен быть считан обычным образом. Функция kbhit позволяет завершить цикл при нажатии клавиши, как, например, в программе main() { intn-O; double x-1.0; white (1) { х*- 1.000000001; n++; if (kbhit()) break; } pr!ntfC^-%d x-%f", n, x); } Существует также функция ungetch, подобная стандартной функции ввода/вывода ungetc. Эта функция применяется после обращения к функции getch или getche и приводит к возвраще- нию символа в стек, обеспечивая возможность его использования при следующем обращении к функциям getch и getche. Стек име- ет только один уровень по глубине, поэтому в него нельзя запи- сать второй символ. В примере ch1-getch(); ungetch(cM); ch2-getch(); с клавиатуры считывается только один символ и этот символ присваивается обеим переменным ch\ и сА2. Функция ungetch будет использована в параграфе 1.2.4.
12 Глава L ВВЕДЕНИЕ 1.2.3. Модели памяти; считывание и запись Процессор 8088/8086 использует способ сегментированной адресации. Каждый адрес состоит из двух 16-разрядных компо- нентов: адреса сегмента и смещения. Адрес сегмента сдвигается на четыре бита влево, так что он оказывается расширенным нулями справа, после чего к нему добавляется смещение. Таким образом получается 20-разрядный адрес, достаточный для пря- мой адресации памяти размером в 1 мегабайт. Имеются четыре "регистра сегментов", содержащих адреса сегментов, а именно: CS " Сегмент команд " DS " Сегмент данных " SS "Сегмент стека" ES "Дополнительный сегмент" Пока наша программа помещается в 64К байт, вполне воз- можно хранить сегментную константу в регистре CS и изменять только смещение. Аналогично адрес сегмента в регистре DS мо- жет быть константой, пока область данных также не превышает 64К байт. Таким образом будут использованы только 16-разряд- ные адреса как для вызова команд, так и для доступа к данным. Это приведет к более эффективным кодам программ, чем при 20-разрядных адресах, а для большинства применений програм- мной области объемом 64К байт и области данных, также объе- мом 64К, вполне достаточно. Такой способ использования памя- ти будем называть моделью S (от слова Small — "малая"). Кроме этой модели существуют модель Р, если только программа пре- вышает 64К, модель D, если только объем данных превышает 64К, и модель L (от слова Large — "большая"), если и программа и данные превышают 64К. Тип модели можно сообщить компи- лятору, каждая из моделей имеет свою собственную библиотеку, так что операции компиляции и редактирования должны соот- ветствовать используемым моделям памяти. По умолчанию при- меняется модель S, если компилятору ничего не сообщается от- носительно модели памяти. Во всех описываемых здесь програм- мах будет использована модель S, поскольку даже достаточно большая чертежная программа, описываемая в главе 6, помеща- ется в 64К байтах. i Приведенное выше обсуждение является машино-ориентиро- ванным, оно вполне может быть опущено, если не иметь дела с
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 13 графикой. Однако, уже в следующей главе будет описано непос- редственное обращение к графическому адаптеру, как если бы он был размещен в нормальной памяти, начиная с адреса 0хВ8000. Заметим, что это 20-разрядный адрес, записанный в шестнадца- теричном формате. В дальнейшем для обозначения участка па- мяти, размещенного в графическом адаптере, будем применять термин "видеопамять". Было бы не очень удобно, если только из-за этого специального случая пришлось бы использовать мо- дели D или L. К счастью, в компиляторе Lattice С для работы с такой областью памяти имеются две функции записи и чтения данных соответственно: poke(segment, offset, source, nbytes) peek(segment, offset, destination, nbytes) В качестве первого аргумента будем задавать константу 0хВ800. Это только 16-разрядное число, поскольку, как уже го- ворилось выше, оно будет расширено четырьмя нулевыми бита- ми справа. Первые два аргумента имеют тип unsigned int ("целое без знака"). Второй аргумент представляет собой смещение от- носительно расширенного первого аргумента. Третий аргумент является обычным указателем на символ, так что он может быть именем массива символов. Четвертый аргумент имеет тип "целое без знака", в нем указывается сколько байтов должно быть скопировано. 1.2.4. Консольное прерывание Прервать выполнение программы можно путем нажатия ком- бинации клавиш Ctrl-Break, что означает нажатие на клавишу Break когда клавиша Ctrl удерживается в нажатом положении. Если на клавиатуре нет клавиши Break, то для этой цели исполь- зуется комбинация Ctrl-C. Для любого из этих способов преры- вания введем термин "Консольное прерывание". Есть два вида проблем, связанных с обработкой консольного прерывания, ко- торые очень важно решить, особенно для графических программ. Первая проблема заключается в том, что машина может "не ус- лышать" возможные попытки воспользоваться консольным пре- рыванием. При работе прикладной программы операционная си- стема проверяет появление консольного прерывания только в оп- ределенных ситуациях. В зависимости от того, был ли
14 Глава 1. ВВЕДЕНИЕ установлен "флаг проверки прерывания", операционная систе- ма выполняет такую проверку либо по "запросу на обслужива- ние", либо только по запросу на обслуживание консоли. Но при выполнении только вычислительных операций, например, как в цикле for (I - 0; i < 30000; Н+) s -н- 1 + 2 * (! / 2); никакого запроса на обслуживание не возникает, и выполнение программы не может быть прервано по консольному прерыва- нию. Эта проблема возникла у автора в более интересной вычис- лительной ситуации, чем приведенный здесь пример, и была сде- лана попытка найти безопасный запрос на обслуживание (пред- почтительно запрос на обслуживание консоли), который можно было бы использовать в цикле, чтобы заставить операционную систему выполнять желаемую проверку. Сначала было опробо- вано простое обращение к функции kbhit (см. параграф 1.2.2), которая работала достаточно удовлетворительно во многих си- туациях. Однако такой метод не срабатывал, когда многократно нажималась какая-нибудь клавиша перед тем, как использовать консольное прерывание. Это иногда происходит, например, если необходимо ввести некоторую букву и нужно нажать не только клавишу с этой буквой, но и клавишу "Ввод". Поэтому было найдено более универсальное решение с использованием сле- дующей функции: checkbreak() { charch; if (kbhit()) { ch - getch(); kbhit(); ungetch(ch); } } Если обращение к функции checkbreak будет стоять внутри цикла приведенного выше примера, то программа окажется чув- ствительной к консольному прерыванию. Вторая проблема заключается в вопросе: "Что произойдет, когда машина обнаружит запрос на консольное прерывание?" Если программист не предусмотрит каких-либо специальных действий, то консольное прерывание вызовет используемый по умолчанию обработчик прерывания, который просто остановит выполнение программы. В большинстве случаев это и будет же- лаемый результат, но в графической программе могут оказаться необходимыми другие действия, как увидим в параграфе 2.9.
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 15 В общем случае можно спланировать действия по прерыва- нию путем составления специальной функции, в которой опре- делено, что нужно делать в случае появления консольного пре- рывания. Адрес этой функции, указанный просто в виде имени этой функции, пересылается в качестве параметра при обраще- нии к функции onbreak, имеющейся в распоряжении компилято- ра Lattice С. Если наша функция возвращает значение 0, то вы- полнение программы продолжается с точки прерывания. В про- тивном случае выполнение программы немедленно прекращается. Для функции onbreak в качестве аргумента мо- жет быть задан нулевой указатель, представленный в виде чис- ла 0. В этом случае при консольном прерывании опять будет ис- пользован принимаемый по умолчанию обработчик прерывания. Если аргументом функции onbreak будет не 0, а имя функции, то эта функция должна быть объявлена как функция до ее исполь- зования, в противном случае компилятор по ошибке может при- нять ее имя за простую целочисленную переменную. Продемон- стрируем все эти ситуации в следующей программе. Она основа- на на программе из параграфа 1.2.2. Но вместо нажатия любой клавиши будем использовать нормальное консольное прерыва- ние. /* BREAKDEMO.C: /* #include "dos.h" int n-O, my_functlon(); double x-1.0; main() { onbreak(myfunction); /* Replace default Interrupt handler with myfunctlon */ /* Замена принимаемого по умолчанию обработчика */ /* прерывания на свою специальную функцию my_function */ while (n<30000) { х*- 1.000000001; n++; checkbreak(); } onbreak(0); /* Any program text Inserted here, when Interrupted, */ /* would Invoke the default Interrupt handler. */ /* Прерывание любой вставленной здесь программы вызо- */ /* вет принимаемый по умолчанию обработчик прерывания*/ prlntf /* "Normal program end" */ ("Нормальное завершение программы, п - 30000 х - %Г, х); } Console-break* demonstration */ Демонстрация консольного прерывания */
16 Глава I. ВВЕДЕНИЕ int my_function() { printf("ripepbiBaHMe после п - %d циклов, х - %f\ n, x); exit(O); } checkbreak() { char ch; if (kbhit()) { ch - getch(); kbhit(); ungetch(ch); } } /* Эта функция onbreak подключается только для Turbo С ! */ /* int onbreak(brfun) int (*brfun) (); { ctrlbrk(brfun); } */ Заметим, что в программе ту Junction имеется обращение к стандартной функции выхода exit Тем самым управление глав- ной программе не возвращается, и нет необходимости включать оператор возврата и определять, нужно или нет возобновлять выполнение программы, как было упомянуто ранее, В качестве завершающего замечания отметим, что в ПК фир- мы IBM имеется существенное различие между нажатиями ком- бинаций клавиш Ctrl-Break и Ctrl-C. Если пустить на выполне- ние программу BREAKDEMO.C и многократно нажать какую- либо клавишу перед использованием консольного прерывания, то комбинация Ctrl-Break сработает правильно, а комбинация Ctrl-C не вызовет никаких действий. Если какая-нибудь из кла- виш на клавиатуре будет нажата только один раз или совсем не нажата, то вполне возможно, что нажатие комбинаций клавиш Ctrl-Break и Ctrl-C вызовет одинаковый эффект. 1.2.5. Доступ к портам ввода/вывода микропроцессора 8088 Нижний уровень, на котором программируются операции ввода/ вывода, основан на машинных командах IN и OUT. Эти команды обычно применяются только на языке Ассемблера (но и на этом языке доступны операции ввода/вывода более высокого уровня, как будет показано в параграфе 1.2.6). По команде IN производится считывание данных с порта ввода, а по команде OUT данные записываются в порт вывода. Эти порты ввода/вы- вода выполнены в виде специальных электронных схем, исполь- зуемых компьютером для связи с внешними устройствами. Во всех программах высокого уровня для ввода/вывода в конце кон-
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 17 цов используются команды IN и OUT. Эти команды также до- ступны и на языке Lattice С, они кодируются следующим обра- зом: v - 1пр(р); outp(p, v); где переменные р и v имеют тип целый (без знака), причем через р обозначается адрес порта, a v — значение данных. При исполь- зовании функций inp и outp в начало программы включается строка: #include "dos.h" Некорректные прямые обращения к портам ввода/вывода мо- гут привести к различным системным ошибкам, так что настоя- тельно рекомендуется использовать функции ввода/вывода вы- сокого уровня везде, где это возможно. В программном обеспече- нии, описываемом в этой книге, если оно предназначено для работы на компьютерах с цветным графическим адаптером CGA, прямое обращение к портам ввода/вывода не используется. Но для платы Геркулес (или аналогичных монохромных графичес- ких адаптеров) функцию outp будем применять для переключе- ния из текстового режима в графический и обратно. 1.2.6. Регистры и программные прерывания Рассмотрим теперь как компьютер может осуществлять связь с внешним миром на более высоком уровне, чем непосредствен- ное обращение к портам ввода/вывода. Для/этого имеется Базо- вая Система Ввода/Вывода, сокращенно БСВВ (BIOS). He сле- дует ожидать, что переход на более высокий уровень потребует больших знаний об архитектуре компьютера, но, как ни странно, несколько углубиться в эту проблему все-таки придется. Нельзя эффективно применять БСВВ, если ничего не знать об общих ре- гистрах микрокомпьютера 8088 (или 8086). Следует принять во внимание, что процедуры БСВВ были специально разработаны для обращения к ним из программы на языке ассемблера, а не языке программирования высокого уровня, такого как язык Си. Нет необходимости обсуждать весь комплект регистров микро- компьютера 8088, можно ограничиться только четырьмя регист- рами данных, показанных на рис. 1.1.
18 Глава 1. ВВЕДЕНИЕ АХ I АН AL I Accumulator — Сумматор ВХ ВН I BL I Base — База 1 АН ВН СН DH AL I BL CL DL | СХ СН CL Count — Счетчик DX DH I DL \ Data — Данные Рис. 1-1. Регистры данных процессора типа 8088 Каждый из регистров АХ, ВХ, СХ, DX содержит 16 бит. Они состоят из старшего и младшего байтов, каждый из которых мо- жет быть использован как 8-разрядный регистр. Например, если шестнадцатеричное число FA3B загружается в регистр DX, то со- держимым байтов DH и DL будут FA и ЗВ соответственно. Другой технический аспект заключается в способе обраще- ния к БСВВ. Программы БСВВ вызываются не как подпрограм- мы, а через так называемое "программное прерывание". Этот термин происходит из аналогии с механизмом прерывания, кото- рый действует для обработки сигналов из внешнего мира. Выпол- нение программы на этот момент действительно прерывается, но таким образом, что впоследствии будет возобновлено ее нор- мальное выполнение. При возникновении прерывания содержи- мое всех регистров записывается в стек и выполняется переход по адресу, который считывается из таблицы, носящей название "вектор прерываний". Фрагмент программы, начинающийся с этого адреса, называется "программой обработки прерывания". Она заканчивается командой "Возврат из прерывания", которая обеспечивает восстановление содержимого всех регистров, со- храненных в стеке, и после этого возобновляет выполнение пре- рванной программы. Таким же образом, как и по внешнему пре- рыванию, наша программа может быть "прервана" специальной командой, называемой "Программное прерывание". Поскольку программист сам решает, когда должно иметь место программ- ное прерывание, оно концептуально выглядит как обращение к подпрограмме, но реализовано аналогично обработке внешнего прерывания. Только вместо адреса подпрограммы, необходимо указать некоторое число (обычно маленькое) для идентифика- ции программного прерывания. Для всех программных прерыва- ний, ассоциированных с видеодисплеем, это число разно 16, что в шестнадцатеричном виде записывается как 10. В программе на
1.2. НЕСКОЛЬКО ЗАМЕЧАНИЙ ДЛЯ ПРОГРАММИСТОВ 19 языке ассемблера для вызова любого программного прерывания, связанного с отображением информации на видеодисплее, ис- пользуется команда INT 10H Поскольку прерывание ЮН применяется для многих целей, необходимо иметь возможность использовать механизм переда- чи параметров. Вот почему в этом контексте потребовалось рас- смотреть регистры, изображенные на рис. 1.1. Для компилятора Lattice С, можно записать: int86(0x10, &regsin, &regsout); Первым аргументом указывается номер прерывания ЮН = 0x10. Второй и третий аргументы указывают на адреса структу- ры данных, соответствующих регистрам, предполагаемым для использования в эквиваленте программы на языке ассемблера. Любая информация, предназначенная для передачи в программу обработки прерывания, должна быть записана в структуру regsin, а любая информация, возвращаемая программой, будет доступна через структуру regsout. Для использования этого ме- ханизма в начало программы следует включить строку #include "dos.h" и сделать объявление union REGS regsln, regsout; В заголовочном файле dos.h тип union REGS объявлен в виде следующей последовательности из трех строк: struct XREG (short ax, bx, ex, dx, si, di;); struct HREG ( byte al, ah, bl. bh, cl, ch, dl, dh;); union REGS (struct XREG x; struct HREG h;); В объекте типа union REGS структуры х и h занимают одну и ту же область памяти. Например, элемент regsin.x.ax занимает общую память с элементами regsin.h.ah и regsin.h.al аналогично тому, как двухбайтовый регистр АХ состоит из двух однобайт- ных регистров АН и AL и так далее. (Не обращайте внимание на элементы si и di в первой из трех строк, а также на порядок пере- числения al и а/г во второй строке: не стоит в данный момент об- суждать организацию процессора £088). Прежде чем будет вы- полнено программное прерывание (с кодом ЮН), необходимо в
20 Глава 1. ВВЕДЕНИЕ регистр АН занести специальный код функции. На языке Си это делается следующим образом: regsin.h.ah - code; Int86(0x10, &regsint &regsout); Значение кода code сообщает прерывающей программе, ка- кое требуется действие из нескольких возможных. Для наших целей нет необходимости в двух различных аргументах regsin и regsout: в обоих случаях годится одна и та же структура regs, по- этому объявление можно упростить: union REGS regs; и использовать одно имя regs вместо имен regsin и regsout. Кроме регистра АН должны быть заполнены и некоторые другие регист- ры, чтобы более детально описать наши желания. Это будет опи- сано в параграфе 2.3. 1.2.7. Максимальный размер стека Автоматические переменные и адреса возврата из функций размещаются в стеке, представляющем непрерывный участок памяти. Размер стека по умолчанию ограничен и он может ока- заться недостаточным, особенно если применяются рекурсивные обращения к функциям. Компилятор Lattice С дает возможность установить максимальный размер стека больше, чем предусмот- рено по умолчанию. Если принимаемое по умолчанию значение (2048) желательно заменить на 15 000, то в программу нужно включить одну дополнительную строку: unsigned int _Stack - 15000; main () { -. } Теперь размер стека может достигать 15 000 байт. Это новое значение, в свою очередь, может быть переопреде- лено при пуске программы на выполнение. Например, если про- грамма называется MYPROG.EXE, то размер стека может быть установлен равным 20 000 байт при вводе команды MYPROG -20000
1.3. ГРАФИЧЕСКИЕ АДАПТЕРЫ 21 Поскольку предполагается, что наши программы будут ис- пользоваться людьми, которые не интересуются такими деталя- ми, как размер стека, то предпочтем предыдущий способ задания максимального размера стека. Если, как в этом примере, значе- ние 20 000 указано для параметра _STACK в тексте программы, то пользователь может запустить программу просто командой: MYPROG с таким же успехом, как и более расширенной командой, приве- денной в качестве примера выше. 1.3. ГРАФИЧЕСКИЕ АДАПТЕРЫ На экране дисплея имеется множество точек, каждая из кото- рых может быть либо подсвеченной, либо темной, или, как иног- да говорят, белой или черной. Экран может быть и цветным, но пока будем считать, что имеются только два цвета: белый и чер- ный. Эти черные или белые точки называются элементами кар- тинки или, сокращенно — пикселами (сокращение английских слов ''picture elements" — pixel или, иногда, pels). Чем больше пикселов размещается на экране, тем выше разрешающая спо- собность. Графический адаптер, он также иногда называется платой, является частью оборудования, которое и определяет эту разрешающую способность. Три из наиболее известных гра- фических адаптеров имеют следующие параметры: • Монохромный дисплейный адаптер, только для вывода тек- ста, может отобразить 25 строк по 80 символов в каждой строке; • Монохромный графический адаптер (например, плата Гер- кулес) , применяется для вывода как текстовой, так и графи- ческой информации. При использовании для вывода текста обладает такими же параметрами, как монохромный дисп- лейный адаптер. При выводе графической информации имеет разрешаю- щую способность 720 * 348 пикселов (348 строк по 720 пик- селов в каждой). • Цветной графический адаптер применяется для вывода как текстовой, так и графической информации. При выводе тек- ста отображает 25 * 80 символов. При выводе графической
22 Глава 1. ВВЕДЕНИЕ информации имеет разрешающую способность 640 х 200 пикселов (или меньше, если используются и другие цвета кроме белого и черного). Эти адаптеры содержат свою собственную память, к которой можно обращаться как к обычной памяти. Текстовая и графиче- ская информация отображается путем засылки соответствую- щих данных в эту область памяти. Однако способ кодирования данных для текста совершенно отличен от способа кодирования графической информации. Для текста значение кода ASCII для каждого отображаемого символа записывается в один байт, за которым следует так называемый байт атрибутов, содержащий информацию о форме отображения этого символа. (Например, это может быть атрибут, определяющий подчеркивание симво- ла). Преобразование этих двух байт в пикселное представление выполняется специальным аппаратным устройством, называе- мым генератором символов. Это дает эффективный способ ото- бражения символов. На экране имеется 25 * 80 = 2000 позиций символов, а каждый символ кодируется двумя байтами, следова- тельно всего необходимо 4000 байт. Это количество очень неве- лико, если представить, что каждый символ изображается в пря- моугольнике 9x14 пикселов (14 горизонтальных строк по 9 пик- селов в каждой). Для каждого пиксела нужен по крайней мере один бит, поэтому для отображения всех символов на экране по- требовалось бы 2000 х14х9/8 = 31 500 байт. Используемые 4000 байт размещаются по адресам от В0000 до B0F9F. Очень важно иметь в виду, что при использовании монохромного дисп- лейного адаптера нельзя выключить генератор символов. Поэ- тому монохромный дисплейный адаптер не может использовать- ся для вывода графической информации. Заметим также, что в названии этого адаптера отсутствует термин "графический", в отличие от названий двух других адаптеров. Монохромный графический адаптер является очень слож- ным устройством. (Напомним еще раз, что его совершенно нель- зя путать с монохромным адаптером, упомянутым выше. Избе- жать этого можно было бы применением часто используемого термина "плата Геркулес", но этот термин точен только в том случае, если адаптер действительно изготовлен фирмой Hercules Computer Technology, что не всегда имеет место). Во-первых, монохромный графический адаптер можно использовать для ге-
1.3. ГРАФИЧЕСКИЕ АДАПТЕРЫ 23 нерации символов точно также, как это делается в монохромном дисплейном адаптере. Однако в монохромном графическом адаптере можно подавить деятельность генератора символов, перейдя из "текстового режима" в "графический режим" (также называемом режимом "битового отображения"). В графическом режиме каждому пикселу отводится один бит в памяти. Если в этом бите записана 1, то соответствующий пиксел подсвечивает- ся, если 0 — то пиксел темный. В каждой горизонтальной строке имеется 720 пикселов, что соответствует 720 / 8 = 90 байтам. По- скольку всего имеется 348 строк, то общий объем необходимой памяти составляет 348 * 90 = 31 320 байт что с округлением со- ставляет 32К = 32 768 байт. В шестнадцатеричном обозначении для этой цели используются адреса памяти от В0000 до B7FFF. Кроме того, монохромный графический адаптер имеет еще одну "страницу" объемом 32К с адресами от В8000 до BFFFF. Обра- щение возможно к обеим страницам, но в каждый момент ото- бражается одна из них. Поскольку в работе всегда только одна из двух страниц, то нужно иметь возможность свободного выбора из них. Страницы, начинающиеся с адресов В0000 и В8000 обозна- чаются номерами 0 и 1 соответственно. Для графического вывода будем использовать страницу 1 по двум причинам. Во-первых, она начинается с адреса В8000, как и в цветном графическом дисплее. Во-вторых, страница 0 перекрывает память текстового дисплея, а страница 1 лежит вне пределов текстовой памяти. Так что если в странице 1 записано нечто нужное, то оно остается не- тронутым при переходе в текстовый режим. В главе 4 это свойст- во будет использовано для получения аварийного дампа графи- ческого экрана. Цветной графический адаптер необходим в тех случаях, когда желательно получить изображение в различных цветах. Подобно монохромному графическому адаптеру, этот адаптер может работать как в текстовом режиме, так и в режиме графи- ческого битового отображения. Однако применение такого адап- тера рекомендуется только тогда, когда действительно необхо- дим7 цвет в отображении, поскольку этот адаптер имеет три ос- новных недостатка: 1 - Символы генерируются в прямоугольной матрице 8 * 8 вме- сто 9x14 как в других адаптерах, что значительно ухудша- ет их читаемость.
24 Глава /. ВВЕДЕНИЕ 2 - В текстовом режиме при прокрутке текста на экране наблю- даются вспышки, что несколько раздражает. 3 - В графическом режиме обеспечивается сравнительно низкое разрешение, а именно 640 * 200. Фактически имеются три режима битового отображения для этого адаптера: 640 х 200 монохромных ("высокое разрешение"), 320 х 200 4 цвета, 160x100 16 цветов. При разрешении 640 х 200 необходимо 640 х 200 / 8 - 16 000 байт. Эта величина округляется до 16К, то есть занимается па- мять с адресами В8000,..., BBFFF. При намерении купить графический адаптер или комплект- ный ПК, необходимо сделать выбор между монохромным графи- ческим адаптером и цветным графическим адаптером (или ку- пить их оба!). Очевидно, что если требование наличия цвета яв- ляется существенным фактором, то необходим цветной графический адаптер. Если же нет, то лучшим выбором будет монохромный графический адаптер. Однако, имеется и другое соображение, которым не следует пренебрегать — нужно всегда учитывать, какой адаптер может быть использован в комбина- ции с применяемым программным обеспечением. Автор не имеет достаточных сведений о коммерчески доступных пакетах, чтобы дать общие советы по этому вопросу. По некоторым внутренним причинам фирмы IBM программы вывода графики Базовой Сис- темы Ввода/Вывода в операционной системе PC DOS были напи- саны для цветного графического адаптера. Это означает, что при разработке программ для монохромного графического дисплея придется работать на более низком уровне, чем для цветного гра- фического адаптера. В первом случае потребуется использовать прямой доступ к видеопамяти и к портам ввода/вывода вместо обращения к программам Базовой Системы Ввода/Вывода. Но на самом деле это не такой уж большой недостаток. Для цветного графического адаптера можно использовать любой способ, как это будет видно из главы 2, но, тем не менее, уже сейчас пред- почтительнее применять прямой доступ для записи в видеопа- мять. В ряде случаев, когда скорость работы имеет решающее значение, может оказаться желательным осуществлять програм- мирование некоторых подпрограмм на языке ассемблера и обхо-
1.3. ГРАФИЧЕСКИЕ АДАПТЕРЫ 25 дить процедуры Базовой Системы Ввода/Вывода при модифика- ции изображения на экране, поскольку эти процедуры работают сравнительно медленно. Однако, мы используем язык Си, а про- цедуры Базовой Системы Ввода/Вывода были написаны на язы- ке ассемблера, так что нельзя поручиться, что в нашем случае программы на языке ассемблера будут работать быстрее, чем процедуры Базовой системы. Фактически этот способ работает достаточно быстро, а что происходит — видно более четко при использовании языка Си, чем языка ассемблера. На последней стадии для ускорения выполнения операций можно потребовать переписать наиболее часто используемую функцию языка Си (с именем dot) на языке ассемблера и тогда окажется, что текст программы на языке Си уже использует прямой метод доступа и, следовательно, может быть переведен на другой язык програм- мирования буквально по командам. Заметим здесь, что все эти машино-зависимые аспекты и проблемы нижнего уровня в своей значительной части относятся к модулю, включающему четыре базовые графические функции initgr, move, draw, endgr, и пользователь этих функций не дол- жен беспокоиться о портах ввода/вывода или процедурах Базо- вой Системы Ввода/Вывода. Первоначально были разработаны две версии этого модуля, по одному для каждого типа графического адаптера, и пользова- тель мог выбрать тот, с которым он хочет работать. Однако впос- ледствии оказалось, что можно оставить только один модуль, если обеспечить возможность автоматического выбора режима. То есть оказалось возможным написать действительно переноси- мое программное графическое обеспечение в том отношении, что выполняемая программа могла быть использована на любом ПК фирмы IBM или совместимом с ним независимо от того, какой ус- тановлен графический адаптер, цветной или монохромный. В главе 2 будут описаны этапы разработки такого обобщенного мо- дуля.
Глава 2 ВЫЧЕРЧИВАНИЕ ЛИНИЙ 2.1. ЭКРАННЫЕ И ПИКСЕЛНЫЕ КООРДИНАТЫ В большинстве случаев очень удобно, если единица длины по горизонтали имеет те же самые реальные размеры, как и одна единица в вертикальном направлении. Поэтому в дальнейшем будем использовать координатную систему с координатами х и у с единицей измерения э каждом направлении примерно в один дюйм. Как это обычно принято в математике, направления коор- динатных осей х и у выберем вправо и вверх соответственно, а начало системы координат расположим в нижнем левом углу эк- рана. Было бы хорошо знать максимальные значения, которые можно использовать для каждой координаты. Вместо того, чтобы задавать точные размеры экрана монитора (которые могут ока- заться различными у разных мониторов), для этих максималь- ных значений были выбраны "круглые" числа 10 и 7. Эти числа достаточно просто запомнить, а их отношение довольно хорошо совпадает с отношением сторон по двум направлениям для боль- шинства экранов. Для удобства единицы длины будем обозна- чать термином "дюйм", что будет точным только в том случае, если монитор действительно имеет размеры 10 * 7 дюймов. В тексте программы числа 10.0 и 7.0 появляются только однажды — в определениях xjnax = 10.0 и yjnax = 7.0. Во всех других местах для обозначения этих чисел будут применяться перемен- ные xjnax и yjnax. 0 < х < xjnax (xjnax= 10.0) 0 < у < yjnax (yjnax- 7.0) Экранные координаты определяются вещественными числа- ми, поэтому используются обозначения 10.0 и 7.0 вместо 10 и 7.
2.1. ЭКРАННЫЕ И ПИКСЕЛНЫЕ КООРДИНАТЫ 27 В дальнейшем будут также применяться пикселные коорди- наты. Это целые числа и для их обозначения будем применять прописные буквы X и У. Их максимальные значения определим как X max и У max. Обратите внимание на двойной знак под- черкивания , что обеспечивает дополнительное различие меж- ду обозначением экранных и пикселных координат. О < X < X max (где X max равно либо 719 либо 639) О < У < У max (где У max равно либо 347 либо 199) Ось Y направлена вниз, поэтому начало координат в системе пикселных координат (X, У) = (0, 0) лежит в левом верхнем углу экрана. Поэтому в выражении для преобразования у в У содер- жится операция вычитания, чего нет в выражении для преобра- зования х в X. На языке Си определим следующие функции для выполнения двух преобразований: int IX(x) float х; { return (intXx*horfact-K).5); } int IY(y) float у; {return Y__max - (lntXy*vertfact+0.5); } где переменные horfact и vertfact означают коэффициенты масш- табирования, вычисляемые в функции initgr по формулам hgrfact - X max / x_max; vertfact - Y max / ymax; Заметим, что оператор приведения типов в виде ключевого слова int у заключенного в круглые скобки, приводит к усечению значения своего операнда. Таким образом добавление числа 0.5 к неотрицательному значению перед усечением приводит к округ- лению этого числа до ближайшего целого. Проверим воздействие этого операнда на предельные значения координат х и у: 1Х(0.0) - (int) (0.0 * X_max / x_max + 0.5) - 0; IX(xmax) - (Int) (x_max * X max / x_max + 0.5) - X max; I Y(0.0) - Y_max - (Int) (0.0 * Y_max / y_max + 0.5) - Y__max; I Y(yjnax) - Y max - (Int) (y_max * Y max / y_max + 0.5) - 0; Функции IX и /У очень полезны, поскольку они преобразуют заданные экранные координаты х и у, используемые на высшем пользовательском уровне, в пикселные координаты X и У, кото- рые необходимы на нижнем уровне при обращении к графиче- скому адаптеру. Функции move и draw, используемые в программах книги ав- тора "Принципы программирования в машинной графикеу\
28 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ предполагают существование текущей позиции пера. Эту пози- цию будем обозначать глобальными переменными XI и Y\, кото- рые выражаются в единицах пикселных координат. Эти пере- менные являются целочисленными, что особенно важно для микрокомпьютеров, у которых обычно отсутствуют технические средства для работы с плавающей точкой и желательно исполь- зовать целые числа везде, где это возможно. Для этих и некото- рых других переменных будем использовать ключевое слово static. Это означает, что они могут быть использованы только в том файле, в котором они определены. Весь набор графических функций и определений переменных будет собран в одном файле (или модуле), как это увидим в параграфе 2.10. Все эти функции имеют доступ к (внешним) статическим переменным, таким как XI и Y\, но программа пользователя не имеет к ним доступа, так что она не сможет неумышленно испортить эти значения. Функ- ция move в основном содержит операторы для изменения теку- щей позиции пера или, как иногда говорят, "текущей точки" или "текущей позиции": static int X1. Y1; move(x, у) double x. у; { Х1 - IX(x); Y1 - IY(y); check(X1, Y1); } с Определение функции check будет дано ниже. Как можно предположить, ее задача заключается в проверке, не будет ли точка, заданная параметрами XI и У1, лежать вне границ экра- на. Для переменных х и у будем использовать тип double вместо float. Это совсем не означает, что в данном случае нужна двойная точность, но вещественные аргументы в языке Си всегда преоб- разуются в двойную точность, то есть если бы и было включено определение float x, у, то параметры х и у все равно будут преоб- разованы в тип двойной точности. Обратимся теперь к одной из самых интересных функций из четырех базовых, а именно — к функции draw. Напомним, что ее задача заключается в вычерчивании отрезка прямой линии из текущей позиции пера в новую точку (х, у). Начальная точка отрезка задана статическими целочисленными переменными Х\ и Y\, а конечная точка — параметрами в формате с плавающей точкой хиу. Было бы хорошо иметь дело с целочисленными пик-
2.2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ В ЦЕЛОЧИСЛЕННОЙ АРИФМЕТИКЕ 29 селными координатами для обеих точек и задавать обе точки в виде параметров функции. Поэтому введем новую функцию drawjine, которая и выполнит почти всю работу. Эта очень важ- ная функция будет рассмотрена в параграфе 2.2, но используем ее уже здесь. Функцию draw можно записать следующим обра- зом: draw(x, у) double x, у; { int X2, Y2; Х2 - IX(x); Y2 - IY(y); check(X2, Y2); draw_line(X1, Y1.X2, Y2); X1-X2;Y1-Y2; } 2.2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ В ЦЕЛОЧИСЛЕННОЙ АРИФМЕТИКЕ Теперь предстоит разработать функцию drawjine. Посколь- ку скорость работы здесь является решающим фактором, окон- чательная версия реализации этой функции будет сравнительно сложной и без дополнительных объяснений ее понимание может быть затруднено. Поэтому здесь обсудим несколько версий, при- чем каждая новая версия будет уточнением предыдущей. В не- сколько иной форме рассматриваемый здесь алгоритм был опуб- ликован Брезенхамом в 1965 г. Между точками (XI у YD и (Х2> У2), координаты которых за- даются в обращении к функции drawjiine в качестве аргументов, обычно вычисляется большое число промежуточных точек. Предположим, что для каждой такой промежуточной точки (X, Y) можно высветить точку на экране обращением к функции dot(X, Y); Реализация функции dot, принадлежащей к нижнему уров- ню программного обеспечения, зависит от используемого графи- ческого адаптера и эта тема будет обсуждаться в параграфах 2.4 и 2.5. В данный момент основная задача заключается в определе- нии точек сетки (X, Y) на экране, которые должны быть исполь- зованы. Поскольку таких точек, подлежащих вычислению, мо- жет быть очень много, то предпочтительнее использовать только целочисленную арифметику, которая выполняется значительно быстрее, чем вычисления с числами с плавающей точкой. Однако
30 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ в нашей первой версии проигнорируем этот аспект с целью упро- щения изложения. Хороший результат получим только в том случае, если положение точки Р2 (Х2, У2) относительно точки Р1 (XI у Y1) грубо соответствует изображению на рис. 2.1. Р2 X Р1 X Рис. 2.1. Позиция точки Р2 относительно точки Р1 При движении от точки Р1 к Р2 перемещение осуществляется вправо и, возможно, вверх, но расстояние по вертикали между точками Р1 и Р2 должно быть не брлыые расстояния между ними по горизонтали или, другими словами: Х\ < XI Yl < Y1 У2-П < Х2-ХХ (В соответствии с обычными математическими определениями здесь считаем ось Y направленной вверх, временно игнорируя тот факт, что фактически ось Y направлена вниз, поскольку это не влияет на ход рассуждений). Теперь можно записать первую версию функции drawjiine: drawJine1(X1, Y1, Х2> Y2) int Х1. Y1, Х2, Y2; { float t; Int X, Y; t - (float) (Y2-Y1) / (float) (X2-X1); for(X-X1;X<-X2;X++) { Y-Y1+(int)(t*(X-X1) + 0.5); dot(X, Y); } } Для общего случая без упомянутых выше ограничений, необ- ходимо определить, какая из переменных X или У является неза- висимой, управляющей переменной цикла. Конечно, для этой цели должна быть выбрана та, которая соответствует наиболь- шей разности из | Y2 - У11 и \Х2 -XI | по абсолютному значе- нию. Кроме того, управляющая переменная должна увеличи- ваться, а не уменьшаться, и если выражение t* (X - XI) имеет
2.2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ В ЦЕЛОЧИСЛЕННОЙ АРИФМЕТИКЕ 31 отрицательный знак, то его величина должна быть уменьшена на 0.5 (вместо увеличения) перед усечением до целого числа. Не будем останавливаться здесь на этих деталях, поскольку их рас- смотрение не приведет к конкретным рекомендациям. Более ин- тересным будет улучшить функцию в другом отношении, а именно — заменой операции с плавающей точкой на целочис- ленную арифметику. Если бы окончательная версия была бы приведена сразу, то ее было бы очень трудно понять, поэтому лучше всего будет повышать уровень сложности постепенно, ма- лыми шагами. В данный момент опять предположим, что пози- ция Р2 относительно Р1 соответствует изображению на рис. 2.1. Для этого случая разработаем алгоритм, который пока использу- ет переменные с плавающей точкой, но таким образом, что это ограничение легко устранить, как это будет показано ниже. Рас- смотрим для примера рис. 2.2 с заданными позициями точек Р1 I Р2 5Ь- X X У 4Y- ххх 3h XX 2н ххх Р1 1Ь X X I I I I С I I I I I I I I I О 1 2 3 4 5 6 7 8 9 10 11 12 13 X Рис. 2.2. Точки PI, P2 и промежуточные (1,1) и Р2 (12,5). (ТоестьЛ:1 = 1, У1 = 1,Х2 = 12, У2 = 5). Для нахождения точек между точками Р1 и Р2 выполним сле- дующие операции. Вначале зададим Х = Я1иУ=У1.В цикле будем задавать приращение 1 для переменной X и будем остав- лять У либо неизменным, либо также увеличивать на единицу. Конечно, последний выбор должен осуществляться таким обра- зом, чтобы новая точка сетки (X, У) располагалась по возможно- сти ближе к прямой линии, проходящей через точки Р1 и Р2. Это означает, что расстояние по вертикали между новой выбранной точкой и этой линией не должно превышать значения 0.5.
32 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ Введем переменную d для обозначения этого расстояния: d= Yexact- Ygridpoint и потребуем, чтобы -0.5 < d < 0.5 Последнее неравенство обеспечивает условие для определе- ния необходимости давать приращение переменной У, как это реализовано в следующей функции: draw_line2(X1, Y1, Х2, Y2) int Х1, Y1, Х2, Y2; { intX,Y; float t, ' /* slope */ /* наклон */ d; /* deviation */ /* отклонение */ t - (float) (Y2-Y1) / (float) (X2-X1); d-0; Y-Y1; for(X-X1;X<-X2;X++) { dot(X.Y); d-H-t; if(d>0.5){Y++;d--;} } } Отклонение d, как было определено выше, вначале устанавлива- ется равным нулю и изменяется на каждом шаге цикла. Посколь- ку d обозначает насколько ниже точной линии лежит вычислен- ная точка, то значение d увеличивается на значение наклона /, если X увеличивается на единицу и если У остается без измене- нияГ Это условие не выполняется, если значение переменной d превышает значение 0.5. В этот момент нужно увеличить У на единицу. Очевидно, что в этот момент и отклонение d (равное Yexact - У) должно быть уменьшено на единицу. Таким образом видим, что переменная d обеспечивает прекрасное средство для принятия решения о необходимости приращения значения У. Попробуем теперь исключить переменные / и d типа плаваю- щей точки. Можно обнаружить, что точное значение переменной / в математическом смысле является рациональной дробью, по- скольку имеем У2-У1 Х2-Х1 где числитель и знаменатель представлены в виде целых чисел. Поскольку величина d вычисляется как конечная сумма элемен-
2.2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ В ЦЕЛОЧИСЛЕННОЙ АРИФМЕТИКЕ 33 тов, каждый из которых равен либо U либо -1, то d также являет- ся рациональной дробью и, подобно t, может быть записана в виде частного с знаменателем, равным Х2 - XI. Следовательно, можно просто перейти к целочисленным переменным Ги Д ко- торые получаются путем умножения значений I и J на знамена- тель Х2 - XI. Также желательно избавиться от нецелочисленной константы 0.5. Это также не трудно реализовать, поскольку для этого нужно просто в 2 раза увеличить знаменатель. Таким обра- зом, будем использовать Т = denom • t D = denom • d где denom = 2- (X2-XI) Теперь условный оператор в функции drowjinel заменим на if(D>dX){Y++;D — denom;} где dX = X2-X\ Компьютер быстрее выполняет операцию сравнения с нулем, чем с ненулевым значением, подобным dX. Поэтому введем переменную E = D-dX так что теперь условие d> dX можно заменить на условие Е > 0. Конечно, начальное значение для Е должно быть равно -dX, что соответствует D = 0. Это приводит к улучшенному варианту функции draw_line3: draw_line3(X1, Y1, Х2, Y2) int Х1, Y1, Х2, Y2; { int X, Y, Т, Е, dX, dY, denom; dX-X2-X1; dY-Y2-Y1; denom-dX«1; /*denom-2*dX */ T-dY«1; /* T -2*dY */ E-dX; Y-Y1; for(X-X1;X<-X2;X++) { dot(X,Y); E4-T; if(E>0){Y++;E —denom;} } } 2-275
34 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ 1 2 3 4 5 6 7 8 9 10 11 12 1 1 2 2 2 3 3 4 4 4 5 5 -3 5 -9 -1 7 -7 1 -13 -5 3 -11 -3 Таблица 2.1 Последовательные значения переменных X, У, £\ вычисляемых функцией drawjiinei для данных на рис. 2.2. Жэ*СХ\У); £+=Т; У++; E^denom; X Y E Y E Yexact 1 2 -17 1+4/11 1+8/11 2+1/11 3 -15 2+5/11 2+9/11 4 -21 3+2/11 3+6/11 3+10/11 5 -19 4+3/11 4+7/11 5 Поскольку эта функция использует только целые числа, ма- шина будет выполнять вычисления значительно быстрее, чего мы и добивались. Последнее совсем не обязательно, но с учебной точки зрения очень полезно применить функцию drawJiineZ для примера на рис. 2.2. Сделав это, легко получим Таблицу 2.1, ко- торая показывает изменение значений X, У, Е на каждом шаге вычислений. В шапке таблицы показаны выражения операторов на языке Си, взятые из функции drawjineb. На каждом шаге числовые значения в таблице получаются немедленно после вы- числения соответствующего оператора. Перед началом цикла будут определены начальные значения </Х=11, </У=4, denom = 22y Г = 8, £-11, У-1 Заметим что У-координаты во втором столбце представлены в виде целых чисел, которые получаются путем округления точ- ных значений Yexact, приведенных в последнем столбце табли- цы. Итак, мы получили быстрый алгоритм для вычерчивания от- резка прямой линии, соединяющей точки Р1 и Р2, при условии, что положение точки Р2 относительно точки Р1 соответствует изображению рис. 2.1. Осталось только обобщить функцию draw_line3 для получе- ния окончательного варианта функции drawjine, которая может воспринимать любые две точки Р1 и Р2. Введем переменную
2.3. ИСПОЛЬЗОВАНИЕ ПРЕРЫВАНИЯ ЮН 35 vertlonger, которая при значении 1 обозначает, что расстояние по вертикали между точками Р1 и Р2 больше, чем по горизонтали. Если vertlonger= 1, то независимой переменной будет У вместо X. Здесь будем использовать оператор while вместо оператора for, поэтому в цикле можно выполнять проверку переменной vertlonger для принятия решения, какой из переменных, X или У, нужно давать единичное приращение в качестве независимой переменной (аналогично управляющей переменной из цикла for). Другая переменная тогда будет зависимой переменной — ее приращение зависит от условия Е > 0. draw_line(X1, Y1. Х2. Y2) int Х1. Y1. Х2, Y2; { int X. Y,T, E, dX, dY, denom, Xinc - 1, Yinc - 1, vertlonger-0, aux; dX-X2-X1;dY-Y2-Y1; if(dX<0){Xinc--1;dX--dX;} if(dY<0){Yinc--1;dY--dY;} if (dY > dX) { vertlonger - 1; aux - dX; dX - dY; dY - aux; } denom - dX « 1; T-dY«1; E--dX;X-X1; Y-Y1; while (dX— >- 0) { dot(X, Y); if((E-H-T)>0) { if (vertlonger) X +- Xinc; else Y +- Yinc; E — denom; } if (vertlonger) Y +- Yinc; else X -f- Xinc; } } 2.3. ИСПОЛЬЗОВАНИЕ ПРЕРЫВАНИЯ ЮН ДЛЯ ПОДСВЕТКИ ПИКСЕЛОВ В параграфе 1.3 было сказано, что в состав графического адаптера включена довольно большая память, которую кратко принято называть экранной памятью. Обращение к нашей функ- ции dot приведет к записи единицы в один из разрядов этой эк- ранной памяти. Для достижения этого есть несколько способов. Один из них заключается в использовании сервиса, предлагае- мого программами Базовой Системы Ввода/Вывода (BIOS) опе- рационной системы. При этом можно использовать концепцию 2**
36 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ "программного прерывания", описанную в параграфе 1.2. Как утверждают некоторые эксперты, это единственный "честный" способ доступа к экранной памяти, но он применим только для цветного графического адаптера, поэтому в параграфе 2.4. рас- смотрим более общий способ. Для цветного адаптера в текст программы можно включить следующие строки: #include "dos.h" union REGS regs; dot(X, Y) int X, Y; regs.h.ah - 12; regs.h.al- 1 /* Предварительная версия с применением /* кодов БСВВ для подсветки пикселов /* Preliminary version using BIOS /* code to light pixel V */ */ V /* код цветности /* color code regs.x.cx - X; /* координата X /* X coordinate regs.x.dx - Y; /* координата Y /* Y coordinate int86(0x10, &regs, &regs); /* программное прерывание ЮН /* software interrupt ЮН } Задание программного прерывания ЮН с кодом 12 означает, что пиксел с координатами X и У должен быть подсвечен. В реги- стры АН, AL, CX, DX должны быть занесены соответствующие значения, как это поясняется комментариями в программе. Обозначения этих регистров приведены в параграфе 1.2. 2.4. НЕПОСРЕДСТВЕННЫЙ ДОСТУП К ЭКРАННОЙ ПАМЯТИ Можно обойти программы Базовой Системы Ввода/Вывода, использованные в параграфе 2.3. и записывать данные непосред- ственно в экранную память. Если бы программирование выпол- нялось на языке ассемблера, то описываемый здесь способ рабо- тал бы значительно быстрее, чем приведенный в параграфе 2.3, но сейчас программирование выполнялось на языке Си и можно подвергнуть сомнению возможность повышения эффективности. Тем не менее, функцию dot с программным прерыванием ЮН нельзя применить для монохромного графического адаптера, так
2.4. НЕПОСРЕДСТВЕННЫЙ ДОСТУП К ЭКРАННОЙ ПАМЯТИ 37 что все равно придется разработать свой собственный способ дос- тупа к экранной памяти, если не используется цветной графи- ческий адаптер. По соображениям симметрии, этот новый способ будет впоследствии применен и для цветного графического дисп- лея, но вначале рассмотрим его только для монохромного графи- ческого дисплея. Как уже известно, при обращении к функции dot(Xy У) устанавливается в единицу бит, соответствующий пик- селным координатам X и У. Начнем с простого примера. Реализуем следующее обраще- ние к функции: dot(0, 0); которое означает подсветку пиксела в левом верхнем углу экра- на. Будем использовать графическую страницу, которая начина- ется с адреса В8000. В единицу должен быть установлен бит 7 (самый левый бит) по этому адресу. Конечно, при этом все дру- гие биты в этой ячейке должны остаться неизмененными. Здесь окажется очень полезной побитовая операция ИЛИ, скомбини- рованная с оператором присваивания, то есть будет использован "оператор редактирования" 1=, а также функции реек и роке, описанные в параграфе 1.2. Функция реек будет использована для "чтения из" экранной памяти, тогда как функция роке — для "записи в" экранную память. Здесь потребуются обе эти функции: charch; peek(0xB800, 0, &ch, 1); /* из ячейки с адресом В800 данные переписываются в ch */ /* the contents of address B800 are assigned to ch */ ch 1-0x80; /* Устанавливается самый левый бит в ch */ /* leftmost bit of ch is set */ poke(0xB800, 0 &ch, 1); /* значение из ch переносится в ячейку памяти с адресом В800 */ /* the value of ch is placed into memory location B800 */ Напомним, что каждая строка на экране содержит 720 пиксе- лов, что соответствует 90 байтам. Поэтому следовало бы ожи- дать, что первый байт для строки У имеет адрес 0хВ8000 + 90 * У.
38 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ Однако по причинам, относящимся к аппаратной реализации растрового сканирования, адрес этого байта определяется как 0хВ8000 + Вх2000* (Y % 4) +90* (Y/ 4) Как известно, в языке Си операторы / и % обозначают операции получения частного и остатка от деления нацело соответственно. Таким образом получим следующие значения смещения (отно- сительно адреса В8000): Таблица 2.2 Смещения для монохромного дисплея Номер строки Смещение самой левой точки строки Y Y (относительно адреса 0*В8000) 0 о 1 0x2000 2 0x4000 3 0x6000 4 90 5 0x2000 + 90 6 0x4000 + 90 7 0x6000 + 90 344 7740 345 0x2000 + 7740 346 0x4000 + 7740 347 0x6000 + 7740 Каждая из начальных позиций соответствует точке (0, У). К этому значению нужно добавить величину, вычисленную в про- грамме как X I 8, чтобы получить адрес байта в котором нахо- дится бит для точки (X, У). Остаток от деления X % 8 исполь- зуется для определения требуемой позиции бита внутри этого байта. Поскольку значение Х=0 должно соответствовать самому левому биту, то есть биту 7, точный номер бита определяется из выражения 7 - X % 8. Теперь функцию dot(X, У) можно соста- вить следующим образом: /* Это предварительная версия функции dot, */ /* работает только для монохромного графического адаптера */ /* This is a preliminary version of the function dot; */ /* it works only for the monochrome graphics adapter V
2.4. НЕПОСРЕДСТВЕННЫЙ ДОСТУП К ЭКРАННОЙ ПАМЯТИ 39 dot(X, Y) kit X, Y; { int offset; char ch; offset - 0x2000 * (Y%4) + 90*(Y/4) + (X/8); peek(0xB800, offset, &ch, 1); ch l-1«(7-X%8); poke(0xB800, offset. &ch, 1); } Вместо выражений X/8 X%8 Y/4 Y%4 будем использовать выражения X»3 X&7 Y»2 Y&3 Операторы » (сдвиг вправо) и & (побитовая операция И) ра- ботают вероятно быстрее, чем операции деления / и %, а дают тот же самый результат. В этом можно убедиться, если записать все операнды в двоичной форме. Таким образом предпоследний оператор в функции dot может быть переписан в виде: ch l-1«(7-(X&7)); Но и этот оператор можно записать в форме, работающей быстрее: ch |-0х80»(Х&7) поскольку вместо сдвига 1 в числе 0000 0001 на 7 - (X & 7) пози- ций влево можно взять число 1000 0000, и сдвигать единичный бит на X & 7 позиций вправо. Тогда получим следующий вариант функции: /* Улучшенная версия функции dot; V /* только для монохромного графического адаптера */ dot(X, Y) Int X, Y; { Int offset; char ch; offset - 0x2000 * (Y&3) + 90 * (Y»2) + (X»3); peek(0xB800, offset, &ch, 1); ch l-0x80»(X&7); poke(0xB800, offset, &ch, 1); } Для монохромного графического адаптера все сделано. На этом можно было бы закончить данный параграф, поскольку для цветного графического адаптера функция dot уже была получе-
40 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ на "честным" способом в параграфе 2.3, но зачем давать два ре- шения, если нужно только одно? Один из доводов заключается в том, что желательно иметь одну функцию dot для обоих адапте- ров и, хотя это вполне возможно, применение, двух существенно различных способов в одной функции выглядит совсем не эле- гантно. Кроме того, область применения разрабатываемых фун- кций в дальнейшем будет расширена и тогда все проблемы будут решаться гораздо проще при наличии совместимого подхода к обоим типам адаптеров. Напомним, что эта проблема уже обсуж- далась почти в самом конце главы 1. В противовес функции dot, приведенной в параграфе 2.3, следующая версия показывает, что фактически происходит в экранной памяти: /* Это другая версия функции dot; */ /* она работает только с цветным графическим адаптером и может */ /* заменить версию этой функции, приведенную в параграфе 2.3 */ dot(X, Y) Int X, Y; { int offset; charch; offset - 0x2000 * (Y&1) + 80 * (Y»1) + (X»3); peek(0xB800, offset, &ch, 1); ch l-0x80»(X&7); poke(0xB800, offset, &ch, 1); } Эта версия аналогична последней версии функции для моно- хромного графического адаптера. Имеется экранная память, со- стоящая из четырех частей по 8К байт каждая (8К - 0x2000). Здесь используются две части указанного размера, первая — для четных строк 0, 2, ..., 198, а вторая — для нечетных строк 1, 3, ..., 199. Значения смещений приведены в Таблице 2.3. Последние две версии функции dot отличаются некоторыми константами. Комбинация двух функций в одну теперь может быть осуществлена очень просто, путем использования перемен- ных вместо констант при условии, что этим переменным можно будет приписать точные значения до того, как появится обраще- ние к функции. Это сделаем в следующем параграфе. 2.5. РАСПОЗНАВАНИЕ ТИПА АДАПТЕРА Было бы ужасно, если бы для каждой операции нижнего уров- ня пришлось бы обсуждать по отдельной версии программы для
2.5. РАСПОЗНАВАНИЕ ТИПА АДАПТЕРА 41 0x2000 0x2000 0jc2000 0x2000 + + + 0 80 80 160 160 7920 7920 Таблица 2.3 Смещения для цветного дисплея Номер строки Смещение для левой точки строки У У (относительно адреса 0хВ8000) о 1 2 3 4 5 198 199 каждого адаптера. В частности, уже было высказано пожелание о реализации единой версии функции dot для обоих графических адаптеров. Такой более унифицированный подход возможен при использовании определенных переменных, значения которых зависят от типа применяемого адаптера. Примерами таких пере- менных являются X max и У max, введенные в параграфе 2.1. (Поскольку они никогда не изменяются для одного адаптера, то их можно рассматривать как константы, но дальше мы увидим, что их можно определять в процессе выполнения программы, и поэтому для них лучше использовать термин "переменная"). Аналогично упомянутым двум переменным введем еще "пере- менные" cl, с2, сЗ, значения которых вместе со значениями пе- ременными X max и У max приведены в Таблице 2.4 Таблица 2 А Значения констант для графических адаптеров Монохромный Цветной графический адаптер графический адаптер 639 199 1 80 1 X max Y max с\ а сЪ 719 347 3 90 2
42 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ С применением переменных cl, с2, сЪ запишем следующую обобщенную функцию dot, которая может применяться для лю- бого типа адаптера: /* Это окончательный вариант функции dot */ /* в том виде, как она будет применяться */ /* This Is the function dot as it will be used */ dot(X, Y) Int X. Y; { int offset; char ch; offset - 0x2000 * (Y&d) + c2 * (Y»c3) + (»3); peek(0xB800, offset, &ch, 1); ch l-0x80»(X&7); poke(0xB800, offset, &ch, 1); } Теперь следует побеспокоиться, чтобы переменным cl, c2, сЗ были присвоены соответствующие значения, и это присвоение должно осуществляться только во время выполнения программы. Программа, использующая наши графические функции, сможет выполняться на любом ПК, совместимом с ПК фирмы IBM, при условии, что он содержит либо монохромный графический адап- тер, либо цветной графический адаптер. Не очень красивым бы- ло бы решение, когда у пользователя необходимо запрашивать сообщения, какой тип адаптера доступен. К счастью, это не яв- ляется необходимым. Вместо этого можно использовать подпрог- рамму Базовой Системы Ввода/Вывода с программным прерыва- нием ПН, которое определяет, установлен ли цветной графичес- кий адаптер. Если нет, то должен иметься монохромный графический адаптер, но и на это нельзя надеяться, поскольку в наличии может оказаться только монохромный дисплейный адаптер, способный отображать только текстовую информацию. Если после обращения к программному прерыванию ПН по крайней мере один из битов 4 и 5 равен нулю, то в состав техни- ческих средств входит цветной графический адаптер. Ситуация оказывается более сложной, если в обоих этих битах окажется единица, поскольку тогда необходимо будет проверить, действи- тельно ли это монохромный графический адаптер. Эту проверку можно выполнить попытавшись записать что-нибудь по адресу 0хВ8000, и затем прочитать это число. Если такая попытка за- вершится неудачей и будет прочитано не то, что было записано, то графического адаптера нет. Если проверка закончилась ус-
2.5. РАСПОЗНАВАНИЕ ТИПА АДАПТЕРА 43 пешно, значит какой-то графический адаптер есть, а поскольку это не цветной графический адаптер, логично предположить, что это монохромный графический адаптер. Функция icolor, приве- денная ниже, возвращает значение: 1 если есть цветной графический адаптер; О если есть монохромный графический адаптер; -1 если есть монохромный дисплейный адаптер, непригодный для вывода графического изображения. #include "dos.h" union REGS regs; intlscolor() { charchO, ch1, x; Int86(0x11, &reg-s, &regs); If ((regs.x.ax & 0x30) I- 0x30) return 1; /* Color graphics */ /* Цветной графический адаптер V outp(0x3BF, 3); /* Configuration switch, see Section 2.6 */ /* Переключатель конфигурации, см. параграф 2.6 */ peek(0xB8O0, 0, &ch0, 1); /* Try to read chO from screen memory */ /* Попытка прочитать chO из экранной памяти */ ch1 - chO Л OxFF; /* Find some value different from ChO */ /* Задание некоторого числа, отличного от chO */ poke(0xB800, 0, &ch1, 1); /* Try to write this Into screen memory */ /* Попытка записать это число в экранную память */ реек(0хВ800, 0, &х, 1); /* Try to read the latter value */ /* Попытка прочитать это последнее значение */ роке(хВ800, 0, &ch0, 1); /* Restore the old value chO */ /* Восстановление старого значения chO */ return (x - - ch 1 ? 0 : 1); /* Has written value been read? */ /* Было ли прочитано записанное число? */ } Хотя обращение к этой функции может быть помещено в лю- бом месте программы, его нецелесообразно включать в состав функции dot, поскольку она вызывается очень много раз и поэто- му должна работать как можно быстрее. Нам еще предстоит раз- работать функцию initgr для инициализации графического ре- жима и это будет тем местом, куда необходимо вставить обраще- ние к функции iscolor, как будет показано в следующем параграфе.
44 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ 2.6. ВКЛЮЧЕНИЕ ГРАФИЧЕСКОГО РЕЖИМА Будем различать два режима работы машины — текстовый режим и графический режим. Нормально машина работает в тек- стовом режиме, а это означает, что символы, отображаемые на экране, формируются генератором символов. Значения кодов ASCII этих символов, каждый с так называемыми атрибутами, записаны в памяти адаптера (который не обязательно должен быть графическим), что уже обсуждалось в параграфе 1.3. Гра- фические функции будут выполняться только в том случае, если будет выполнено переключение на графический режим, который иногда также называется режимом "битового отображения". Но с этим нужно быть очень осторожным, поскольку в графиче- ском режиме операционная система не может отображать ника- ких сообщений и если будем вводить текст с клавиатуры, он не будет отображаться на экране. В параграфах 2.8 и 2.9 эта ситуа- ция будет рассмотрена более детально. Если требуется перейти в графический режим, то опять действия будут несколько проще с цветным графическим адаптером. В любом случае необходимо воспользоваться функцией initgr, но ее зависимая от типа адап- тера часть будет обращаться к одной из двух функций initcolgr и initmongr в зависимости от типа адаптера. Как уже упоминалось в параграфе 2.5, другой задачей функции initgr будет запрос типа используемого адаптера. Как только станет известен тип адапте- ра, будут определены значения некоторых важных глобальных переменных: int in_textmode-1, colorgr, X max, Y max; static int с 1,c2,c3; float x_max-10.0, y_max-7.0, horfact, vertfact; /* Это функция инициализации для графического режима */ /* This Is an initialization function for graphics */ InltgrO { if(!in_textmode) error ./* "initgr is called in graphics mode" */ ("Обращение к функции Initgr в графическом режиме"); colorgr -iscolor(); if (colorgr < 0) error /* "Wrong display adapter" */ ("Неверный дисплейный адаптер"); If (colorgr) { inltcolgrQ; /* Enter graphics mode (color graphics) */ /* Переход в графический режим (цветная графика)*/ Х_тах - 639; Y_max - 199;
2.6. ВКЛЮЧЕНИЕ ГРАФИЧЕСКОГО РЕЖИМА 45 с1-1;с2-80;сЗ-1; } else { initmongr( );/* Enter graphics mode (monochrome graphics) */ /* Переход в графический режим (монохромная графика) */ Х_тах - 719; Y_max - 347; d-3;c2-90;c3-2; } in_textmode-4); horfact - X тах/хтах; vertfact - Y^max/y_max; } Для этой программы необходимо определить значения пере- менных X max, Y max, xjnax, yjnax, horfact, vertfact, кото- рые упомянуты в параграфе 2.1, и новые переменные с\, с2, сЗ, injextmode, colorgr. Напомним, что ключевое слово static пред- полагает, что эти (внешние) переменные не сообщаются редак- тору связей, поэтому другие модули не имеют к ним доступа и, следовательно, не могут их изменить. Опустим это ключевое слово для тех переменных, которые могут быть полезны в про- граммах, обращающихся к графическим функциям. Введение переменных injextmode и colorgr не является, строго говоря, не- обходимым, но они могут оказаться удобными. Переменную injextmode будем использовать в некоторых функциях (напри- мер, в initgr) для проверки корректности обращения к данной функции в соответствующем режиме. Например, если обраще- ние к функции initgr происходит, когда машина уже находится в графическом режиме, то будет выдано сообщение об ошибке и выполнение программы прекратится. Эту же внешнюю перемен- ную можно будет использовать и в других сложных программах для запроса, в каком режиме находится машина. Переменная colorgr может использоваться для запроса типа применяемого адаптера, без обращения каждый раз к функции iscolor. Теперь выполним конкретную задачу по входу в графический режим. В случае цветного графического адаптера для этой цели можно использовать программу Базовой Системы Ввода/Выво- да. Применим программное прерывание ЮН дважды. Для этого адаптера существуют несколько состояний отображения и теку- щее состояние можно запросить записью кода 15 в регистр АН. Сделав это, сохраним полученную информацию в переменной old_vid_state. Это дает возможность восстановить старое состоя- ние отображения позднее, когда будем возвращаться в текстовый режим. Для фактического перехода в графический режим в
46 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ регистр АН необходимо записать код 0. Для указания конкретно- го состояния отображения с разрешением 640 * 200 необходимо занести значение 6 в регистр AL: static int old_vld_state; /* This function switches to graphics mode (color graphics) */ /* Функция переключает в графический режим (цветная графика) */ initcolgr() { regs.h.ah - 15; /* Inquire current video state */ /* Запрос текущего состояния видеомонитора */ Int86(0x10, &regs, &regs); old_vld_state - regs.h.al; regs.h.ah - 0; /* Set graphics mode */ /* Установка графического режима */ regs.h.al - 6; /* 640 x 200. black/white */ /* черно/белый, 640 x 200 */ int86(0x10, &regs, &regs); } Переменная regs должна быть определена как в параграфе 2.5. Ее определение здесь не повторяется, поскольку все функ- ции должны быть объединены в одном модуле, в котором такое определение должно появиться только однажды. Для монохромного графического дисплея нельзя применять какую-либо из программ Базовой Системы Ввода/Вывода, так что такую операцию низкого уровня необходимо организовать самим. Как мы уже видели в параграфе 1.2, можно использовать функцию outp для выполнения элементарных команд вывода. Опишем вкратце, что необходимо сделать без детального обсуж- дения каждого шага. Исчерпывающую информацию об этом можно найти в книге Sargent and Shoemaker (1984) и в докумен- тации по имеющемуся монохромному графическому адаптеру, если оно есть. Переключение на графический режим занимает пять шагов: Шаг 1. На выходной порт 3BF посылается значение 3, так называемый переключатель конфигурации. Для этого выполним: outp(0x3BF, 3); Это обращение установит единицу в битах 0 и 1 ключа, что означает разрешение использования обеих графических страниц (0 и 1), хотя практи-
2.6. ВКЛЮЧЕНИЕ ГРАФИЧЕСКОГО РЕЖИМА 47 чески будем использовать только одну страницу (1). Когда в этих битах записаны нули, переклю- чатель конфигурации предохраняет от случайно- го включения графического режима. Этот шаг упомянут здесь единственно ради полноты. Фак- тически эта операция уже выполнена при запросе типа используемого адаптера в функции iscolor. Этот вопрос обсуждался в параграфе 2.5. Шаг 2. На выходной порт ЗВ8, порт управления режимом отображения, посылается значение 0x82: outp (0хЗВ8, 0x82); В двоичной форме значение 0x82 записывается как 1000 0010, то есть единица находится в битах 7 и 1, а в битахг6, 5, 4, 3,2,0 — нули. Установка единицы в бите 1 является наиболее существен- ным во всей операции переключения из текстово- го режима в графический. Страница 1, начиная с адреса 0хВ8000, выбирается установкой единицы в бите 7, в противном случае выбирается страни- ца 0, начинающаяся с адреса ОхВОООО. Другим важным действием этого шага является обнуле- ние бита 3. В результате будет запрещен доступ к видеодисплею до тех пор, пока в него не будет записана единица на шаге 5. Тем самым будет исключено раздражающее мерцание на экране во время выполнения действий следующих шагов. Шаг 3. В регистры 0,..., 11 контроллера ЭЛТ 6845 будут записаны значения, требующиеся для работы в графическом режиме. Эти данные относятся к способу работы растрового дисплея. Поскольку объяснение их смысла привело бы к анализу слишком сложных аспектов технической реали- OQTYuii rirrS2HLriIUwr''T тчг\гьс*пг\ nAnAUUP ПРИ ИРМ rVT«f*v ЗаЦИИ, Шрапичлшся njA/vi^ i*wj/w "ilLJIvnKEIE ^шл величин в десятичном формате: Регистр: 0 1 2 3 4 5 6 7 8 9 10 11 Значение: 53 45 46 7 91 2 87 87 2 3 0 0
48 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ Для записи требуемых данных будем исполь- зовать только два порта, а именно: 0384 — индексный регистр контроллера 6845; 0385 — регистр данных контроллера 6845. Сначала будем записывать номер регистра (0,..., 11) в индексный регистр, а затем заносить требу- емое значение в регистр данных. Например, зна- чение 53 в регистр 0 записывается следующим образом: outp(0x3B4, 0); /* выбор регистра 0 */ outp(0x3B5, 53); /* запись в него значения 53 */ Шаг 4. Заполним нулями целиком всю графическую страницу 1 (32К байт, начиная с адреса В8000). Для этого воспользуемся функцией роке, описан- ной в параграфе 1.2, копируя группу из 128 по- следовательных байт при каждом обращении к этой функции, что выполняется значительно быстрее, чем пересылка по одному байту. Опре- делим массив с именем zeros, содержащий 128 нулевых байт, и запишем: for 0 - 0; j < 256; j++) poke(0xB800. j « 7, zeros, 128); Поскольку j« 7 эквивалентно;'* 128, то началь- ный адрес последовательно принимает значения 0,128,256, ...и всего будет скопировано 256 * 128 « 32 768 байт. Шаг 5. Посылаем битовую последовательность 1000 1010 (шестнадцатеричный код 8А) в порт управ- ления ЗВ8. Теперь в бит 3 записана 1, что означа- ет разрешение доступа к видеодисплею, см. так- же шаг 2. В функцию initmongr включены комментарии, относящиеся к вышеописанным шагам: static char gtable[12]-{53,45,46, 7,91,2,87,87,2, 3, 0, 0}, zeros[128]; /* implicitly initialized to zero */ /* неявно инициализируется нулями */
2.7. ВЫХОД ИЗ ГРАФИЧЕСКОГО РЕЖИМА 49 /* This function switches to graphics mode (monochrome graphics) */ /* Функция переключения в графический режим (монохромная графика)*/ initmongrO { Intl. J; outp(0x3BF,3); /* Step 1 V /* Шаг 1 */ outp(0x3B8, 0x82); /* Step 2 */ /* Шаг 2 */ for(i-0;K12;i-H-) { outp(0x3B4,1); outp(0x3B5, gtable[ij; } /* Step 3 */ /* Шаг 3 */ for (j-0; j<256; J++) poke(0xB800, j « 7, zeros, 128); /*Step4V /*Шаг4*/ outp(0x3B8, 0x8A); /* Step 5 */ /* Шаг 5 */ } 2.7. ВЫХОД ИЗ ГРАФИЧЕСКОГО РЕЖИМА Для выхода из графического режима и возврата в текстовый режим необходимо обратиться к функции endgr, обещанной в па- раграфе 1.1. Однако с этим нужно быть очень осторожным. При возврате в текстовый режим экран будет очищен, поэтому если это сделать сразу же после завершения формирования изображе- ния, у нас не хватит времени даже заметить его! Неудовлетвори- тельным будет также решение о сохранении отображения сфор- мированной картинки в течение фиксированного периода време- ни. Гораздо лучшим будет решение, позволяющее сохранить картинку на экране до тех пор, пока не будет нажата какая-ни- будь клавиша на клавиатуре. Если для этой цели использовать функции scan/ или getchar, то ввод единственного символа не всегда сработает, поскольку многие программы считывают числа в начале выполнения программы и такая последовательность цифр обычно завершается вводом символа начала новой строки при нажатии клавиши "ввод" (обозначаемой также "RETURN" или "ENTER"). Этот символ новой строки может все еще нахо- диться во входном буфере и он будет немедленно стираться при считывании следующего символа. Для решения этой проблемы пришлось бы прочитать два сим- вола, приняв соглашение, что в качестве сигнала для стирания с экрана завершенной картинки необходимо ввести один или два символа. Но есть и лучшее решение. Вместо обращения к функ- циям scan/ или getchar используем функцию getch, которая осу- ществляет чтение непосредственно с клавиатуры, а не из "стан-
50 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ дартного входного файла" stdin. Другим очень хорошим свойст- вом функции getch является то, что она не дает эхо-отображения вводимого символа на экране, которое в графическом режиме во- обще нежелательно, и что считывание символа происходит не- медленно при нажатии клавиши (как читателю уже известно, функции getchar и scan/ фактически считывают введенный сим- вол только после нажатия клавиши RETURN, что позволяет вос- пользоваться клавишей возврата курсора (backspace) для исп- равления, пока не нажата клавиша ввода). Вот как может выгля- деть функция endgr: endgr() { getch(); /* Wait until any key Is hit and revert to text mode */ /* Ожидание нажатия любой клавиши и возврат в текстовый режим */ to_text(); } Фактически операция возврата к текстовому режиму выпол- няется функцией tojext. Такое определение функции tojtext для конкретной задачи имеет еще и то преимущество, что ее можно использовать и в других ситуациях для немедленного возврата в текстовый режим, если вдруг это окажется необходимым. Как для операции включения графического режима, описанной в па- раграфе 2.6, для операции возврата также необходимо опреде- лить две зависимые от типа адаптера функции endcolgr и endmongr и вызывать ту, которая необходима в конкретной си- туации: to_text() { If (ln_textmode) error /* "endgr or to_text is called In textmode" */ ("функции endgr или totext вызваны в текстовом режиме"); If (colorgr) endcolgr(); else endmongr(); } Для цветного графического адаптера: /* This function reverts to text mode (color graphics): */ /* Функция восстановления текстового режима (цветная графика): */ endcolgrQ { regs.h.ah - 0; regs.h.al - old_vid_state; Int86(0x10, &regs, &regs); }
2.7. ВЫХОД ИЗ ГРАФИЧЕСКОГО РЕЖИМА 51 Для монохромного графического адаптера снова необходимо выполнить несколько элементарных действий: Шаг 1. В порт управления режимом отображения засы- лается значение 0: outp (0хЗВ8, 0); Поскольку в бит 1 записан 0, то это уже опреде- ляет возврат в текстовый режим. Однако и бит 3 обнулен, а это приводит к запрету обращения к видео дисплею (см. также шаг 2 в параграфе 2.6, который обсуждался в связи функцией initmongr. Шаг 2. Как и на шаге 3 в параграфе 2.6 необходимо запи- сать определенные значения в регистры 0,..., 11 контроллера ЭЛТ 6845. Опять, не входя в техни- ческие детали, приведем список требуемых зна- чений. Регистр: 01 23456789 10 11 Значение: 97 80 82 15 25 6 25 25 2 13 11 12 Шаг 3. В экранную память занесем коды пробелов. Для каждого символа должны быть записаны два бай- та: - символ пробела, шестнадцатеричный код 40; - значение атрибута 7. Обращение к функции роке будет происходить 256 раз, копируя каждый раз по 16 байт, что со- ставит 256 * 16 - 4096 байт. На экране имеем 25 строк по 80 позиций, что составляет 2000 пози- ций. Поскольку для каждой позиции необходимы байт значений и байт атрибута, то практически используются 2000 * 2 - 4000 байт. Шаг 4. Битовая последовательность 0000 1000 (шест- надцатеричное число 08) посылается в порт уп- равления ЗВ8. Установкой единицы в бит 3 разре- шается доступ к видеодисплею, см. также шаг 1. Текст программы функции endmongr: static char ttable[12]-{97, 80, 82. 15, 25, 6, 25, 25, 2, 13, 11, 12}; /* This function reverts to text mode (monochrome graphics): */ 7* Функция восстанавливает текстовый режим (монохромная графика): */
52 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ endmongr() { Intl. J; outp(0x3B8,0); /* Step 1 */ /* Шаг 1 */ for(M);K12;l++) { outp(0x3B4,1); /* Step 2 */ /* Шаг 2 */ outp(0x3B5, ttable[ij; } /*Step3*/ ЛШагЗ */ for 0-0; J<256; J++) poke1(0xB000, j «4, M\40\7\40\7\40\7\40\7\40\7\40\7\40\7\40\7", 16); outp(0x3B8, 0x08); /* Step 4 */ /* Шаг 4 */ } 2.8. АВАРИЙНОЕ ЗАВЕРШЕНИЕ ПРОГРАММЫ В начале этой главы была использована функция check для проверки попадания точки (X, Y) в пределы границ экрана. Сама операция проверки тривиальна, но проблема заключается в оп- ределении необходимых действий в случае отрицательного ре- зультата проверки. Не будем сейчас пытаться исправить оши- бочную ситуацию, а попробуем просто напечатать сообщение об ошибке и завершить выполнение программы обращением к функции exit(l). Так как система в этот момент находится в гра- фическом режиме, то ее сначала нужно перевести в текстовый режим. Этот переход можно осуществить немедленно, используя функцию tojtexty описанную в параграфе 2.7, так как вероятнее всего картинка в этом случае будет неправильной и ее незачем рассматривать. Но, с другой стороны, нам может понадобиться та часть картинки, которая была сформирована до появления ошибки, чтобы получить данные для отладки программы. А для этого нужно заставить компьютер ожидать нажатия какой-либо клавиши на клавиатуре, аналогично действиям при работе функции endgr. Однако при этом возникает другой нежелатель- ный эффект, заключающийся в том, что при долго работающей программе, например, при удалении невидимых линий, опера- тор может очень долго полагать, что программа продолжает ра- ботать, тогда как она просто не может завершить работу из-за ошибки и фактически ожидает реакции оператора! Простым ре- шением в этом случае может быть вычерчивание диагонали на весь экран из левого нижнего угла в правый верхний угол в ка- честве сигнала от компьютера, что что-то неверно и необходимо нажать на любую клавишу для перехода в текстовый режим.
2.8. АВАРИЙНОЕ ЗАВЕРШЕНИЕ ПРОГРАММЫ 53 После этого на экране появится сообщение об ошибке. Но до на- жатия клавиши можно иметь столько времени, сколько необхо- димо для анализа графического изображения, сформированного до этого момента. Для этой цели составим функцию fatal. Она вызывается из функции check, если обнаруживается, что пози- ция заданной точки будет неверной. К этой функции можно об- ратиться и непосредственно из программы в случае фатальной ошибки, требующей перехода в текстовый режим. Заметим, что эта функция не прекращает выполнения программы, а передает управление вызвавшей функции. После обращения к функции fatal обычно следуют оператор печати сообщения об ошибке и вызов функции exit. Ниже приведены тексты двух функций: fatal() { draw_line(0, Y max, X max, 0); endgr(); } check(X, Y) int X, Y; { if (X < 0 I I X > X_max I I Y < 0 I I Y > Y_max) { fatal(); prlntf /* "Point outside screen (X and Y are pixel coordinates" */ ("Точка вне экрана (X и Y в пикселных координатах):\п"); printf("X - %d Y - %d\n", X, Y); printf("x - % 10.3f у - % 10.3f\n", X/horfact, (Y_maxY)/vertfact); exit(1); } } Аргументами функции check являются пикселные координа- ты, но пользователь обычно работает в своих собственных коор- динатах, поэтому по значениям пикселных координат X и У про- изводится примерный пересчет в исходные координаты х и у. На- помним, что в графическом режиме операционная система не может выводить на экран какие-либо сообщения в случае серьез- ных фатальных ошибок, например, при переполнении стека. По- этому в графических программах чрезвычайно важно предотвра- тить возможность появления таких ошибочных ситуаций. Что касается переполнения стека, что может произойти при работе рекурсивных программ, то целесообразно предусмотреть увели- чение размера памяти, доступной для стека, по сравнению с при- нимаемым по умолчанию значением, скажем, 2048 байт. При
54 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ работе с компилятором Lattice С можно задать размер памяти, отводимой для стека, записав оператор: unsigned int _STACK - 15000; для определения максимального размера стека в 15 000 байт. Эта строка будет включена в наш графический пакет. 2.9. ИСПОЛЬЗОВАНИЕ КЛАВИШИ BREAK В ГРАФИЧЕСКОМ РЕЖИМЕ Наши графические функции, разработанные в предыдущих параграфах, еще не завершены. Если их использовать в сущест- вующем виде, то любая попытка воспользоваться консольным прерыванием при работе в графическом режиме приведет либо к нежелательному эффекту, либо вовсе не будет замечена. Как мы уже видели в параграфе 1.2, операционная система не всегда об- наруживает нажатие комбинаций клавиш Ctrl-Break или Ctrl-C. Функция checkbreak, описанная в параграфе 1.2.4, несколько возмещает этот недостаток и обращение к ней можно вставлять в некоторые наиболее часто используемые графические функции. Поскольку такое обращение сравнительно безобидное, место его размещения не имеет особого значения. Для этой цели можно использовать функцию drawjine. Позднее список графических функций будет расширен и обращение к функции checkbreak можно поместить в некоторых других местах. Конечно, можно было бы использовать и функцию dot, но эта функция вызывает- ся очень часто, и здесь лучше избегать сравнительно больших за- трат времени на проверки. Но есть и более серьезная проблема. Предположим, что в ре- зультате обращения к функции checkbreak, машина обнаружит попытку вызвать консольное прерывание. Если это произойдет в графическом режиме, то без специальных мер система может "повиснуть", создавая впечатление о появлении какой-то неис- правности. И только после выключения питания и повторного включения машины возможна снова правильная работа. Ситуа- ция объясняется просто. Когда программа прерывается по кон- сольному прерыванию, принимаемый по умолчанию обработчик прерывания просто завершит выполнение программы. Но при этом не происходит возврата в текстовый режим, что является существенным обстоятельством, если система находится в гра-
2.9. ИСПОЛЬЗОВАНИЕ КЛАВИШИ BREAK 55 фическом режиме. По этой причине следует предусмотреть спе- циальную функцию, аналогичную функции ту Junction из па- раграфа 1.2.4. Дадим ей название brfun и определим следующим образом: ' int brfun() { to_text(); exit(); } В начале функции initgr вставим строку onbreak (brfun); После этого оператора консольное прерывание будет приво- дить к обращению к функции brfun, так что система сначала пе- рейдет в текстовый режим, а затем остановит выполнение про- граммы. Нормальное обращение к функции tojext производится не из функции brfun у а из других функций, например, из функции endgr. После такого нормального обращения к функции tojext программа может не завершиться немедленно, а будет продол- жать выполнение некоторых операций, которые также жела- тельно остановить через консольное прерывание. Поскольку теперь система находится уже в текстовом режиме, необходимо активировать принимаемый по умолчанию обработчик прерыва- ний, а не функцию brfun. Следовательно, при возврате в тексто- вый режим необходимо восстановить обработчик прерываний по умолчанию. Для этого в функцию tojext нужно включить оператор: onbreak (0); В результате обсуждения, проведенного в этом разделе, фун- кции initgr, tojext, drawjine в модуле LINDRAW.C (см. ниже) будут слегка отличаться от их версий, описанных в предыдущих разделах. Кроме обращения к функции checkbreak, в функцию drawjine включим еще одну новую операцию. Эта функция дол- жна вызываться только в графическом режиме, поэтому в ней целесообразно осуществлять проверку текущего режима. При разработке прикладной программы иногда можно забыть вклю- чить в нее обращение к функции инициализации графического режима initgr и для предотвращения нежелательных последст- вий рекомендуется включить выдачу доброжелательного сооб-
56 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ щения о такой ошибке. Напомним, что для контроля режима бы- ла введена переменная injtextmode, следовательно в программу drawjtine можно включить строку: if (in_textmode) error /* "Not in graphics mode (call initgr)" */ ("He в графическом режиме (нужен вызов initgr)"); 2.10.ПАКЕТ ДЛЯ ВЫЧЕРЧИВАНИЯ ЛИНИЙ В этом параграфе объединим все функции этой главы в один модуль, который после компиляции может быть загружен вместе с основной программой, использующей некоторые из этих функ- ций. /* LINDRAW.C: */ /* Пакет подпрограмм для вычерчивания отрезков прямых V /* линий. См. также книгу Аммерал Л. "Принципы */ /* программирования в машинной графике", Москва, 1991. */ /* A package of routines for line drawing, see also */ /* PROGRAMMING PRINCIPLES IN COMPUTER GRAPHICS, */ /* by L Ammeraal */ #include "dos.h" union REGS regs; unsigned Int STACK- 15000; int ln_textmode-1, colorgr, X max, Y max; static int d, c2, c3, old_vld_state, X1, Y1; float x_max-10.0, y_max-7.0, horfact, vertfact; static char gtable[12]-{53,45,46, 7,91,2,87,87,2, 3, 0, 0}, ttable[12]-{97, 80, 82, 15, 25, 6, 25, 25, 2, 13, 11, 12}, zeros[128]; /* implicitly initialized to zero */ /* неявно инициализируется нулями */ int IX(x) float x; { return (intXx*horfact-K).5); } int IY(y) float y; { return Y max-(intXy*vertfact-K).5); } initgr() /* Initialize graphics */ /* Инициализация графического пакета */ { intbrfun(); if (!in_textmode) error /* Initgr is called In graphics mode */ ("Обращение к функции initgr в графическом режиме"); colorgr -IscolorQ; if (colorgr < 0) error /* Wrong display adapter */ ("Неверный дисплейный адаптер" ); onbreak(brfun); /* Set break trap */ /* Установка программы обработки прерывания */
2.10. ПАКЕТ ДЛЯ ВЫЧЕРЧИВАНИЯ ЛИНИЙ 57 if (colorgr) { lnitcolgr(); /* Enter graphics mode (color graphics) */ /* Переход в графический режим (цветная графика)*/ Х_тах - 639; Y_max - 199; d-1;c2-80;c3-1; } else { inltmongr(); /* Enter graphics mode (monochrome graphics */ /* Переход в графический режим (монохромная графика) */ Х_тах - 719; Y__max - 347; d-3;c2-90;c3-2; } Intextmode^); horfact - X тах/хтах; vertfact - Y тах/утах; } initcolgr() /* Switch to graphics mode (color graphics) */ /* Переключение в графический режим (цветная графика) */ { regs.h.ah - 15; /* Inquire current video state V /* Запрос текущего состояния видеомонитора */ int86(0x10, &regs, &regs); oldvldstate - regs.h.al; regs.h.ah - 0; /* Set graphics mode */ /* Включение графического режима */ regs.h.al - 6; /* 640 x 200, black/white */ /* черно/белый, 640 x 200 */ Int86(0x10, &regs, &regs); } initmongr() /* Switch to graphics mode (monochrome graphics) */ /* Переключение в графический режим (монохромная графика)*/ { inti.J; /* See Section 2.6 */ /* См. параграф 2.6 */ /* outp(0x3BF, 3); Step 1, already dealt with in iscolor */ /* Шаг 1 уже был выполнен в функции 'iscolor' */ outp(0x3B8,0x82); /* Step 2 */ /* Шаг 2 */ for(i-0;i<12;i++) { outp(0x3B4, i); outp(0x3B5, gtablepD; /* Step 3 */ /* Шаг 3 */ } for (j-0; j<256; J+t) poke(0xB800, j « 7, zeros, 128); Л Step 4 V /*Шаг4 */ outp(0x3B8,0x8A); /* Step 5 */ /* Шаг 5 */ } endgr() /* Walt until any key is hit and revert to text mode */ /* Ожидание нажатия любой клавиши возврат в текстовый режим */ { getch(); to_text(); }
58 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ to_text() /* Revert to text mode */ /* Возврат в текстовый режим*/ { if(intextmode) error /* "endgr or totext is called in textmode" */ ("функции endgr или totext вызваны в текстовом режиме"); if (colorgr) endcolgr(); else endmongK); intexjmode - 1; onbreak(O); /* Restore default break interrupt handler */ /* Восстановление обработки прерывании по умолчанию */ } endcoigr() /* Revert to text mode (color graphics): */ /* Возврат в текстовый режим (цветная графика): */ { regs.h.ah - 0; regs.h.al - old_vld_state; Int86(0x10, &regs, &regs); } endmongr() /* Revert to text mode (monochrome graphics): */ /* Возврат в текстовый режим (монохромная графика):*/ { int i, j; /* See Section 2.7 — См. параграф 2.7 */ outp(0x3B8,0); /* Step 1 */ /* Шаг 1 */ for(i-0;i<12;i++) { outp(0x3B4, i); /* Step 2 */ /* Шаг 2 */ outp(0x3B5, ttable[i]); } /* Step 3 V ЛШагЗ */ for (j-O; j<256: j++) poke1(0xB000. J « 4, "\40\7\40\7\40\7\40\7\40\7\40\7\40\7\40\7". 16); outp(0x3B8, 0x08); /* Step 4 */ /* Шаг 4 */ } error(str) char *str; /* Display a message and terminate program execution */ /* Вывод сообщения и завершение выполнения программы */ { if (!in_textmode) to_text(); printf("%s\n", str); exit(1); } move(x, y) double x, y; /* Move the current point to (x, y); */ /* x and у are screen coordinates */ /* Перенос текущей точки в (х, у); */ /* х и у в экранных координатах */ { Х1 - IX(x); Y1 - IY(y); check(X1, Y1); } draw(x, у) double x, у; /* Draw a line segment from the current point to (x, y) */ /* Вычерчивание отрезка прямой линии из текущей точки в точку (х, у)*/ { intX2. Y2; Х2 - IX(x); Y2 - IY(y); check(X2. Y2); drawJine(X1,Y1,X2, Y2); X1-X2;Y1-Y2; }
2.10. ПАКЕТ ДЛЯ ВЫЧЕРЧИВАНИЯ ЛИНИЙ 59 draw_llne(X1, Y1, Х2, Y2) int Х1, Y1, Х2, Y2; /* Draw the line segment from (X1. Y1) to (X2, Y2); */ /* X1, Y1, X2, Y2 are pixel coordinates */ /* Вычерчивание отрезка прямой из (Х1, Y1) в (Х2, Y2); */ /* Х1, Y1, Х2, Y2 — в пикселных координатах V { Int X, Y, Т, Е, dX, dY, denom, Xlnc - 1, Ylnc - 1, vertlonger - 0, aux; checkbreak(); /* To make DOS check for console break */ /* Установка в ДОС консольного прерывания */ If (ln_textmode) error /* "Not In graphics mode (call inltgr)" */ ("He в графическом режиме (нужен вызов Inltgr)"); dX-X2 X1;dY-Y2 Y1; if(dX<0){Xlnc-1;dX-dX;} lf(dY<0){Ylnc-1;dY-dY;} If (d Y > dX) { vertlonger - 1; aux - dX; dX - dY; dY - aux; } denom-dX« 1; T-dY«1; E-dX;X-X1; Y-Y1; while (dX—>-0) { dot(X,Y); lf((E-H-T)>0) { If (vertlonger) X -и- Xlnc; else Y -+- Ylnc; E — denom; } If (vertlonger) Y 4- Ylnc; else X -н- Xlnc; } } checkbreak() { char ch; if (kbhlt()) { ch - getch(); kbhlt(); ungetch(ch); } } dot(X, Y) Int X, Y; /* Light the pixel with coordinates X, Y */ /* Подсветка пиксела с координатами X, Y */ { Int offset; charch; offset- 0x2000*(Y&d) + c2*(Y»c3) + (X»3); peek(0xB800, offset, &ch, 1); ^ ch l-0x80»(X&7); poke(0xB800, offset, &ch, 1); int iscoloK) /* Find out which adapter is used */ /* Определение типа используемого адаптера */ { charchO, ch1, x; int86(0x11, &regs, &regs);
60 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ if ((regs.x.ax & 0x30)!- 0x30) return 1; /* Color graphics */ /* Цветной графический адаптер */ outp(0x3BF, 3); /* Configuration switch, see Section 2.6 */ /* Переключатель конфигурации, см. параграф 2.6 */ peek(0xB8002, 0. &ch0, 1); /* Try to read chO from screen memory */ /* Попытка прочитать chO из экранной памяти */ ch1 - chO Л OxFF; /* Find some value different from chO */ /* Определение некоторого числа, отличного от chO*/ poke(0xB800, 0, &ch1, 1); /* Try to write this Into screen memory */ /* Попытка записать это число в экранную память */ реек(0хВ800, 0, &х, 1); /* Try to read the latter value */ /* Попытка прочитать это последнее значение */ poke(0xB800, 0, &ch0, 1);/* Restore the old value chO */ /* Восстановление старого значения chO */ return (x - - ch1 ? 0 : 1); /* Has written value been read? */ /* Было ли прочитано записанное число? */ } fatal() /* Draw a diagonal, then wait until a key is pressed, */ /* and finally revert to text mode */ /* Вычерчивание диагонали на весь экран, затем ожидание */ /* нажатия на любую клавишу для окончательного перехода */ /* в текстовый режим. */ { draw_line(0, Y max, X max, 0); endgr(); } check(X, Y) int X, Y; /* If point (X, Y) lies outside the screen boundaries, */ /* then call fatal, print the wrong coordinates, and stop */ /* Если точка (X, Y) лежит вне пределов экрана, то вызов */ /* функции fatal, отображение неверных координат и останов */ { if (X < 0 II Х>Х__тах II Y<0 II Y>Y__max) { fatal(); printf /* "Point outside screen (X and Y are pixel coordinates" */ ("Точка вне экрана (X и Y в пикселных координатах):\п"); prlnttfX - %d Y - %d\n". X, Y); printf("x - % 10.3f у - % 10.3f\n", X/horfact, (Y_maxY)/vertfact); exit(1); } } intbrfun() /* Graphics interrupt handler, dealing with console break */ /* Обработка консольного прерывания в графическом */ /* режиме */ { to_text(); exit(1); /* Before exit, return to text mode! */ /* Перед выходом возврат в текстовый режим ! */ }
2. U. ПРИМЕР 61 2.11.ПРИМЕР В предыдущей книге автора "Принципы программирования в машинной графике" все программы с выводом графической ин- формации использовали четыре графические подпрограммы initgr, move, draw, endgr, включенные в модуль LINDRAW.C, по- этому все эти программы могут служить примером применения этого модуля. Приведем здесь еще один пример программы, в ко- тором, кроме упомянутых известных функций, определяются внешние переменные xjnax и yjnax. Кроме того, выполнение программы может занять довольно много времени, поэтому при желании можно использовать средства прерывания. Начнем с центра экрана и будем вычерчивать квадраты, диагонали кото- рого горизонтальны и вертикальны. В качестве меры длины бу- дем задавать половину их размера и для первого квадрата уста- новим эту меру равную одному дюйму. Затем будут вычерчены четыре меньших квадрата в направлениях на север, юг, восток и запад по отношению к исходному квадрату. Их размер определя- ется путем умножения размера исходного квадрата на заданный "коэффициент уменьшения". Расстояния между центрами этих квадратов и центром исходного квадрата находятся путем умно- жения исходного размера на заданный "коэффициент расстоя- ния". Этот процесс повторяется рекурсивно таким образом, что теперь четыре последних квадрата выступают в качестве исход- ного и так далее. Квадрат вычерчивается и используется для ге- нерации новых квадратов только в том случае, если его размер больше заданного "минимума" и, в то же время, он лежит в пре- делах границ экрана. /* MANYSQ.C: Эта программа вычерчивает много квадратов */ /* This program draws a great many squares */ extern float x_max, ymax; float sizereduction, distance_factor, limit; main() { printf /* Enter 'size reduction', 'distance factor' and 'limit' */ ("Введите: 'коэфф. уменьшения', 'расстояние' и 'предел'An"); printf /* (for example: 0.5 2.5 0.05) */ ("Например: 0.5 2.5 0.05 \n"); scanf("%f %f %f", &size_reduction, &distance_factor, &limit); initgrO; move(0.0, 0.0); draw(x_max, 0.0); draw(x_max, y_max); draw(0.0, y_max); draw(0.0, 0.0);
62 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ polnt(0.5 * x_max, 0.5 * y_max, 1.0); endgr(); } point(x, у, size) float x, y, size; { float size 1,d; if (size > limit && x + size < x_max && x-size>0&& у + size < y_max && у - size > 0) { move(x + size, y); draw(x, у + size); draw(x - size, y); draw(x, у - size); draw(x + size, y); slzel - slze_reductlon * size; d - distancefactor * size; polnt(x + d, y, sizel); polnt(x, y + d, sizel); point(x-d, y, sizel); polnt(x, y-d, sizel); } } При вводе значений 0.5, 2.5, 0.05 результат работы этой про- граммы будет выглядеть примерно так, как показано на рис. 2.3. На экране квадраты выглядят действительно как квадраты, но при выводе на принтер вертикальные диагонали оказались длин- нее горизонтальных. Эту проблему обсудим в параграфе 4.4. Рис. 2.3. Результат работы программы MANYSQ.C
Глава 3 РЕДАКТИРОВАНИЕ НА ЭКРАНЕ 3.1. БИТОВЫЕ ОПЕРАЦИИ В ЭКРАННОЙ ПАМЯТИ До сих пор наши картинки были статическими — если что-то было начерчено, то все оставалось на экране. Если ограничиться выводом изображений только на графопостроитель, получить можно только статические изображения. Но при наличии видео- дисплея любой пиксел, подсвеченный на экране, можно снова сделать темным или, в терминах экранной памяти, можно сбро- сить в нуль состояние любого бита, ранее установленного в еди- ницу. Один из способов использования этого принципа заклю- чается в стирании изображения со всего экрана и вычерчивании нового изображения. Этот способ особенно хорошо подходит для получения эффекта движения, подобного мультипликации. Но для этого требуется быстродействующий процессор в комбина- ции с несколькими графическими страницами, что имеет место, например, в случае монохромного графического адаптера. Каж- дую новую картинку можно создавать на неактивной странице и отображать ее только после полного завершения. В тот момент, когда неактивная страница становится активной, вся новая кар- тинка мгновенно появляется на экране. Если процессор работает достаточно быстро, то можно формировать в реальном времени перемещающиеся картинки и тем самым создавать мультипли- кационные фильмы. Заполнение нулями графической страницы очищает весь экран, что реализуется в следующей функции: clearpage() { Inti.n; n-(colorgr? 128:256); for (l-O; i<n; H+) poke(0xB800,1« 7, zeros, 128); }
64 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ Напомним, что массив нулей zeros уже был использован в мо- дуле LINDRAW.C в параграфе 2.10. Он состоит из 128 нулевых байтов, которые записываются либо в 128 * 128 = 16К байт либо в 256 * 128 = 32К байт экранной памяти. Другой подход заключается в удалении только некоторой части картинки, например, отрезка прямой линии, и замены ее другим изображением. В связи с этим имеется интересная и по- лезная идея, заключающаяся в "переключении" или "инверти- ровании" нужных бит в памяти дисплея. При такой операции значение бита станет равным 0, если перед этим в нем была запи- сана 1, или 1, если был 0. Как и ранее, будем использовать X и Ув качестве пикселных координат, то есть: 0 < X < Х_тах 0 < У < У_тах где X max = 719, У max = 347 для монохромного графического адаптера и X max = 639, У max - 199 для цветного графичес- кого адаптера. Введем программную переменную drawmode ("режим черчения"), от которой зависит результат выполнения обращения к функции dot (X, Y) следующим образом. Пиксел с координатами X, Убудет: подсвечен, если drawmodee +1 (положительная запись), переключен, если drawmode = 0 (переключение), затемнен, если drawmode = -1 (отрицательная запись). При значении drawmode = 1 новая функция dot будет давать такой же эффект, как и функция, описанная в главе 2. Но если значение этой переменной будет 0 или -1, то эффект будет со- всем другой. Если значение нулевое, то темный пиксел будет подсвечен, а подсвеченный — погашен. Заметим, что в этом слу- чае многократное обращение к функции dot(X, У) для одной и той же точки (X, У) приводит к переменному состоянию этой точки. Например, если точка первоначально была темной, то ее состояние станет светлым, темным, светлым, темным и так далее. Наконец, при drawmode = -1, обращение к функции dot(Xy У) просто сделает этот пиксел темным, независимо от его предыдущего состояния.
3.7. БИТОВЫЕ ОПЕРАЦИИ В ЭКРАННОЙ ПАМЯТИ 65 На языке Си изменение определенного бита в байте в каждом случае выполняется непосредственно и эффективно. Читатель должен быть знаком с операторами I Побитовая операция ИЛИ & Побитовая операция И л Побитовая операция исключающее ИЛИ и с соответствующими операторами присваивания 1=, &=, ~=. (Если нет, то следует обратиться к какому-либо учебнику, на- пример, "Язык Си для Программистов"). Пусть, например, в байте ch k-й бит слева должен быть изменен (0 < к < 7). Тогда, если переменная drawmode имеет одно из значений +1, 0, -1, можно записать: pattern - 0x80 » к; if (drawmode - - 1) ch I- pattern; else If (drawmode - - -1) ch &- ^pattern; else ch ~- pattern где pattern — целочисленная переменная. Предположим, что ch = 1111 1001 (в двоичном формате), к - 2, drawmode - 0 Тогда, начиная счет слева с нуля, второй бит в байте ch дол- жен быть сброшен (поскольку сейчас он установлен!), а все дру- гие биты должны остаться неизменными. Сдвигом числа 0x80 (или, в двоичном формате, 0.. .0 1000 0000) на к позиций вправо получим pattern = 0...010 0000 Поскольку запись ch ^= pattern означает ей = ch л pattern, где знаком л обозначена операция исключающее ИЛИ, будем иметь: Старое значение ch: 1111 1001 "Расширенное" до типа "целый": 0...0 1111 1001 Значение переменной pattern: 0...0 0010 0000 Результат операции "исключающее ИЛИ": 0...0 1101 1001 Новое значение ch (типа сАаг): 1101 1001 Отсюда видно, что значение к-того бита слева инвертирова- но, а остальные остались без изменения. В главе 2 при обсуждении функции dot был использован опе- ратор 1= в ситуации, совершенно аналогичной случаю при drawmode = 1 .Теперь эту функцию можно записать по-иному: 3—275
66 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ intdrawmode-1; static int offset; static char lastchar; dot(X. Y) int X, Y; { Int pattern; offset- 0x2OOO*(Y&d) * c2*(Y»c3) + (X»3); peek(0xB800, offset, &lastchar, 1); pattern - 0x80 »(X&7); If (drawmode - - 1) lastchar I- pattern; else If (drawmode - - -1) lastchar &- ("pattern); else lastcharуч- pattern; poke(0xB800, offset, &lastchar, 1); } Переменные offset и lastchar объявлены как внешние (стати- ческие) , причина для этого будет пояснена в параграфе 3.4. Также желательно что-то добавить к функции drawjiine. Непосредственно перед последней фигурной скобкой } в этой функции вставим условный оператор If (drawmode - - 0) dot(X1, Y1); Очень часто вычерчивание фигуры задается последователь- ностью операторов в виде одного обращения к функции move, за которым следуют несколько обращений к функции draw. Напри- мер, треугольник ABC вычерчивается следующей последова- тельностью операторов: move(xA, yA); draw(xB, уВ); draw(xC, yC); draw(xA, yA); Как мы уже знаем, три обращения к функции draw приведут к большому числу вызовов функций dot, каждый из которых со- ответствует одной точке в треугольнике. Однако в режиме "пе- реключения" было бы неверным, если для некоторой точки обра- щение к функции dot происходило точно дважды, так как в этом случае при втором обращении точка, сформированная при пер- вом обращении, была бы удалена. Но это как раз тот случай, ко- торый имеет место, например, для точки В. Она фигурирует дважды: сначала как конечная точка отрезка АВ, а затем как на- чальная точка отрезка ВС. Для предотвращения исчезновения этой точки и нужно использовать третье обращение к функции dot Поскольку последовательные вызовы функции dot для одной и той же точки попеременно подсвечивают и гасят эту точку, то точка будет подсвечена после нечетного числа вызовов (при
3. У. БИТОВЫЕ ОПЕРАЦИИ В ЭКРАННОЙ ПАМЯТИ ^ 67 условии, что до этого она была темной). Теперь должна быть по- нятна причина дополнительного вызова функции dot при выпол- нении условия в предыдущем примере программы. Заметим, что часть оператора :* if (drawmode - - 0) не является обязательно необходимой, поскольку ни при "поло- жительном", ни при "отрицательном" режимах черчения лиш- ний вызов функции dotiXly Yl) не принесет вреда. Проверка включена исключительно с целью повышения эффективности. Это лишь первый момент, потребовавший специального анализа в связи с вычерчиванием последовательности отрезков. Если, как в примере треугольника ЛВС, вычерчивается отрезок пря- мой линии АВ, то дополнительный вызов функции dot для точки А кажется нежелательным, поскольку эта точка не принадлежит отрезку, вычерченному перед этим. Но она будет принадлежать стороне СА, поэтому появится и третье обращение для этой точ- ки и треугольник будет неверным, если фигура не замкнута, то есть при отличии начальной и конечной точек. Простейшим при- мером такой фигуры будет простой отрезок прямой линии PQ. Для точного вычерчивания отрезка в режиме "переключения" необходимо предусмотреть дополнительный вызов для началь- ной точки и записать move(xP, yP); dot(xP, yP); draw(xQ. yQ); Это не очень красиво, но нельзя забывать, что дополнитель- ный вызов функции dot здесь требуется только в случаях: а) использования режима "переключения", б) когда рассматриваемая точка является свободной концевой точкой точкой, то есть она не принадлежит каким-либо дру- гим отрезкам прямых в этой фигуре, в) когда ухудшается точность. Во многих случаях будет совсем незаметно, если отсутствует единственный пиксел на конце свободного отрезка, то есть имеем случай в). Для замкнутых фигур (таких, как треугольник), мож- но пропустить пиксел в точке, которая является общей для двух соседних отрезков, так что это более важный случай, но он не требует каких-либо дополнительных вызовов функции dot. 3**
68 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ 3.2. ВРАЩАЮЩАЯСЯ ЗВЕЗДА Наступило время посмотреть на реальную работу режима "переключения" при вычерчивании линий. Сформируем враща- ющуюся картинку, которая состоит из нескольких концентриче- ских звезд одинакового размера, повернутых относительно их центра. Звезда получается путем выбора пяти точек на окружно- сти. Если эта окружность имеет радиус /?, а центр расположен в точке с координатами jcO, jO, то можно соединить точки (именно в указанном порядке) ОсО + Я cos 0°,>O + /?sin 0°) ОсО + Я cos 144°,;y0 + *sin 144°) ОсО + R cos 288°, >0 + R sin 288°) Oc0 + /*cos 72°,>0 + /?sin 72°) (xO + R cos 216°, jO + R sin 216°) 0c0 + /?cos 0\>0 + /?sin 0°) Такие же звезды использовались в книге автора "Принципы программирования в машинной графике", параграф 2.7 для де- монстрации применения рекурсии. Заметим, что указанные вы- ше значения углов кратны углу 144° по модулю 360° (например, 72 = 3 х 144 - 360). Но мы будем использовать не это точное зна- чение, а кратное углу 145°, вместо 144°. Кроме того, мы не оста- новимся на вычерчивании одной "уродливой" звезды, а будем просто продолжать работу, пока не будет нажата какая-нибудь клавиша. Наряду с вычерчиванием новых отрезков прямых ли- ний будем удалять старые в том же порядке, как они вычерчива- лись. Так что, за исключением начала, на экране будут видны некоторое фиксированное число звездоподобных фигур и они бу- дут смотреться как вращающиеся звезды. От пользователя будет запрошен ввод числа Л7ш, определяющего число видимых отрез- ков в движущейся фигуре. Каждый раз после вычерчивания отрезка прямой линии из точки ОсО + R cosd • 145°), >0 + R sinii-145°)), необходимо удалить старый отрезок прямой линии из точки ОсО + R cos{(i-Nlin) • 145°}, >0 +Я sin{(i-Nlin) • 145°}) при условии, что i не меньше Nlin. Напомним, что новый режим черчения ("переключения" при значении drawmode = 0) позво- ляет нам удалить отрезки очень простым способом, а именно —
3.2. ВРАЩАЮЩАЯСЯ ЗВЕЗДА 69 повторно задать их вычерчивание. Адаптируем модуль LINDRAW.C, как было описано в параграфе 3.1, а также добавим несколько новых функций из параграфа 3.4. Допустим, что есть возможность вызвать этот новый пакет, который назовем GRPACK.C. Следующая программа должна использоваться со- вместно с этим отредактированным модулем GRPACK.C. /* ROTSTAR.C: Rotating stars */ /* Вращающиеся звезды */ #lnclude "math.h" float angle 145, xO, yO, R; maln() { Intl.J, Nlln; extern int drawmode; extern float xmax, углах; prlntf /* "How many line segments in rotating stars? " */ ("Сколько отрезков будет во вращающихся звездах? "); scanf("%d'\ &Nlin); xO - 0.5 * х jnax; yO - 0.5 * yjnax; R - 0.9 * yO; angle145- 145.0 * atan(1.0)/45.0; /* Angle In radians, 145 * pi / 180 */ /* Угол в радианах */ drawmode-0; /* 'Alternating' */ /* Режим переключения */ lnltgr();i-0;J-i-Nlln; while (lkbhit()) { If ()>-0) llne(|); llne(i); KM-; If (I - - 72) I - 0; J++: If 0 — 72) J - 0; } endgr(); } llne(k) Int k; { float alpha, beta; alpha - k * angle145; beta - alpha + angle145; move(x0 + R * cos(alpha), yO + R * sln(alpha)); draw(x0 + R * cos(beta), yO + R * sin(beta)); } Функция kbhit, описанная в параграфе 1.2, используется в основной программе для завершения цикла while. Эта функция проверяет, не было ли нажатия на клавишу и, если было, возвра- щает значение 1, что приводит к завершению цикла. Символ на- жатой клавиши все еще доступен для чтения, так что функция
70 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ endgr не будет ожидать ввода символа, а сразу же вызовет пере- ход к текстовому режиму. Переменными / и у обозначены номера отрезков прямых ли- ний, которые должны быть вычерчены и удалены соответствен- но. Для любого значения 0, 1, ... переменной i при обращении к функции lined) вычерчивается отрезок прямой с номером г, а когда позднее переменная / достигает такого же значения, то этот же сегмент удаляется также обращением к функции lined)* Когда значения переменных / или j становятся равными 72, то это значение заменяется на 0. Это вполне допустимо, поскольку 72 х 145 = 10 440 = 29 * 360, то есть углы, вычисленные для зна- чений i: = 72 и i = 0, отличаются на число, кратное 360°. Это необ- ходимо сделать по двум причинам, относящимся к несовершенс- тву машинных вычислений вообще. Во-первых, желательно, чтобы программа правильно работала в течение неопределенно- го периода времени, поэтому значения переменных i и/не долж- ны превышать максимально допустимого значения целого числа для конкретной машины. Во-вторых, если вычисляются значе- ния косинусов cos (phi) и cos(phi + 2* к* pi) для очень больших целых чисел к (при pi = 3.14159...), резуль- тат, хотя теоретически и должен быть одинаковым, но он может слегка отличаться из-за конечной точности вычислений. Следо- вательно, без нормирования чисел i и /до диапазона 0, ..., 71, наша программа может пытаться удалить отрезок прямой линии, слегка отличающийся от того, что был вычерчен перед этим, и некоторые пикселы могут остаться подсвеченными. (Этот эф- фект был проверен на предыдущей версии программы. Хотя та- кой эффект и исчезал при замене типа float на double, предпоч- тительно использовать указанное нормирование). Интересно по- экспериментировать с выполнением программы для различных значений числа Nlin. Для Nlin = 1 на экране будет виден только один отрезок, поскольку в символической нотации это приведет к следующей последовательности вызовов: lined = 0); lineij = 0); lined = 1); lined~ 1); lined = 2); lined- 2); lined = 3);
3.2. ВРАЩАЮЩАЯСЯ ЗВЕЗДА 71 В общем случае будем иметь: lined = 0); lined - 1); lined = Nlin- 1); /me(/я 0); /mi(i ■ Nlin); /me(/ * 1); lined = Л7ш +1); то есть отрезок 0 удаляется непосредственно перед вычерчивани- ем отрезка Nlin. Этим объясняется, что при Nlin = 5 видна каж- дый раз точно одна звезда, а при Nlin e 5п видно одновременно п звезд до тех пор, пока значение Nlin не превысит числа 75, по- скольку можно видеть не более 15 звезд. Для больших значений Nlin вызов функции lined) не всегда приведет к вычерчиванию нового отрезка прямой, а иногда также будет удалять старый. Прекрасная вращающаяся картинка получается, например, при задании Nlin = 100. Изображение на рис. 3.1 дает только очень Рис. 3.1. Пример вывода программы ROTSTAR.C
72 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ несовершенное представление о такой картинке. Во-первых при выводе на печать перерывы в линиях более заметны, чем при наблюдении их на экране, где они движутся, и, во-вторых, на твердой копии можно заметить несоответствие размеров по гори- зонтали и по вертикали. Последняя проблема может быть реше- на путем незначительного изменения программы, как будет по- казано в параграфе 4.4. Для еще больших значений Nlin, напри- мер, 300, фигура будет попеременно расти и сжиматься, пока число вычерченных отрезков прямых не станет равным значе- нию Nlin. С этого момента количество видимых отрезков станет постоянным. Более детальный анализ этой программы выходит за рамки данной книги. Необходимо еще раз обратить внимание на точки пересече- ний. Если используется режим "переключения", то в случае чет- ного количества линий, проходящих через точку, эта точка в ре- зультате не будет видна. В большинстве случаев точка пересече- ния располагается только на двух линиях, поэтому чаще всего она будет невидимой. Пропуск точек пересечения, без сомнения, является недостатком режима переключения, но он вполне ком- пенсируется восстановлением точки пересечения после удале- ния всех пересекающихся линий, кроме одной. В программе ROTSTAR.C это явление видно очень четко. 3.3.. ДВИЖУЩАЯСЯ КРИВАЯ Обсудим кратко другую программу, в которой показано, как можно удалять отрезки прямых линий. Графические результаты можно рассматривать как современное искусство, если читатель его не воспринимает, то он, как и автор, обладает старомодным художественным вкусом. Автор надеется, что несмотря на это читатель не будет возражать против анализа бесполезной про- граммы, если в ней демонстрируется несколько полезных при- емов рационального программирования. Будем вычерчивать го- ризонтальные и вертикальные отрезки прямых определенной длины, причем размер шага основывается на теоретическом раз- мере шага, который должен быть определен пользователем. (С помощью генератора случайных чисел этот теоретический шаг будет несколько модифицироваться, чтобы избежать появления частично* совпадающих отрезков.) Аналогично предыдущей про- грамме будем добавлять сегмент с одной стороны "кривой" и уда-
3.3. ДВИЖУЩАЯСЯ КРИВАЯ 73 лять последней отрезок с другой стороны. Количество видимых отрезков, каждый из которых получен на одном шаге, также дол- жен быть введен извне. На каждом шаге делается выбор из трех возможностей: 1) поворот на 90° влево; 2) поворот на 90° вправо; 3) продолжение движения вперед. Для принятия решения о выборе будем использовать генера- тор случайных чисел. Но пользователь должен задать относи- тельную частоту (выраженную в процентах) выбора поворота направо или налево. Например, если ввести число 80 в качестве "процента поворота шагов", то вероятность случаев выбора (1), (2), (3), именно в этом порядке, будет 40, 40 и 20 процентов. Для пояснений использования случайных чисел в графике автор рекомендует обратиться к его предыдущей книге по графике "Принципы программирования в машинной графике'1. В про- грамме MOVCUR.C (см. ниже) будем использовать связанный список для запоминания координат видимых точек, поэтому, в противоположность нашей предыдущей программе, эти точки не нужно будет реконструировать путем вычислений. Рискуя пока- заться надоедливым, автор рекомендует обратиться к его книге "Язык Си для программистов" для получения более подробных сведений о структурах данных, динамическом распределении памяти и связанных списках. /* MOVCUR.C: /* #lnclude "time.h" int turning; float step; extern int drawmode; main() { int I, n, size; charch, *malloc(); float xmin-O., xmax-10., ymin-0., ymax-7., x0, yO, x, y; struct node { float X, Y; struct node *NEXT; } Mront, *rear, *p; size-sizeof(struct node); printf /* "Step size (in inches)?" */ ("Укажите размер шага (в дюймах):"); scanf("%f". &step); printf /* "How many line segments in visible curve?" */ ("Сколько отрезков прямой в видимой кривой? "); scanf("%d", &n); A moving curve */ Движущаяся кривая */
74 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ П-Н-; printf /* "Percentage of turning steps*?" V ("Процент шагов с поворотом? "); scanf("%d", &turning); printf /* "Press 1 to start or resume drawing, 2 to stop, 3 to terminate program executionAn" */ ("Нажмите одну из клавиш:\п\ 1 — для пуска или возобновления черчения,\п\ 2 — для остановаДгЛ 3 — для завершения выполнения программы."); do {ch-getch(); if (ch- -'2' 11 ch- -3') exlt(0);} while (ch К1'); xf><xmin+xmax)/4; yO-(ymin+ymax)/2; p front-rear-(struct node *)malloc(slze); f ront->X - xO; front->Y - yO; initgr(); drawmode-0; Ю; while (1) { if(kbhit()) { ch-getch(); if (ch- -'2') { while (!kbhit()); ch-getch(); } if (ch--3') break; } do { gen(xO, yO, &x, &y); } while (x<xmln 11 x>xmax 11 y<ymln 11 y>ymax); move(xO, yO); draw(x, y); if(-H-i>-n) { p-rear; rear- p->NEXT; i-n; move(p->X, p->Y); draw(rear->X, rear->Y); free(p); p-front; front-{struct node *)malloc(size); p->NEXT-front; front->X - x; front->Y - y; xO-x; yO-y; } endgr(); } gen(xO, yO, px, py) float xO, yO, *px, *py; { Int r; static lntfirst-1,dir-0; long int seed; float step 1; if (first) { flrst-O; time(&seed); srand((lnt)seed); } r-rand()%100; If (r < turning/2) dlr-(++dlf)%4; else if (f < turning) dir-(dir4-3)%4; step 1-step*(1 .-Kr-50.)/100,); /* a distortion to avoid accidentally coinciding lines */ /* рассеяние для случайно совпадающих линий */ switch (dir) { caseO: *px-xO^tep1;*py-y0; break; case 1: *px-x0; *py-y0+step1; break;
3.3. ДВИЖУЩАЯСЯ КРИВАЯ 75 } case 2: *px-xO-step1;*pyyO; break; case 3: *px-xO; *py-yOstep-1; break; } На рис. 3.2. показан пример результата работы программы MOVCUR.C. Здесь были заданы следующие входные данные: Размер шага (в дюймах): 1.0 Количество отрезков в видимой кривой: 100 Процент шагов с поворотом: 50 1 Г- • Рис. 3.2. Пример вывода программы MOVCUR.C Отметим, что исходное число seed для генератора случайных чисел получается с помощью функции time. Поскольку это число при каждом выполнении программы будет другим, то графичес- кий результат также каждый раз будет различным. Графический результат работы программы MOVCUR демон- стрирует, что условный оператор If (drawmode - - 0) dot(X1, Y1); добавленный в функцию drawjine, действительно является полезным. Иногда возникает ситуация, что два соседних отрезка имеют одно и то же направление и, особенно в данном случае, очень важно, чтобы точки соединения таких отрезков были под- свечены, чтобы не получались линии с небольшими промежут-
76 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ ками. Без такого условного оператора это могло происходить по- тому, что функция dot аля точек соединения вызывалась бы точ- но два раза. 3.4. БЫСТРАЯ ПРОГРАММА ДЛЯ ЗАПОЛНЕНИЯ ОБЛАСТИ Существует разница между художниками-графиками, кото- рые рисуют картины, пользуясь только пером, и теми, кто рисует кистью. До сих пор мы вычерчивали отрезки прямых линий; на самом деле подобно художникам, мы можем закрашивать значи- тельные области. Если в распоряжении имеется цветной графи- ческий адаптер и цветной монитор, то можно получать области с закраской разными цветами, но в нашем случае ограничимся рассмотрением только монохромной графики, тогда будет анали- зироваться только подсветка пикселов. Причем анализ выполня- ется для всех темных пикселов внутри некоторой замкнутой гра- ницы, образуемой подсвеченными пикселами. Определенная та- ким образом область часто является полигоном, внутри она может содержать и другие полигоны — внутренние части — ко- торые тогда не закрашиваются. На рис. 3.3 показан пример такой области до и после закраски (темные и подсвеченные пикселы напечатаны в белом и черном цвете соответственно). Составим новую функцию fill с двумя аргументами с плаваю- щей точкой, представляющими координаты х и у внутренней точки в области, подлежащей заполнению. Начиная с этой точ- ки, программа ищет темные соседние пикселы во всех направле- ниях, подсвечивает их и начинает поиск с каждой из них во всех направлениях и так далее, пока не будут исчерпаны все темные пикселы. В первую очередь для этой цели понадобится новая функция pixlit ("подсветка пиксела"), запрашивающая состоя- ние подсветки для пиксела с координатами X, Y. int pixlit(X, Y) int X, Y; { int pattern; offset- 0x2000*(Y&d) + c2*(Y»c3) + (X»3); /* d, c2 and c3 have been defined in Inltgr */ /* d, c2 и с3 были определены в 'initgr' */ pattern - 0*80 »(X&7); peek(0xB800, offset, &lastchar, 1); return ((lastchar & pattern) !-0); }
3.4. БЫСТРАЯ ПРОГРАММА ДЛЯ ЗАПОЛНЕНИЯ ОБЛАСТИ 77 Рис. 3.3. Заполнение области Эта функция возвращает значение соответствующего бита в экранной памяти: 1, если пиксел подсвечен, и 0, если он темный. Обратите внимание на схожесть этой функции и функции dot. В функции pixlit выполняется побитовая операция И над перемен- ной pattern и выбранным битом, которым мы интересуемся. Если этот бит установлен, то результат будет ненулевым, в противном случае он будет равен нулю. Поскольку нам нужно, чтобы воз- вращаемое значение было равно либо 0, либо 1, то можно этот результат просто сравнить с 0, используя операцию "не равно" (!=). Всегда, когда важна скорость, предпочтительно вместЧ) вещественных переменных (с плавающей точкой) применять целочисленные переменные, то есть координаты пиксела X и У. Будем также использовать рекурсивную функцию pixfill, аргу- ментами которой, в отличие от функции fill, также будут коор- динаты пиксела. Для последней функции можно просто записать fill(x. у) float х, у; { pixfill(IX(x). IY(y)); } Напомним, что функции преобразований IX и /Убыли введе- ны в параграфе 2.1.
78 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ Проблема составления функции pixfill теоретически могла бы быть решена следующим образом: pixfill(X, Y) int X, Y; /* Не для практического использования ! '*/ /* Not to be used in practice ! */ { if(pixlit(X, Y)--0) { dot(X,Y); pixflll(X - 1. Y); pixfllJ(X, Y - 1); pixflll(X+1,Y); pixfill(X, Y+1); } } С практической точки зрения эта версия имеет ряд недостат- ков: 1. Может понадобиться очень большой стек; 2. Будет работать очень долго. Первый недостаток очень опасен, поскольку переполнение стека в графическом режиме даже хуже, чем в текстовом режи- ме, как мы видели в параграфе 2.8. Напомним, что может ока- заться необходимой подсветка многих тысяч пикселов, поэтому глубина рекурсии может вырасти далеко за разумные пределы. Второй недостаток также не лучше. Медленная работа этой вер- сии функции pixfill возникает из-за побитовой проверки и уста- новки пикселов. Скорость можно увеличить примерно в восемь раз, рассматривая полный байт там, где это возможно. Другой очень сложный аспект заключается в том, что функция dot вы- полняет (также с серьезной затратой времени) выборку бита сра- зу же после того, как это же самое было сделано в функции pixlit. Наконец, можно сэкономить время, если бы можно было умень- шить глубину рекурсии и, по крайней мере частично, заменить рекурсию итерацией. Как уже было показано в параграфе 3.1, переменные offset и lastchar являются "статическими внешни- ми", что означает их доступность во всем модуле GRPACK.C. (Имя GRPACK.C было введено в параграфе 3.2.). Функции pixlit, fill, pixfill являются достаточно важными, чтобы включить их в этот модуль, поэтому в них можно применить эти перемен- ные. В функции pixfill попытаемся значительно уменьшить глу- бину рекурсии. Все последовательные пикселы в горизонтальных строках будут подсвечиваться итеративно, а для каждой строки ее соседние строки при необходимости будут анализироваться рекурсивно. Ниже функция pixfill представлена в том виде, как
3.4. БЫСТРАЯ ПРОГРАММА ДЛЯ ЗАПОЛНЕНИЯ ОБЛАСТИ 79 она будет включена в модуль GRPACK.C, ее работа поясняется ниже. pixfill(X, Y) int X, Y; /* Fill a closed region, starting In point (X, Y) */ /* Заполнение замкнутой области, начиная с точки (X, Y) */ { Int Xleft, Xrtght. YY. I, dm; charones4)xFF; dm-drawmode; drawmode-1; check(X, Y); /* Defined in GRPACK.C; also used in move and draw */ /* Определена в GRPACK.C; используется в move и draw */ checkbreak(); /* See Section 2.9*/ /* См. параграф 2.9 */ /* Light as many pixels as possible on line Y, */ /* and determine Xleft and Xrlght: */ /* Подсветка максимального количества пикселов */ /* в строке Y и определение значений Xleft и Xrlght: */ Xleft-Xright-X; while (plxlltfXIeft, Y) - - 0 && Xleft >- 0) { If (lastchar - - 0) { poke(0xB800. offset, tones, 1); Xleft &-0xFFF8; If (Xrlght--X) Xrlght I-7; } else dotfXleft, Y); Xleft- -; } Xright-н-; while (pixlit(Xright, Y) - - 0 && Xrlght <- X__max) { if (lastchar --0) { poke(0xB800, offset, tones, 1); Xrlght I-7; } else dot(Xright, Y); Xrlght-н-; } /* Recursive calls of pixfill for at most two remote points: */ /* Рекурсивные обращения к функции pixfill для двух */ /* наиболее удаленных точек: / */ X-(Xleft+Xrlght)»1; for (I—1;i<-1;l+-2) { YY-Y+i; while (pixllt(X, YY) - - 0) YY 4- i; YY-(Y+YY)»1; if (pixlit(X, YY)- -0) plxfill(X, YY); } /* Recursive calls for all dark pixels next to line Y */ /* (with X values between Xleft and Xrlght): */ /* Рекурсивные вызовы для всех темных пикселов рядом */ /* со строкой Y (при значениях X между Xleft и Xrlght): */ for(YY-Y-1; YY<-Y+1; YY-h-2) { X-Xleft+1;
80 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ while (X < Xrlght) { l-plxllt(X, YY); If (lastchar - - ones) X 1-7; else lf(l--0)plxflll(X,YY); X++; } } drawmode-dm; } Как ясно из комментариев, эта функция состоит из трех час- тей. В первом цикле while начинаем с заданной точки (X, Y) и двигаемся влево до тех пор, пока обнаруживаем нулевые биты. Каждый раз переменная Xleft принимает значение координаты X для пиксела, который будет рассматриваться следующим. Одна- ко вместо анализа только одного бита за один раз ускорим про- цесс следующим образом. Для каждого обнаруженного нулевого бита будем проверять переменную lastchar, чтобы узнать, не бу- дет ли нулевым и весь байт, к которому принадлежит данный бит. Если так, то в этот байт записывается число OxFF и соответ- ственно изменяется значение Xleft Последнее означает, что зна- чение переменной Xleft сначала усекается до числа, кратного 8, а затем уменьшается на 1, после чего она будет указывать на са- мый правый бит соседнего левого байта. После того, как нашли самую левую конечную точку строки, аналогичным образом ищем правую конечную точку, каждый раз увеличивая значение Xright Здесь нужно соблюдать осторожность, чтобы начать с правильного значения этой переменной. Если при движении вле- во в первый байт было записано число OxFF, то переменная Xright должна указывать на бит, находящийся немедленно спра- ва, от этого байта. В противном случае переменная Xright должна быть равна X + 1. В начале второго цикла while рассматриваемой функции переменная Xright имеет точно нужное значение в лю- бом случае. До сих пор в первой части функции анализировалась только та горизонтальная строка, в которой находится заданная точка (X, У). Проанализируем теперь точки, лежащие выше и ниже этой строки. Первая идея заключается в рекурсивном анализе обеих соседних строк. Она реализована в третьей части функ- ции, которую и рассмотрим сейчас, отложив обсуждение второй части. Напомним, что пикселы, которые нужно подсветить на
3.4. БЫСТРАЯ ПРОГРАММА ДЛЯ ЗАПОЛНЕНИЯ ОБЛАСТИ 81 двух соседних строках, совсем не обязательно должны образовы- вать одну непрерывную последовательность, они могут распола- гаться в нескольких разъединенных частях. Для каждой их этих строк (с Y-координатами УУ=У-1иУУ=У+1) будем последо- вательно анализировать точки (X, YY), где через X теперь обоз- начена управляющая переменная, изменяющаяся от Xleft + 1 до Xright - 1. Будем рекурсивно вызывать функцию pixfill для всех темных пикселов в этом интервале. Опять ускорим этот процесс, рассматривая полные байты, когда это возможно. Для каждого бита будем проверять, не равен ли весь байт, в котором находит- ся этот бит, числу OxFF. Если так, то увеличим значение теку- щей переменной А" не на 1, а так, чтобы она указывала на бит, находящийся немедленно справа от этого байта. Заметим, что это происходит довольно часто, поскольку после одного рекур- сивного вызова функции pixfilliX, YY) обычно будет подсвечено довольно большое число пикселов в строке YY. Поэтому практи- чески может быть использована версия функции pixfill, содержа- щая только 1 и 3 части. Глубина рекурсии этой функции определяется количеством горизонтальных строк пикселов, а не количеством пикселов, что дает существенную разницу. Однако для сложных фигур, напри- мер, таких как спираль, глубина рекурсии может все еще превы- шать допустимый предел, определяемый размером стека. Если читатель не очень знаком с принципами рекурсии, то последую- щее может быть непонятно, но постараемся кратко пояснить происходящее. Рекурсивный вызов функции соответствует уз- лам упорядоченного дерева и высота этого дерева равна глубине рекурсии. Обычно требуется, чтобы деревья были разумно орга- низованы, чтобы ограничить их высоту. Иначе говоря, деревья не должны перерождаться в почти линейные структуры. В нашей функции pixfill такое перерождение может произойти в том слу- чае, если все пикселы на одной стороне строки уже подсвечены в непрерывной последовательности. По этой причине целесооб- разно ввести несколько дополнительных рекурсивных вызовов для точек, находящихся где-то в середине области, подлежащей заполнению. Этим и занимается вторая часть функции pixfill. Она была добавлена только после экспериментального обнару- жения случаев переполнения стека, что само по себе, как мы уже знаем, является весьма неприятной ситуацией, особенно если
82 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ она появляется в графическом режиме. Результирующее умень- шение глубины рекурсии просто потрясающе. Это можно прове- рить самостоятельно введением двух глобальных переменных depth и maxdepth для фиксации глубины рекурсии следующим образом: pixfill(X, Y) int X, Y; { int Xleft, Xright, YY, i, dm; char ones4)xFF; If (++depth > maxdepth) maxdepth - depth; depth- } Значение переменной maxdepth следует вывести на экран в конце выполнения программы (после обращения к функции endgr). Это число покажет максимальную глубину рекурсии, ко- торая появлялась в процессе выполнения программы. Для экспериментальной проверки этого способа заполнения областей можно использовать нижеследующую программу ARE A FILL. Она очерчивает границы экрана и два идентичных полигона с отверстиями внутри их. Программа запрашивает на- чальную точку и, если эта точка не подсвечена, заполняет замк- нутую область, в которой она находится. Таким образом можно выбирать различные области для заполнения и для заданной об- ласти можно указывать внутреннюю точку для начала процесса заполнения. /* AREAFILLC: Area filling and emptying */ /* Заполнение и стирание области */ main() { float xO, уО; prlntf /* This program demonstrates area filling */ ("Эта программа демонстрирует заполнение области\п\п"); prlntf /* x ranges from 0 to 10 */ ("Координаты х изменяются в пределах от 0 до 10\п"); prlntf /* у ranges from 0 to 7 */ ("Координаты у изменяются в пределах от 0 до 7\п\п"); /* Enter the coordinates х, у of some starting point within these ranges: */ prlntf (n Введите значения координат х и у некоторой начальной\п"); prlntf (" точки в пределах этих диапазонов: ");
3.5. ОБРАБОТКА ПОЛУТОНОВ 83 scanfT%f %Г, &хО, &уО); lnitgr(); /* Screen boundary: */ /* Границы экрана */ move(0.0, 0.0); draw( 10.0, 0.0); draw(10.0, 7.0); draw(0.0, 7.0); draw(0.0, 0.0); /* Two copies of the same picture: */ /* Две колии одной и той же картинки */ picture(2.5, 3.5); picture(7.5, 3.5); /* Fill the region In which xO, yO lies: */ /* Заполнение области, в которой расположена точка х0, у0*/ Ш(х0, уО); endgrQ; } picture(x, у) float x, у; { /* Polygon: */ /* Полигон */ * move(x+2, y-3); draw(x+2, y-1); draw(x+1.5, у); draw(x+2, y+1); draw(x+2, у+3); draw(x, у+-1.5); draw(x-2, y+3); draw(x-2, y+1); draw(x-1.5, у); draw(x-2, y-1); draw(x-2, y-3); draw(x-0.5, y-3); draw(x-0.5, y-2.4); draw(x-1.5, y-2.4); draw(x-1.5, y-1.8); draw(x+1.5, y-1.8); draw(x+1.5, y-2.4); draw(x+0.5, y-2.4); draw(x+0.5, y-3); draw(x+2, y-3); /* Hole: */ /* Отверстие */ move(x+0.5, y); draw(x, y+1); draw(x-0.5, y); draw(x, y-1); draw(x+0.5, y); Если ввести числа 9.0 и 1.0 в качестве координат х и у началь- ной точки, то будет получено изображение рис. 3.3 из начала этого параграфа. 3.5. ОБРАБОТКА ПОЛУТОНОВ Кроме простого заполнения замкнутой области путем подсве- чивания пикселов, что делалось в параграфе 3.4, существует еще ряд возможностей. Во-первых, можно использовать цвета, если есть цветной графический адаптер и цветной монитор. Во-вто- рых, при монохромной графике, пользуясь только одной интен- сивностью, можно получить эффект полутонов сравнительно простым способом. Например, в квадрате из 3 на 3 пикселов мож- но различить до 10 различных фигур, начиная от 9 темных пик- селов до 9 подсвеченных пикселов. Такой квадрат может быть использован в качестве " суперпиксела" с десятью различными
84 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ интенсивностями 0, 1, ..., 9, соответствующих количеству под- свеченных пикселов. Недостатком этого способа является умень- шение разрешающей способности вследствие большого размера "суперпикселов". Попытаемся использовать некоторые порого- вые значения для каждого пиксела. Предположим, что для каж- дого пиксела может быть задана желаемая интенсивность в виде целого числа в диапазоне от 0 до 29. Пиксел будет темным при интенсивности 0 и полностью подсвечен при интенсивности 29. Для значений между 0 и 29 в качестве порогового значения будем использовать среднее значение 14.5. Таким образом, при значениях от 0 до 14 пиксел будет темным, а для значений от 15 до 29 он будет подсвечен. К сожалению, этот простой способ дает нежелательный эффект, называемый контурным. Будет получе- на очень резкая граница между темными и подсвеченными пик- селами даже в том случае, если затемнение должно происходить только постепенно. Есть очень простой способ устранения этого явления. Например, если интенсивность некоторого пиксела равна 11, то он будет темным, поскольку для этого пиксела будет использована интенсивность 0. Затем перенесем значение 11 на следующий пиксел. Если для этого пиксела была задана интен- сивность 13, при добавлении 11 он получит значение 24, что ле- жит выше порогового значения 14.5, поэтому этот пиксел будет подсвечен. Поскольку для этого будет использовано значение 24 вместо теоретического значения 29, которое присваивается под- свеченному пикселу, то для переноса на следующий пиксел бу- дем иметь число 5 и так далее. Здесь приведен пример горизон- тальной строки желаемой интенсивности с результатами приме- нения обоих пороговых способов. Опять используем диапазон 0, ..., 29 с пороговым значением 14.5. Буквой L обозначен под- свеченный пиксел, темный пиксел обозначен точкой (.). Желаемые интенсивности: 11 13 15 17 19 2123 23 21 18 14 10 6 5568101010 Результат простого ограничения: ..LLLLLLLL Результат вычисления порога с переносом: .L.LL.LLL.L...L...L.
3.5. ОБРАБОТКА ПОЛУТОНОВ 85 В алгоритмической форме оба пороговых способа для пикселов CY, У), X = Xmin, ..., Хтах могут быть записаны в следующем виде: /* Simple thresholding */ /* Простое пороговое ограничение*/ for (X - Xmln; X <- Xmax; Х++) If (intenslty(X. Y) > 14) dot(X, Y); /* Thresholding with carry */ /* Ограничение по порогу с переносом */ carry-0; for (X - Xmln; X <- Xmax; X++) { carry +- lntenslty(X, Y); If (carry > 14) { dot(X.Y); carry - 29; } } Рассмотрим теперь демонстрацию порогового способа с пере- носом, что приводит к получению изображения рис. 3.4. Рисунок выглядит здесь как изображение двух сфер. Про- грамма, формирующая это изображение будет ясна только в том Рис. 3.4. Две сферы
86 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ случае, если нам известны некоторые свойства поверхности, отражающей свет. Поскольку нужно будет вычислять значения интенсивностей для очень большого числа пикселов, наша про- грамма будет работать очень долго, если не сделать некоторых упрощений. Предположим, что направление падающего света совпадает с направлением наблюдения, то есть перпендикулярно плоскости экрана. Основной источник света удален очень дале- ко, поэтому лучи света параллельны. Кроме этого источника бу- дем предполагать наличие некоторой рассеянной составляющей, которая обуславливает, что не будут совершенно темными даже те части объекта, которые непосредственно не освещаются ос- новным источником. Итак интенсивность света, отраженного от поверхности, состоит из трех составляющих: 1. Рассеянная составляющая / вследствие наличия освещения объекта от окружающей среды, например, от стен комнаты. 2. Диффузионная составляющая /d, которая в соответствии с законом косинусов Ламберта пропорциональна косинусу уг- ла в между направлением падающего света и нормалью к поверхности. 3. Зеркальная составляющая /s, которая особенно применима к гладким и отражающим поверхностям, подобно зеркалу. Для полностью отражающей поверхности угол отражения равен углу падения. Способ вычисления и комбинирования этих составлякицих называется моделью освещения. Опять будем считать, что зна- чения интенсивности определяются в диапазоне от 0 до 29. Для ускорения работы программы везде, где только возможно, будем применять целочисленную арифметику. Поэтому вместо долей используются целочисленные коэффициенты £а, к#к^ соответ- ствующие процентам каждой составляющей. Отсюда значения интенсивности будут вычисляться по формуле: '=<V4+VWV/100 <зл> где вселеременные имеют тип integer (следовательно, значением для /будет усеченное частное). Коэффициенты к& и #d будут вводиться пользователем. Тогда коэффициент к$ вычисляется как 100 - к& - кд. Будем считать, что /а = 29, поэтому остается определить только значения /d и /s. Для получения изображения рис. 3.4 начнем с дальней сферы и будем рассматривать полную
3.5. ОБРАБОТКА ПОЛУТОНОВ 87 окружность, которая была бы представлена на картинке, если бы она не была закрыта сферой на переднем плане. Введем местную систему координат с началом в точке О, которая совпадает с центром С этой окружности. Координатные оси х и у лежат в плоскости картинки и имеют обычные направления, поэтому окружности с радиусом г описываются уравнением: Предположим, что ось z из точки С направлена в нашу сторо- ну и для каждой точки (дс, у) на окружности или внутри ее можно вычислить точку Ос, у, z) на сфере, где z = Vrz - xL - / (3.2) Можно выразить сомнение, что это приведет к практическо- му результату, поскольку существует бесконечно большое коли- чество таких точек. Однако число пикселов внутри окружности является конечным и на основании их пикселных координат (X, Y) можно вычислить соответствующие вещественные коор- динаты х и у, по которым можно определить конечное число то- чек P(jc, у, z) на сфере. Как видно из рис.3.5, нормаль на сфере в Рис. 3.5.
88 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ точке Р имеет направление ОР, поэтому косинус угла 0, упомя- нутый выше в связи с законом косинусов Ламберта, равен cos0= — (3.3) г Благодаря выбранному направлению лучей света можно вы- числить диффузную составляющую /d по значению z и г, не обра- щаясь фактически к функции вычислений косинуса. Рассмотрим теперь зеркальную составляющую /s, которая связана с отраженным лучом, показанным на рис. 3.5. Если бы сфера обладала идеальной зеркальной поверхностью, то этот луч сконцентрировал бы в себя весь свет отраженный в точке Р в ре- зультате освещения точки Р лучами, параллельными оси z. В об- щем случае сфера не обладает идеальной отражающей поверхно- стью. Отраженный луч, показанный на рис. 3.5. будет осью неко- торого конуса и рассеянные отраженные лучи света будут лежать внутри этого конуса в соответствии с некоторой пространствен- ной функцией распределения. Обозначим через а угол между осью этого конуса и направлением наблюдения. Затем применим простую эмпирическую функцию распределения согласно моде- ли Фонга. По этой модели зеркально отраженная составляющая вдоль линии наблюдения пропорциональна функции cosna (\a\ <л/2) где п — некоторое положительное целое число. Поскольку мак- симальное значение этой функции, равное 1, соответствует углу а = 0, то интенсивность в направлении оси конуса является наи- большей и она уменьшается по мере увеличения угла а: Чем больше выбрано значение показателя степени л, тем быстрее уменьшается интенсивность при увеличении угла отклонения. Если п стремится к бесконечности, то конус вырождается в ось, поэтому следует выбирать большие значения п для более глад- ких и зеркальных поверхностей. Для рис. 3.4, было выбрано зна- чение п = 4 (при кй = 20, k^ = 60, ks = 20). Напомним еще раз, что в нашем примере как линия наблюдения, так и направление ос- вещения, падающего на сферу, параллельны оси z. Как следст- вие этого, имеем а = 20 и cos a = cos 20 = 2 cos2 0 - 1 = 2(z/r)2 - 1
3.5. ОБРАБОТКА ПОЛУТОНОВ 89 Таким образом мы получим значения для обоих косинусов cos в и cos а без применения функции косинуса. В самом внут- реннем цикле нашей программы все арифметические вычисле- ния ведутся с целыми переменными, а не с вещественными пере- менными, не говоря уже о таких функциях, как cos, sqrt и pow. Конечно, это ухудшает читаемость программы, но это вполне разумная плата, иначе программа работала бы уж слишком дол- го. В Таблице 3.1 показан смысл некоторых переменных в про- грамме. Таблица 3.1 Целочисленные переменные в программе Используется Вместо целая вещественной 2 ixmax2 900 хтах iz2 900 z2 ix2[X] 900 х2 Idl Id2 cosa900 900 cos a Будем также использовать функции setprdim и printgr, кото- рые будут описаны в следующей главе. Они применяются при не- обходимости вывода графических результатов непосредственно на матричное печатающее устройство. Это упоминание сделано потому, что иначе было бы непонятно, как получено изображе- ние, приведенное на рис. 3.4. С другой стороны, программа отно- сится к содержанию данной главы, поскольку она показывает, что можно сделать на простой монохромной растровой графике. Заметим, в частности, что изображение на экране изменяется в процессе работы этой программы, поскольку ближняя сфера бу- дет частично закрывать сферу, ранее сформированную на зад- нем плане. Укажем также на резкий контраст между этим спосо- бом получения перспективного изображения и приборно-незави- симым способом вычерчивания линий, изложенным в предыдущей книге автора "Принципы программирования в ма- шинной графике". И, наконец, приводим ниже полный текст программы SHADE.С, которую можно использовать для экспе- риментов с различными значениями коэффициентов кл, £d, ks и
90 Глава J. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ показателем степени п в модели освещения Фонга. После компи- ляции эта программа должна быть скомпонована совместно с файлом GRPACK.OBJ. /* SHADE.С: Two spheres, displayed with shading */ /* Полутоновое изображение двух сфер */ ♦Include "stdlo.h" ♦include "math.h" extern float x_max, ymax, horfact, vertfact; extern Int X max, Y max, drawmode, colorgr, drawmode; Intel, c2, c3; main() { Int j, k, XI, Xr, X, Yb, Yt, Y, ka, kd, ks, offset, Ika, n, I. 12, sq_root[1000], Id2, Id, Is, I, carry-O, cosa900, Ix2[720], r2. Ixmax2, Iz2; float x, у, г, fy(), rO, y2, xC, yC, xmax2, xmax, xxC[2],yyC[2],rr[2]; char ch, evenllne[90], oddllne[90], result[90]; for (j-0; J<90; j-и-) { evenllne[J}-Ox7F; oddllne[J]-0xF7; } prlntf /*ka, kd and ks are percentages, ka + kd + ks - 100 */ /*(a - ambient, d - diffuse, s - specular)*/ ("Коэффициенты отражения ka, kd, ks задаются в процентахЛп"); prlntf ("Должно соблюдаться соотношение ka + kd + ks - 100.\n"); prlntf ("(ka - рассеянный свет, kd - диффузионное,"); ^ prlntf ("ks - зеркальное отражение)\п"); prlntf /* "Enter ka:"*/ ('ЛпВведите ka:"); scanf("%d", &ka); Ika - ka * 29; prlntf /* "Enter kd:" V ('ЛпВведите kd:"); scanf("%d", &kd); prlntf ('ЛпКоэффициент ks вычисляетсяЛп"); ks - 100 - ka - kd: prlntf /* "Enter the exponent n (n * 1, 2,...) of Phong shading: */ ('ЛпВведите показатель n полутонов по Фонгу (n - 1,2,...):"); scanf("%d". &n); prlntf /* "Printed Output required and printer switched on? (Y/N): */ ('ЛпТребуется вывод на печать и включен ли принтер? (Y/N):"); do {ch - getchar(); ch - toupper(ch); } while (ch !- 'Y' && ch I- 'N'); lf(ch--'Yt)setprdlm(); xxC[0] - 0.38 * x_max; xxC[1] - 0.68 * x__max; yyC[0] - 0.42 * y_max; yyC[1] - 0.60 * y_max; rr[0] - 0.35 * y_max; rr[1] - 0.28 * y_max; * InltgK); If (colorgr) { d-1;c2«80;c3-1; } else { с1-3; c2-90; c3-2; }
3.5. ОБРАБОТКА ПОЛУТОНОВ 91 /* The intensity ranges from 0 to 29. */ /* The background intensity is now set. */ /* Интенсивность определяется в пределах от 0 до 29. */ /* Вначале устанавливается интенсивность фона. */ for (Y-0; Y<-Y_max; Y++) { offset - startaddress(Y); /* Address of pixel (0, Y) */ /* Адрес пиксела (0, Y) */ p6ke(0xB800. offset, (Y & 1 ? oddline : evenllne), c2); } drawmode- 1; for (I2-0; I2<1000; I2++) sq_root[l2] - (int) (sqrt((double) 12) + 0.5); for(k-1; k>4); k--)/* k is sphere number(0-nearersphere) */ /* k — номер сферы (0 - ближайшая сфера) */ { r0 - rr[k]; /* Floating */ /* С плавающей точкой*/ r2 - (Int) (rO * Ю + 0.5); /* Integer */ /* целое число */ r-sqrt((double)r2); xC-xxC[k];yC-yyC[k]; for (X-IX(xC-r)-1; X <- IX(xC+r)f 1; Х++) { х - X / horfact - xC; ix2[X] - (Int) (900.0 * x * x + 0.5); } Yt - IY(yC + r); Yb - IY(yC - r); /* Yt < Yb */ for (Y-Yt; Y<-Yb; Y++) { y-fy(Y)-yC:y2-y*y; xmax2 - r2 - y2; if (xmax2 < 0.0) xmax2 - 0.0; ixmax2 - (int) (900.0 * xmax2 + 0.5); xmax - sqrt(xmax2); XI - IX(xC - xmax); Xr - IX(xC + xmax); for (X-XI; X<-Xr; X++) /* This loop uses only integers */ /* В этом цикле только целые числа */ { Iz2 - lxmax2 - lx2[X]; /* iz2-900* z * z */ Id2 - Iz2/r2; /* Square of diffuse Intensity */ /* Квадрат диффузионной интенсивности */ lf(ld2<0)ld2-0; Id - sq_root[ld2]; /* Diffuse Intensity *• 30 cos theta */ 9 /* Диффузионная интенсивность - 30 cos theta */ if(ld2<-450)ls-0;else { cosa900 - 2 * Id2 - 900; /* cosa900 - 900 cos alpha */ Is - cosa900 / 30; for (!-2; K-n; i++) Is - Is * cosa900 / 900; /* Exponentiation according to Phong */ /* Возведение в степень по Фонгу */ } l-(lka + kd*ld + ks*ls)/100; carry +-1; if (carry > 14)
92 Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ { dot(X. Y); carry - 29; } else { drawmode - -1; dot(X. Y); drawmode - 1; } } } } if(ch--'Y' &&getch()) { /* Reverse video: */ /* Негативное изображение */ for (Y-0; Y <- Y__max; Y++) { offset - startaddress(Y); peek(OxB800, offset, result, c2); for Q-0; J<c2; J++) result[j] л- OxFF; poke(0xB800, offset, result, c2); } prlntgr(0, X_^max, 0, Y max); } endgr(); } float fy(Y) Int Y; { return (Y_maxY)/vertfact; } Int startaddress(Y) Int Y; /* Start address of pixel (0, Y) */ /* Начальный адрес пиксела (0, Y) */ { return 0x2000*(Y&d) + c2*(Y»c3); } Замечания о модуле GRPACK.C В этой главе к модулю LINDRAW.C, приведенному в конце главы 2, были добавлены несколько функций и изменены неко- торые существующие функции. Модифицированная версия получила имя GRPACK.C и может вызвать удивление, что этот модуль здесь не приведен. Но вхлаве 4 будут сделаны дополни- тельные изменения и поэтому исходные тексты функций, входя- щие в этот модуль, помещены в конце следующей главы. Тем не менее, модуль GRPACK.C можно применять уже сейчас, игно- рируя функции, которые еще предстоит рассмотреть.
Глава 4 ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ 4.1. ПРИНЦИПЫ РАБОТЫ МАТРИЧНЫХ ПРИНТЕРОВ Существуют два наиболее распространенных типа печатаю- щих устройств (принтеров), применяемых совместно с персо- нальными компьютерами, а именно — ромашковые и матричные принтеры. Для вывода текста каждый из принтеров имеет свои определенные преимущества перед другим. Если необходимо на- печатать текст с качеством пишущей машинки, то ромашковый принтер подойдет для этого наилучшим образом. Матричные принтеры дают меньший контраст между черными литерами и белым фоном, но зато они предоставляют значительно больше возможностей, если в одном документе требуется использовать несколько типов литер и шрифтов. Однако сейчас нас больше ин- тересует вывод не текста, а графической информации, и по этой причине мы не сможем использовать ромашковые принтеры, по- этому предметом рассмотрения в этой главе будут только мат- ричные принтеры. Матричные принтеры сейчас очень популяр- ны, поскольку они менее дорогие, чем перьевые плоттеры (гра- фопостроители) и одинаково пригодны для вывода как текстовой, так и графической информации. Очень удобна доста- точно хорошая совместимость матричных принтеров, выпускае- мых разными фирмами. Хотя некоторые незначительные отли- чия и существуют, но общие характеристики большинства мат- ричных принтеров вполне пригодны для достижения нужного нам результата. Попытаемся не применять никаких особых свойств и команд, которые в некоторых принтерах могут приве- сти к трудностям применения. Перед тем, как писать какие-либо программы для матричных принтеров, необходимо получить некоторые элементарные све-
94 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ Битовый набор Колонка точек О О О 1 • О О 1 • 1 • Рис. 4.1. Битовый набор и колонка точек дения об их "точечно-графических" возможностях. При выводе текста на печатающее устройство обычно печатаются строки ли- тер, каждая из которых содержит, скажем, до 80 символов. Со- вершенно аналогичным образом работает матричный принтер и в графическом режиме, печатая строки "символов", но теперь каждый такой "символ", является точечной фигурой, состоящей из точек в восьми позициях, расположенных вертикально, одна над другой. Поскольку такая фигура по ширине занимает только одну точку в горизонтальном направлении, то на одной строке их может разместиться очень много, порядка 960. Будем рассматри- вать каждую такую фигуру как вертикальный столбец из восьми бит, каждый из которых может быть единицей, если в этой пози- ции печатается точка, или 0, если точка отсутствует. Пронуме- руем их позиции через 0, 1, ..., 7 снизу вверх, так что самый старший бит располагается сверху, а не слева, как в нормальной двоичной записи. Как обычно, эти позиционные числа использу- ются в виде показателя степени основания 2, то есть каждому столбцу может быть приписано число. На рис. 4.1. показан при- мер фигуры вместе с соответствующим (увеличенным) столбцом точек. Значение этой комбинации равно 16 + 2+1 = 19, что на языке Си может быть записано как 0x13. Для каждой комбинации, ко- торая должна быть напечатана, ее значение должно быть посла- но на принтер в восьми битах, то есть в одном байте. Последова- тельности таких байтов должна предшествовать команда прин- тера, в которой указывается сколько байтов будет послано. Будем использовать команду вида: ESC 'L' Ail л2
4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ 95 где ESC Символ "расширения" кодов ASCII с числовым представлением 27. 'L' Символ "L" (его код 76). л 1, я2 Два восьми-битовых числа, таких, что если число N определяет количество передаваемых байтов, то на языке Си числа п\ и п2 могут быть вычис- лены как: nl=N%256; л2»ЛГ/256; Следовательно, N - 256 * п2 + п\. 4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ В языке Си есть три способа посылки данных на матричный принтер или, говоря техническим языком, на параллельный порт принтера: 1) используя функцию /open с символическим именем устрой- ства LPT1; 2) применяя программу Базовой Системы Ввода/Вывода (BIOS), ассоциированную с программным прерыванием 0x17; 3) непосредственно через порты ввода/вывода ОхЗВС, 0x3BD, ОхЗВЕ. Фактически это три разных уровня. В нашей первой програм- ме будем использовать способ (1) — самый высокий уровень. После этого обсудим, почему на практике предпочтем способ (2) — средний уровень (замечание об этом выборе приведено в конце этого параграфа). Самый нижний уровень (3) здесь упо- мянут только ради полноты, он фактически используется про- граммами уровней (1) и (2), но о нем не нужно беспокоиться. Программа MATPRLC показывает, как можно напечатать строку, состоящую из 300 копий точечной фигуры, показанной на рис. 4.1. /* MATPR1.C: This program prints a line of 300 dot patterns */ /* Эта программа печатает строку из 300 столбцов точек */ ♦Include "stdlo.h" #define N 300
96 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ main() { int n1. n2. J; FILE *fp; fp - fopen("lptr, "wb"); /* Write Binary to printer */ n1 - N % 256; /* Посылка числа на принтер */ /*n1-44 */ n2-N/256; /*n2- 1 */ fprintf(fp, "%c%c%c%c". 27, 'L\ n1. n2); for (j-0; j<N; J-++) putc(19, fp); /* 300 bit patterns */ fclose(fp); /* 300 битовых наборов */ } Результат работы этой программы показан на рис. 4.2. Изо- бражение состоит из 300 столбцов, один из которых показан на рис. 4.1. Рис. 4.2. Пример вывода программы MATPR1.C Некоторые моменты этой программы требуют пояснения. При вызове функции /open имя Iptl является зарезервирован- ным именем файла, которое идентифицирует принтер. В качест- ве второго аргумента функции /open используем строку "wb"y которая сообщает компилятору Lattice С о режиме "бинарной за- писи". Буква Ъ в этой строке отражает тот факт, что битовое чис- ло должно быть использовано буквально как двоичное значение и не должно обозначать нормальный (печатаемый или непеча- таемый) символ из набора ASCII. Вызов функции /open возвра- щает указатель файла /р, который теперь может применяться, когда что-то нужно послать на принтер. При первом обращении к функции /print/ на принтер посылаются четыре байта. Их зна- чения равны: 27 76 44 1 Конечно,запись ESC 4L' 44 1 была бы более читаемой, но предыдущая последовательность по- казывает более четко, что фактически посылается на принтер. Напомним, что спецификация формата %с приводит к тому, что соответствующий элемент данных должен быть записан как сим- вол из восьми бит, даже если элемент данных имеет тип int (за- нимающий в памяти 16 бит). В команде ESC 'L' п\ п2
4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ 97 буква *L' определяет "двойную плотность", означающая, что по горизонтали размещается 120 точек на дюйм. Вместо буквы 4L' можно задать букву 4К' для "одинарной плотности" при 60 точ- ках на дюйм; в зависимости от типа принтера могут быть доступ- ны и другие возможные плотности. Поскольку в нашем примере мы имеем ЛГ=256*л2 + п1=300 то принтер ожидает, что за этой командой последует точно 300 байт. После печати этих 300 байт система выйдет из графическо- го режима. В цикле был использован оператор putc(19, fp); который дает результат, совершенно аналогичный результату, получаемому при выполнении оператора printf(fp, П%<Л 19); Этот способ (то есть уровень 1) применялся автором довольно длительное время без каких-либо затруднений. Пока, к величай- шему изумлению, в один прекрасный день одна из картинок упорно не хотела выводиться на печать, это случилось с рис. 3.1. Ее начало печаталось совершенно правильно, но где-то в середи- не принтер останавливался. Потребовался полный рабочий день для тестирования, чтобы обнаружить, что причиной была бито- вая комбинация 0001 1010, то-есть число 26. Из таблицы кодов ASCII выяснилось, что число 26 является кодом комбинации Ctrl-Z, которая означает "конец ввода". Оказалось, что принтер, даже в графическом режиме, воспринимает только 255 из 256 возможных значений, образуемых восемью битами. Как только пытались послать число 26, операционная система интерпрети- ровала его как конец потока данных и последующие данные на принтер больше не посылались. Книги и руководства хранили глубокое молчание по этому вопросу, так что автору пришлось принимать решение самостоятельно. Вначале была рассмотрена возможность простого изменения какого-либо бита при появле- нии комбинации 0001 1010. Вполне вероятно, что это не сказа- лось бы на результате, но это было бы все-таки некорректным, поэтому пришлось искать более приемлемое решение. Посколь- ку такая проверка на печатаемые символы производится на самом высшем уровне средств ввода/вывода, обойдем этот тест 4—275
98 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ переходом на более низкий уровень вызова программ Базовой Системы Ввода/Вывода BIOS, используя программное прерыва- ние. Однобайтовые данные посылаются на параллельный порт принтера (иными словами, на матричный принтер) путем зане- сения их в регистр AL и вызова программного прерывания 0x17, очистив предварительно регистры АН и DX. static void prchar(char ch) /* Print character */ /* Посылка байта ch на параллельный порт принтера */ { regs.x.dx4); /* Print selection */ /* Выбор принтера */ regs.h.ah-O; /* Send byte from AL to printer */ /* Посылка байта из регистра AL на принтер */ regs.h.al-ch; /* Byte to be sent to printer */ /* Байт, посылаемый на принтер */ Int86(0x17, &regs, &regs); } Если мы захотим включить этот исходный текст в нашу соб-' ственную программу, то в ее начало необходимо включить еще две строки: ♦include "dos.h" union REGS regs; Но эту функцию мы будем включать в состав модуля GRPACK.C, который уже содержит эти две строки. Строго говоря функция prchar пригодна не только для графики. Она может быть использована также для пересылки нормальных символов непосредственно на принтер. Но это уже можно реализовать про- сто применением функции fprint/, как это сделано в программе MATPR1.C, поскольку среди нормальных символов код 26 ни- когда не появится. Поэтому только в связи с графикой нам дейст- вительно нужна функция prchar и с этой точки зрения целесооб- разно расширить модуль GRPACK.C включением этой функции. Если следующую программу MATPR2.C связать с этим расши- ренным модулем и пустить ее на выполнение, то будет получен точно такой же результат, как и для программы MATPRLC. /* MATPR2.C: This program prints a line of 300 dot patterns */ /* Эта программа печатает строку из 300 столбцов точек */ #include "dos.h" #define N 300 maln() { intn1.n2,J; n1-N%256; /*n1-44 */
4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ 99 п2 - N / 256; /* п2 - 1 */ prchar(27); prcharfL'); prchar(n1); prchar(n2); for (j-0; J<N; j++) prchar(19); /* 300 bit patterns */ } /* 300 битовых наборов */ Если в программе MATPRLC число 19 заменить на 26, то это значение было бы интерпретировано как комбинация Ctrl-Z и программа не'станет работать. Но программа MATPR2.C при та- кой модификации прекрасно работает, поэтому эти две програм- мы не эквивалентны и предпочтительна вторая программа (см. также замечание в конце данного параграфа). До сих пор на печать выводилась только одна строка даже без попытки перемещения в начало следующей строки. В принципе, принтер находится в "графическом режиме" только начиная с команды ESC 'L' п\ п2 (или некоторых подобных команд) и до получения последнего из 256 * л2 + п\ байтов, следующих за ней. После этого можно печатать нормальные символы и, в частно- сти, можно послать на принтер символ перевода строки. В про- грамме MATPRLC для этого следовало бы включить операторы putcC\n\fp) или fprintf(fpy"\n") Поскольку, мы предпочитаем MATPR2.C, то будем использовать prcharC\n') Коду4 W фактически соответствует значение 10 (шестнадца- теричное ОхОА), поэтому вместо него можно записать prchari 10) или prchar(ОхОА), но обозначение ' W более ясно выражает на- ше намерение. Более важно принять меры, чтобы бумага переме- щалась вверх точно на необходимое расстояние. Без специаль- ных мер бумага вероятнее всего переместится вперед на 1 /6 дюй- ма, поскольку такое расстояние между строк применяется при обычной печати. По вертикали расстояние между двумя соседни- ми точками равно 1/72 дюйма. Поэтому при печати столбца из 8 точек необходимо расстояние между строками в 8/72 дюйма, чтобы две соседние строки располагались рядом. Хотя это и мож- но осуществить, но более безопасно избегать такого расстояния между строками, позднее поясним почему. Вместо этого будем использовать только семиточечные столбцы, поэтому все значе- ния будут меньше 128 и потребуется расстояние между строками равное 7/72 дюйма. В качестве примера напечатаем шесть строк по 400 битовых наборов в каждой. Единственными битовыми 4**
100 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ 0x55 0х2А X • • X X • • X X • • X X • Рис. 4.3. Два битовых набора и их значения комбинациями в этом примере будут числа 0x55 и 0х2А. Соот- ветствующие вертикальные столбцы показаны на рис. 4.3. Обозначим шесть печатаемых строк числами 0, 1,2, 3, 4, 5. На строках 0, 2, 4 будем печатать значения 0x55 и 0х2А именно в этом порядке попеременно группами по четыре одинаковых значения в каждой. Для получения приятного изображения в ре- зультате вывода шести соседних строк, в строках 1, 3, 5 будем выводить те же комбинации, но в другом порядке: сначала четы- ре раза 0х2А, затем четыре раза 0x55, опять четыре раза 0х2А и гак далее. Программа MATPR3.C приведена ниже вместе с результатом ее работы на рис. 4.4. /* MATPR3.C: Six adjacent lines */ /* Шесть соседних строк */ #define N 400 main() { int n1, n2, i, j, oddline, oddquadruple; prchar(27); prchar(T); /* ESC T: Set line spacing to 7/72 inch */ /* Межстрочное расстояние 7/72 дюйма */ prchaifXn'); n1 - N % 256; n2 - N / 256; for (i-0; i<6; i++) { oddline-i%2; prchar(27); prcharCL'); prchar(n1); prchar(n2); for O-O; j<400; j-н-) { oddquadruple - j%8 > 3 ; prchar(oddline - - oddquadruple ? 0x55 : 0x2A); } prcharC\n'); } prchar(27); prcharC®')', /* ESC '@': Reset printer to power-up state */ /* Восстановление состояния принтера */ } Рис. 4.4. Пример вывода программы MATPR3.C
4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ 101 Первые два вызова функции prchar выдают на принтер ко- манду ESCT которая говорит, что последующие команды перевода строки должны быть выподнены с межстрочным расстоянием 7/72 дюй- ма. Такая команда перевода строки находится в следующей стро- ке операторов, а именно prchar О W). Переменная oddline будет принимать значения 1 для строк 1, 3, 5 и 0 — для строк 0, 2, 4. Четыреста столбцов в каждой строке логически будут поделены на 100 четверок, пронумерованных 0, 1, ..., 99, причем каждая из них состоит из четырех одинаковых последовательных столб- цов. Переменная oddquadruple равна 1 для четверок 1,3, •.., 99 и 0 для четверок 0, 2,..., 98. Для самой первой четверки имеем: oddline = oddguadruple = 0 и здесь значение 0x55 дает желаемую конфигурацию битовой комбинации. Если только одна из двух переменных будет равна 1 (вторая может оставаться равной 0), то будет установлена бито- вая комбинация, соответствующая значению 0х2А, а если обе переменные равны 1, то опять будет действовать значение 0x55. Это объясняет появление условного оператора в качестве аргу- мента функции prchar в самом внутреннем цикле. Команда: ESC '@' заданная почти в самом конце программы, восстанавливает для принтера состояние, которое он имеет после включения питания в отношении межстрочного расстояния. Остается объяснить, почему потребовалось использовать се- мибитовые вместо восьмибитовых колонок. Довольно курьезно, но применение восьмибитовых колонок может вызвать две про- блемы, которые отсутствуют при семибитовых колонках. Во- первых, некоторые принтеры имеют внутренние переключате- ли, и если они неправильно установлены, то восьмой бит будет игнорироваться. При обычной текстовой печати необходимы только семь бит и когда автор впервые попытался вывести графи- ческое изображение на матричной принтер, то попался чей-то принтер, который до этого использовался исключительно для вывода текстовой информации. Потребовалось затратить не- сколько часов работы, чтобы обнаружить, почему при графичес- ком выводе на изображении появлялось несколько тонких белых
102 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ горизонтальных полос. Оказалось, что некоторые внутренние переключатели на принтере были включены, что означало игно- рирование восьмого бита каждой комбинации, то есть предпола- галось, что он равен 0. После их выключения принтер стал при- нимать все восемь битов и был получен правильный графический результат. Если бы весь вопрос заключался только в этом, то можно было бы продолжать применение восьмибитовых комби- наций, которые, кроме всего прочего, обеспечивают более высо- кую скорость работы. Тогда было бы достаточным сделать пре- дупреждение о необходимости обратить внимание на положение переключателей в случае каких-либо неполадок при выводе. Но решение об использовании семибитовых комбинаций позволило избежать и такого затруднения: в этой ситуации принтер работа- ет с переключателями, находящимися в любом состоянии! Есть и еще один аргумент в пользу семибитовой комбинации. Команда ESCT для 7/72 дюймового межстрочного расстояния не имеет доста- точно простого аналога команды для 8/72 дюймового расстоя- ния, которое требуется для восьмибитовой комбинации. В прин- ципе для этого есть команда ESC 'А' 8 но когда автор попытался ее применить, то обнаружилась разни- ца ее интерпретации в разных принтерах. В принтере STAR NL 10 эта команда стала эффективной только после второй коман- ды, а именно ESC '2' но, к сожалению, эта дополнительная команда должна отсутст- вовать, если применяется принтер EPSON LX-80, поскольку здесь она установила бы межстрочное расстояние в 1/6 дюйма. Тогда была сделана попытка использовать команду ESC 'У 24 которая установила бы межстрочное расстояние 24/216 (« 8/72), но опять было обнаружено различное восприятие этой команды на разных принтерах. К счастью, простая команда ESC4' работает правильно на всех принтерах, которые использовались автором. После анализа первой проблемы, упомянутой выше,
4.2. ПРОГРАММЫ, ПЕЧАТАЮЩИЕ ГРАФИЧЕСКИЙ РЕЗУЛЬТАТ 103 было решено не заниматься этими неприятными трудностями и использовать семибитовую графику. Цена, которую пришлось заплатить за этот выбор, составляет ухудшение характеристик в отношении 7/8, что, в конце концов, не так уж страшно по срав- нению с ухудшением переносимости программ и удобства для пользователя. Замечание Проблема с комбинацией CTRL-Z, о которой было упомянуто в этом параграфе, возникла при использовании компилятора Lattice С, версии 3.0. Но после получения версии 3.1. с новым описанием обнаружилось, что эта проблема возникала из-за ошибки и что она исправлена в этой новой версии. Это значит, что с новым компилятором способ печати высокого уровня опе- ратором fopen("lpt1M, "wb") будет правильно работать для всех значений байтов, включая 26. Таким образом больше нет необходимости применять несколько более низкий уровень, основанный на программном прерывании 17Н. Однако автор рекомендует продолжать применять послед- ний способ. Кажется маловероятным, что программное прерыва- ние будет вызывать какие-либо более серьезные затруднения у других пользователей, чем принятие зависимого от компилятора соглашения об исключении имени Iptl для обозначения обычного файла и резервирования его для принтера. Можно возразить, что функция тйб, используемая нами для программного прерыва- ния, также зависит от компилятора, но не следует забывать, что такое программное прерывание требуется нам во многих других местах (см. параграф 1.2 и главу 2), то есть мы уже предполага- ем, что функция int&b (или нечто эквивалентное) всегда доступ- на. Другим подтверждением справедливости нашего выбора яв- ляется то, что при таком решении нет необходимости в какой- либо стандартной функции ввода/вывода для печати. Следовательно, выполняемая графическая программа теперь может быть короче, чем она была бы, если бы пришлось исполь- зовать такую функцию, как /open исключительно только ради вывода на печать.
104 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ 4.3. ПЕЧАТЬ ЭКРАННОГО ДАМПА После такого предварительного анализа нам теперь необхо- димо сделать твердую копию результатов графических программ в том виде, как они изображены на экране. Разработаем функ- цию printgr, которая вызывает информацию из экранной памяти и посылает ее на принтер. Временно мы не будем слишком сильно беспокоиться о конк- ретных размерах по горизонтальному и вертикальному направ- лениям. Без специальных мер предосторожности окружность на экране после распечатки принтером на бумаге будет выглядеть как эллипс, но в данный момент это неважно. Решение этой проблемы будет дано в параграфе 4.4. Напомним, что мы применяем целочисленные пикселные координаты X и У, где 0 < X < Х_тах 0 < Y < Y_max и точка (X max, Y max) находится в нижнем правом углу экрана. Конкретные значения координат этой точки равны: X max = 639 (для цветной графики) или 719 (для монохромной) Y тах= 199 (для цветной графики) или 347 (для монохромной) К функции printgr добавим четыре аргумента, которыми обозначим границы прямоугольного окна на экране. Содержи- мое этого окна и будет выведено на печать. Первые два аргумен- та означают минимальное и максимальное значения для Ху а по- следние два — аналогичные значения для У. Поэтому, если нуж- но вывести на печать весь экран, то включается оператор: printgr(0, X max, 0 Y max); Используя результаты обсуждения предыдущего параграфа, можно написать функцию printgr в следующем виде: printgr(Xlo, Xhi. Ylo, Yhi) int Xlo, Xhi, Ylo, Yhi; /* Print contents of rectangle on matrix printer */ /* Вывод содержимого прямоугольника на матричный принтер*/ { int n1. n2, ncols, i, X, Y, val; prchar(27); prcha'r(T); /* Line spacing 7/72 inch */ ■/* Расстояние между строками 7/72 дюйма */
4.3. ПЕЧАТЬ ЭКРАННОГО ДАМПА 105 ncols-Xhi-Xlo+1; n1-ncols%256; n2-ncols/256; for(l-Ylo;i<-Yhi;H-7) { checkbreak(); /* To make DOS check for console break */ /* Установка проверки консольного прерывания ДОС */ prchar(27); prchar('L'); prchar(n1); prchar(n2); for(X-Xlo;X<-Xhi;X++) { val-0; for(YH;Y<i+7;Y++) { val «- 1; val I - (Y>Yhi ? 0 : pixlit(X, Y)); } prchar(val); } prchar('\n'); } prchar(27); prcharO®')', } Если применяется цветной графический адаптер, то обраще- ние к функции printgr может производиться только в графичес- ком режиме, поскольку программное прерывание, используемое для перехода в текстовый режим стирает графические страницы. Как уже обсуждалось в параграфе 2.7, для перехода из графичес- кого режима в текстовый режим применяется функция endgr. Эта функция обращается к функции tojtext, которая в свою оче- редь вызывает функцию endcolgr, если подключен цветной гра- фический адаптер. В последней функции применяется програм- мное прерывание ЮН для фактического переключения режима и это приводит к нежелательному стиранию графической страни- цы. Если читатель знаком с языком ассемблера, то он может это уточнить путем изучения листингов Базовой Системы Вво- да/Вывода (BIOS), имеющихся в техническом руководстве для ПК фирмы IBM. Из-за такого стирания графической страницы в компьютерах с цветным графическим адаптером нельзя использовать про- грамму, аналогичную программе PRGR.C, приведенной ниже. В нашей функции endmongr (см. параграф 2.7) для моно- хромного графического адаптера текстовая страница очищается, начиная с адреса В0000, но графическая страница, начинающая- ся с адреса В8000, остается без изменения. Как результат этого можно использовать функцию printgr даже после возврата в текстовый режим. Тогда графические результаты больше не ото- бражаются на экране, но они остаются в графическом адаптере.
106 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ Может показаться, что использование функции printgr в тексто- вом режиме не имеет практической ценности, но в действитель- ности это имеет смысл, поскольку в текстовом режиме более просто реализовать взаимодействие с компьютером, чем в гра- фическом режиме. В частности, мы можем завершить выполне- ние нашей программы (очевидно перейдя в текстовый режим), а затем запустить на выполнение новую программу для вывода на печать результата, полученного в предыдущей программе и все еще находящегося в экранной памяти. Поскольку сама графи- ческая программа уже не существует больше в памяти, то для этого можно применить термин "аварийный дамп" графического экрана. Автор применял такой способ для получения различных иллюстраций в этой книге (например, рис. 2.3.) при использова- нии монохромного графического адаптера. В этом есть забавный момент, который не должен быть обойден вниманием. Для выво- да на печать применяется функция printgr, в которой функция pixlit определяет состояния пикселов, а эта функция использует 'константы' cl, с2, сЪ для необходимых вычислений адресов. Однако в параграфе 2.6 мы видели, что значения с\, с2, сЪ опре- деляются в функции initgr, но в программе аварийной распечат- ки нежелательно входить в графический режим, поэтому функ- ция initgr вызываться не будет. Следовательно, задачу определе- ния графических констант нужно выделить в самостоятельную функцию: setgrcon(colorgr) Int colorgr; /* Set graphics constants */ /* Формирование графических констант*/ { If (colorgr) { X__max - 639; Y__max - 199; d-1;c2-80;c3-1; } else { X_max-719;Y__max-347; d-3;c2-90;c3-2; } } , Эту функцию добавим в модуль GRPACK.C и будем обра- щаться к ней в функции initgr. Теперь ее можно вызывать в лю- бом месте, что и реализуем в следующей программе, предназна- ченной для аварийного дампа графического экрана в системах с монохромным графическим адаптером:
4.4. РАСПЕЧАТКА ОКРУЖНОСТИ В ВИДЕ ОКРУЖНОСТИ 107 /* PRGR.C: This program prints the screen on a matrix printer; */ /* It can be used with the monochrome graphics adapter, */ /* not with the color graphics adapter. */ /* Эта программа выводит содержимое экрана на */ /* матричный принтер; она может быть использована */ /* только с монохромным графическим адаптером, но */ /* совершенно не пригодна для цветного дисплея. */ main() { extern int X max, Y max; if (iscolor()) /* 0: monochrome graphics */ /* 0: монохромная графика */ { prlntf("3Ta программа пригодна только для монохромной графики\п"); /* This program is suitable only for monochrome graphics */ exlt(1); } setgrcon(0); printgr(0, X max, 0, Y max); } 4.4. РАСПЕЧАТКА ОКРУЖНОСТИ В ВИДЕ ОКРУЖНОСТИ Остается решить еще одну проблему, а именно: как достичь такого результата, чтобы в отпечатанном изображении размеры объектов по вертикали соответствовали размерам по горизонта- ли. Выражаясь более кратко, желательно, чтобы на принтере изображение окружности выглядело как окружность, а не как эллипс. Это кажется сложной задачей, поскольку плотность, то есть количество точек на единицу длины, различна в обоих на- правлениях, а для принтера эти плотности отличаются от плот- ностей для видеодисплея. Как это ни плохо, существует разли- чие между монохромным и цветным графическими адаптерами, поэтому можно было бы ожидать, что требуемый анализ и соот- ветствующий текст программы будут длинными и утомительны- ми. К счастью, это не так. Мы сохраним принцип один-к-одному для отражения пикселов на экране на точки на бумаге. Это озна- чает, что как на экране, так и на бумаге (и для обоих типов адап- теров!) будем использовать целочисленные значения координат X и Y с максимальными значениями X max и Y max. Кроме этих целочисленных координат нами были введены веществен- ные координаты х и у, которые имеют очень хорошее свойство, заключающееся в том, что единицы длины по обеим координа- там имеют одинаковые размеры на экране: если оба значения
108 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ увеличиваются на единицу, то происходит перемещение при- мерно на один дюйм вправо и почти на то же расстояние вверх. Это достигалось путем определения х_тах = 10.0 и yjnax = 7.0; в параграфе 2.1. Значения 10.0 и 7.0 были основаны на размерах экрана. Эти хорошие "круглые" числа предпочтительнее, чем фактически измеренные размеры, а именно 8.27 * 5.58 дюйма. Более важно их отношение: 10.0/7.0=1.43 и 8.27/5.58 = 1.48 Если бы для xjnax и у_тах вместо 10.0 и 7.0 были выбраны более точные значения 8.27 и 5.58., то один дюйм, измеренный на экране по вертикали или по горизонтали, соответствовал бы единичной разности наших вещественных координат х и у и, в частности, изображение окружности на экране дало бы несколь- ко лучшие результаты, чем сейчас. Этот анализ приводит нас к решению проблемы. Теперь, когда мы используем принтер, зна- чения xjnax и yjnax следовало бы ориентировать на макси- мальные размеры и на этот раз нам не следует приносить в жерт- ву точность за счет получения "круглых" чисел. Наибольший прямоугольник, который можно начертить, выражается в пик- селных координатах как 0 < X < Х_тах 0 < Y < Y_max и будет иметь длину xjnax и ширину yjnax дюймов при условии определения их точного значения. Это означает, что нам необ- ходимо только переопределить значения переменных xjnax и yjnax как размеры наибольшего прямоугольника, который бу- дет изображен на бумаге. Поскольку на бумаге горизонтальная плотность составляет 120 точек на дюйм, а самая длинная гори- зонтальная линия состоит из X max + 1 точек как на экране, так и на бумаге, то она будет занимать точно (X шах +1) / 120 дюймов при выводе на печать. Эту величину мы и должны задать для переменной xjnax. Аналогично, переменной yjnax должно быть приписано значение (Y max +1) /72, поскольку верти- кальная плотность составляет 72 точки на дюйм. Ниже приво- дится текст функции, выполняющей такое переопределение:
4.4. РАСПЕЧАТКА ОКРУЖНОСТИ В ВИДЕ ОКРУЖНОСТИ 109 setprdim() /* This function sets xmax and ymax such that graphics */ /* results will eventually be printed with correct */ /* dimensions, both horizontally and vertically. */ /* Эта функция устанавливает значения xmax и ymax */ /* с таким расчетом, чтобы графическое изображение */ /* было выведено с точным соблюдением размеров как */ /* по горизонтали, так и по вертикали. */ { extern float xmax, ymax; extern int X max, Y max; setgrcon(iscolor()); xmax - (X_max + 1)/120.0; y_max-(Y_max + 1)/72.0; } Заметим, что значения переменных X max и Y max перед использованием должны быть установлены в соответствии с при- меняемым типом графического адаптера. С этой целью из дан- ной функции производится обращение к функции setgrcon для присвоения этим переменным соответствующих значений. Ког- да, после обращения к функции setprdim будет вызвана функция initgr, то уже новые значения х_тах и yjnax будут использова- ны для вычисления значений переменных horfact и vertfact, кото- рые с этого момента будут использоваться функциями IX и /У для вычисления всех пикселных координат X и Y по соответ- ствующим вещественным координатам х и у. Как пользователям, нам не нужно об этом беспокоиться. Нужно только иметь в виду, что функцию setprdim лучше всего вызывать как можно раньше, во всяком случае до обращения к функции initgr, а также до лю- бого другого использования переменных xjnax и yjnax. Функ- ция setprdim будет включена в модуль GRPACK.C. Можно задать вопрос, а почему бы просто не убрать старые значения 10.0 и 7.0 и вместо них всегда использовать новые значения xjnax и yjnax. Одна из причин заключается в том, что эти новые значе- ния не обеспечивают правильного соотношения размеров в изо- бражении на экране. Если вызвать функцию setprdim в графиче- ской программе, то окружность будет выглядеть на экране как эллипс и только вывод на печать приведет к отображению пра- вильной окружности. Поэтому, если в распоряжении нет мат- ричного принтера или в конкретном применении требуется со- блюдение точных пропорций на экране, а не на принтере, то в программу не следует вставлять обращение к функции setprdim и картинка на экране остается без изменений. Другая причина
по Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ заключается в том, что иногда желательно представлять все па- раметры в абсолютных единицах, скажем, в дюймах, а не в более абстрактных понятиях, таких как xjmax и yjnax, значения ко- торых зависят от типа графического адаптера. В соответствии с заголовком этого параграфа обсудим теперь необходимые действия для вывода окружности на печать. В про- грамме CIRPRINT.C предполагается, что функция для форми- рования изображения окружности уже существует и мы будем просто применять эту функцию. Основная программа устанав- ливает, каким может быть максимальный радиус R> чтобы ок- ружность помещалась внутри границ прямоугольника на экране. /* CIRPRINT.C: This program prints a large circle. */ /* Эта программа выводит на печать большую окружность*/ main() { extern float xmax, y_max; extern Int X max, Y max; float xC, yC, r; char ch; prlntf ("Принтер готов? (Y/N): *'); ch - getche(); /* "Is the printer ready? (Y/N):" */ lf(ch!-y &&ch!-'Y')exlt(1); setprdim(); /* Set print dimensions */ /* Установка размеров для печати */ xC - 0.5 * xmax; yC - 0.5 * ymax; /* Center of the circle */ /* Центр окружности */ г-хС>уС?уС : xC; /* Take г as large as possible */ /* Выбор наибольшего значения радиуса*/ InltgrO; clrcle(xC, yC, r); prlntgr(0, X__max, 0, Y max); endgrQ; > Функция с именем circle включена в состав модуля GRPACK.C, поэтому после компиляции CIRPRINT.C и компо- новки объектного модуля вместе с GRPACK.OBJ, получим пол- ную выполняемую программу CIRPRINT.EXE. Как следствие обращения, к функции setprdimi) эта програм- ма будет на экране изображать эллипс, а выводить на печать ок- ружность. Если измерить диаметр окружности, то обнаружим, что он будет равен либо 4.83, либо 2,78 дюйма (с двумя десятич- ными знаками), в зависимости от применяемого типа адаптера, монохромного или цветного. Эти числа равны значению пере- менной у_тах, что также видно из таблицы 4.1.
4.4. РАСПЕЧАТКА ОКРУЖНОСТИ В ВИДЕ ОКРУЖНОСТИ Ш Максимальное Таблица 4.1 значение координат (после вызова функции setprdim) X max Y max x_rnax-{X_max+1)/120.0 y_max-( Y_max+l) /72.0 Монохромный графический адаптер 719 347 6.00 дюйма 4.83 дюйма Цветной графический адаптер 639 199 5.33дюйма 2.78 дюйма Заметим, что если некоторая программа генерирует вывод графической информации только на экран и она используется в комбинации с программой PRGR в качестве "постпроцессора", тогда именно в первую программу (а не в PRGR) должно быть включено обращение к функции setprdim, чтобы в случае необ- ходимости распечатки графического результата обеспечить точ- ные размеры. Составим теперь функцию, используемую в CIRPRINT.C для вычерчивания окружности. Поскольку мы можем вычерчи- вать только отрезки прямых линий, то придется аппроксими- ровать окружность вписанным правильным многоугольником. Будем использовать полигон с 80 вершинами, что вполне доста- точно для изображения окружности. Следующая версия очень проста, но неэффективна. #include "math.h" circle(xC, уС, г) float хС, уС, г; /* Это только предварительная версия */ /* This Is only a preliminary version */ { intl; float delta, theta; delta - atan(1.0)/10; /* 80 * delta - 2 * pi */ move(xC+r, yC); for(i-1;i<-80;i++) { theta - i * delta; draw(xC + r * cos(theta), yC + r * sin(theta)); } } На большинстве микрокомпьютеров вычисления с плаваю- щей точкой выполняются довольно медленно. Очень хорошо бы- ло бы применять по возможности вычисления только с целыми
112 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ числами и, в частности, не вызывать математические функции atan, cos, sin, если этого можно избежать. С аналогичными рас- суждениями мы уже встречались в параграфе 2.2, где рассматри- вался алгоритм Брезенхама для вычерчивания прямых линий. Существует алгоритм Брезенхама также и для окружностей. Од- нако он подразумевает, что плотность пикселов одинакова как по горизонтали, так и по вертикали, что не имеет места в персо- нальных компьютерах. Но все-таки, применяя подобную мето- дику можно значительно ускорить процесс вычислений по срав- нению с предварительной версией. Мы не сможем полностью из- бавиться от вычислений с плавающей точкой, но попытаемся уменьшить их количество и не применять совсем математичес- кие функции atan, cos, sin. В приведенной ранее версии можно внести замену theta - 0.0; for (i—1; i<«80; i++) { theta - theta + delta; Само по себе это не улучшение, поскольку не похоже, что такой фрагмент будет работать намного быстрее, и, кроме того, проис- ходит накопление ошибки округления (последнее не представля- ет серьезной проблемы, особенно если использовать тип double вместо float для переменных delta и theta). Однако идея много- кратного увеличения переменной theta на величину delta очень полезна. Нам нужен не реальный угол в, а скорее значения сину- са и косинуса этого угла. Это означает, что можно воспользо- ваться известными тригонометрическими соотношениями cos(0 + д) = cos в cos д - sin в sin д sin(0 + д) = sin в cos д + cos в sin д Будем использовать то же самое значение delta, как и ранее, но здесь нам нужен только синус и косинус этого угла, которые являются константами и могут быть записаны непосредственно в их числовом виде. Начнем с угла theta = 0, поэтому косинус и синус этого начального угла равны 1 и 0 соответственно. Все ос- тальные значения косинусов и синусов могут быть вычислены по тригонометрическим формулам для суммы двух углов. Преобра- зуем вещественные горизонтальные и вертикальные расстояния
4.4. РАСПЕЧАТКА ОКРУЖНОСТИ В ВИДЕ ОКРУЖНОСТИ ИЗ (в дюймах) в целочисленные разности пикселных координат Н и V как можно раньше и будем непосредственно использовать функцию drawjine, для которой аргументы задаются в виде це- лочисленных пикселных координат. Следующее усовершенство- вание получается за счет выполнения остающихся операций над переменными с плавающей точкой только для одного квадранта, то есть только 20 шагов вместо 80. Координаты точек в осталь- ных трех квадрантах могут быть легко получены на основании координат точек в первом квадранте. circle(xC, yC, r) float xC, уС, г; /* Display a circle with given center and radius */ /* Изображение окружности по заданным центру */ /* и радиусу (улучшенная версия) */ { extern float horfact, vertfact; /* defined In GRPACK */ /* определены в GRPACK */ double cosd, sind, costh, slnth, cO, sO; Int I, XC, YC, H, HO, V, V0; /* delta - pi / 40 */ cosd - 0.996917333733120; /* cosd - cos delta */ sind - 0.078459095727844; /* sind - sin delta */ costh - 1.0; H - (IntXr * horfact + 0.5); sinth-0.0; V-0; /* theta - i * delta */ XC - IX(xC); YC - IY(yC); /* costh - cos theta */ for (i-1; K-20; i++) /* sinth - sin theta */ { cO - costh; sO - sinth; HO - H; V0 - V; costh - cO * cosd - sO * sind; sinth - sO * cosd + cO * sind; H - (IntXr * costh * horfact + 0.5); V - (IntXr * slnth * vertfact + 0.5); draw_line(XC+H0, YC+V0, XC+H, YC+V); draw_line(XC-H0, YC+V0, XC-H, YC+V); draw_line(XC+H0, YC-V0, XC+H, YC-V); draw_line(XC-H0, YC-V0, XC-H, YC-V); } } Эта улучшенная версия больше, чем предыдущая, поэтому можно было бы ожидать, что результирующая выполняемая про- грамма займет больший объем памяти. Но это совсем не так. Улучшенная версия не использует никаких стандартных мате- матических функций и поэтому, если они не используются в на- шей программе для каких-либо иных целей, результирующая выполняемая программа будет даже намного короче, чем преды- дущая версия. Экспериментально было получено сокращение примерно на 4000 байт! В связи с этим заметим, что в предыду-
114 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ щей версии была использована команда ^include "maXKH\ кото- рая теперь не нужна. Улучшенная версия функции circle будет включена в модуль GRPACK.C. 4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C В главе 3 и в данной главе был разработан ряд графических функций, которые объединены под общим названием модуля GRPACK.C. Их предшественником был модуль LINDRAW.C, приведенный в параграфе 2.10. Эти два модуля "совместимы снизу вверх", то есть все возможности, предлагаемые модулем LINDRAW.C также реализуются модулем GRPACK.C. Настало время представить все листинги программ из последнеге-модуля, предпочтительно в самой последней версии, поскольку замена отредактированной версии может привести к недоразумениям. По этой же причине в этот модуль включены и те функции, кото- рые будут еще обсуждаться в главе 5. Они помещены в конце тек- ста программ и отмечены в комментариях, но в данный момент они могут быть просто проигнорированы. Новыми функциями в модуле GRPACK.C являются описанные в главах 3 и 4 функции clearpage, pixlit, fill, pixfill, prchar, printgr, setgrcon, setprdim, circle Некоторые функции, например, initgrn dot были модифици- рованы, но их можно использовать таким же образом как и их предшественников в модуле LINDRAW.C. В функцию initmongr были добавлены некоторые свойства для предотвращения стира- ния графической страницы памяти, если она должна быть вызва- на вторично. Это свойство будет использовано в главе 6. Для помощи программистам, которые намерены только поль- зоваться функциями из модуля GRPACK.C, не интересуясь их реализацией, в приложении А приведен полный обзор всех функций. /* Эта программа несколько модифицирована, чтобы вместо */ /* компилятора Lattice С можно было применить компиляторы */ /* Microsoft С или Turbo С. Для этого в первой строке определения */ /* #define после данного комментария необходимо сделать */ /* замену и вместо символической константы LATTICEC вставить */ /* в эту строку константу MICROSOFT_C или TURBO_C, если */ /* предстоит работа с Турбо Си */
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 115 /* This program text has been updated to enable you to use either */ /* Microsoft С or Turbo С instead of Lattice C. */ /* If you are using Microsoft C, then just change LATTICE_C */ /* into MICROSOFT_C in the first #define-line following this */ /* comment. Similarly, replace LATTICE_C with TURBO_C In */ /* this line If you are using Turbo C: */ #define TURBO_C 1 /* In the preceding line, one of the three words */ /* LATTICE_C, MICROSOFT_C, TURBO_C */ /* must occur, depending on the compiler you are using. */ /* В предшествующей строке определения может появиться */ /* только одно из трех слов: */ /* LATTICE_C, MICROSOFT^, TURBO_C */ /* в зависимости от типа применяемого компилятора. */ /* GRPACK.C: An extended graphics package, by Leendert Ammeraal */ /* Расширенный графический пакет по Л. Аммералу */ #include<dos.h> #lnclude<signal.h> union REGS regs; unsigned int_STACK- 15000; int in_textmode-1, colorgr, X max, Y max, drawmode-1; static int d, c2, c3, old_vid_state, X1, Y1, offset; float x_max-10.0. y_max-7.0, horfact, vertfact; static char lastchar, gtable[12]-{53.45,46, 7,91,2,87,87,2, 3. 0, 0}, ttable[12]-{97, 80. 82, 15, 25, 6, 25, 25, 2, 13, 11, 12}, zeros[128]; /* implicitly Initialized to zero */ /* неявно инициализируется нулями */ int IX(x) float x; { return (intXx*horfactK).5); } int IY(y) float y; { return Y__max-{intXy*vertfact-K).5); } initgr() /* Initialize graphics */ /* Инициализация графического пакета */ { intbrfunO; if(!in_textmode) еггог("06ращение к функции initgr в графическом режиме"); /* "initgr is called in graphics mode" */ colorgr- iscolorQ; If (colorgr < 0) /* "Wrong display adapter" */ error("Неверный дисплейный адаптер"); onbreak(brfun); /* Set break trap */ /* Включение программы обработки прерывания */
116 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ if (colorgr) initcolgrQ; else initmongr(); setgrcon(colorgr); intextmodeK); horfact - X max/xmax; vertfact - Y max/ymax; } initcolgrf) /* Switch to graphics mode (color graphics) */ /* Переключение в графический режим (цветная графика) */ { regs.h.ah-15; /* Inquire current video state */ /* Запрос текущего состояния видеомонитора */ int86(0x10, &regs, &regs); oldvldstate - regs.h.al; regs.h.ah - 0; /* Set graphics mode */ /* Включение графического режима */ regs.h.al - 6; /* 640 x 200, black/white */ /* черно/белый, 640 x 200 */ Int86(0x10, &regs, &regs); } initmongr() /* Switch to graphics mode (monochrome graphics) */ /* Переключение в графический режим (монохромный адаптер)*/ { static lntflrstcall-1; int i; /* See Section 2.6 — См. параграф 2.6 */ /* outp(0x3BF, 3); Step 1, already dealt with in iscolor */ /* Шаг 1 уже был выполнен в функции 'iscolor' */ outp(0x3B8,0x82); /* Step 2 */ /* Шаг 2 */ for(i-0;i<12;i++) { outp(0x3B4,1); outp(0x3B5, gtable[i]); /* Step 3 */ /* Шаг 3 */ } if (firstcall) { firstcall-0; ciearpage(); } /*Step4*/ /*Шаг4 */ outp(6x3B8,0x8A); /* Step 5 */ /* Шаг 5 */ } setgrcon(colorgr) int colorgr; /* Set graphics constants */ /* Формирование графических констант*/ { if (colorgr) { X_max- 639; Y__max - 199; d-1;c2-80;c3-1; } else { X_max - 719; Y_max - 347; d-3;c2«90;c3-2; }
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 117 endgK) /* Wait until any key is hit and revert to text mode */ /* Ожидание нажатия любой клавиши, после чего */ /* возврат в текстовый режим */ { getch(); to_text(); } to_text() /* Revert to text mode */ /* Возврат в текстовый режим */ { if (intextmode) /* "endgr or totext is called in textmode" */ error ("функции endgr или totext вызваны в текстовом режиме"); if (colorgr) endcolgr(); else endmongr(); in_textmode - 1; onbreak(0); /* Restore default break interrupt handler */ /* Восстановление обработки прерываний по умолчанию */ } endcolgr() /* Revert to text mode (color graphics): */ /* Возврат в текстовый режим (цветная графика): */ { regs.h.ah - 0; regs.h.al - oldvidstate; int86(0x10, &regs, &regs); } endmongr() /* Revert to text mode (monochrome graphics): */ /* Возврат в текстовый режим (монохромная графика):*/ { inti, j; /* See Section 2.7 — См. параграф 2.7 */ outp(0x3B8,0): /* Step 1 */ /* Шаг 1 */ for(H); i<12; i++) { outp(0x3B4,1); /* Step 2 */ /* Шаг 2 */ outp(0x3B5, ttable[i]); } for (j-0; j<256; J++) /* Step 3 */ /* Шаг 3 */ poke1(0xB000, j « 4, "\40\7\40\7\40\7\40\7\40\7\40\7\40\7\40\7", 16); outp(0x3B8, 0x08); /* Step 4 */ /* Шаг 4 */ } static error(str) char *str; /* Display a message and terminate program execution */ /* Вывод сообщения и завершение выполнения программы */ { if (!in_textmode) to_text(); printf("%s\n*\ str); exit(1); } move(x, y) double x, y; /* Move the current point to (x, y); */ /* x and у are screen coordinates */ /* Перенос текущей точки в (х, у); */ /* х и у в экранных координатах */ { Х1 - IX(x); Y1 - IY(y); check(X1, Y1); }
118 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ draw(x, у) double x, у; /* Draw a line segment from the current point to (x, y) */ /* Вычерчивание отрезка прямой линии из текущей точки в точку (х, у) V { lntX2, Y2; Х2 - IX(x); Y2 - IY(y); check(X2, Y2); draw_line(X1,Y1,X2.Y2); X1-X2;Y1-Y2; draw_llne(X1, Y1, X2, Y2) Int X1. Y1, X2, Y2; /* Draw the line segment from (X1, Y1) to (X2, Y2); */ /* X1, Y1, X2, Y2 are pixel coordinates V /* Вычерчивание отрезка прямой из (Х1, Y1) в (Х2, Y2); */ /* Х1, Y1, Х2, Y2 — в пикселных координатах */ { int X, Y. Т, Е, dX, dY, denom, Xlnc - 1, Ylnc - 1, vertlonger-O, aux; checkbreak(); /* To make DOS check for console break */ /* Включение консольного прерывания ДОС V If (ln_textmode) error ("He в графическом режиме (нужен вызов lnitgr)M); /* "Not In graphics mode (call Initgr)" */ dX-X2-X1;dY-Y2-Y1; lf(dX<0){Xinc—1;dX—dX;} if(dY<0){Yinc—1; dY—dY;} If (d Y > dX) { vertlonger - 1; aux - dX; dX - d Y; d Y - aux; } denom-dX« 1; T-dY«1; E—dX;X-X1; Y-Y1; while (dX-->-0) { dot(X.Y); lf((E+-T)>0) { if (vertlonger) X ч- Xinc; else Y -f- Ylnc; E — denom; } If (vertlonger) Y -h- Yinc; else X -н- Xinc; } if (drawmode - - 0) dot(X1. Y1); } checkbreak() { charch; If (kbhlt()) { ch - getch(); kbhit(); ungetch(ch); } } dot(X, Y) Int X, Y; /* Light or darken a pixel */ /* Подсветка или гашение пиксела */ { int pattern; offset- 0x2000*(Y&d) + c2*(Y»c3) + (X»3);
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C И9 #ifdef LATTICE_C реек(0хВ800. offset, &lastchar, 1); #else lastchar - *(char far *X0xB8000000L + offset); #endif pattern - 0x80 »(X &7); if (drawmode - - 1) lastchar I- pattern; else if (drawmode - - -1) lastchar &- ("-pattern); else lastchar л- pattern; #ifdef LATTICE_C poke(0xB800, offset, &lastchar, 1); #else *(char far *X0xB8000000L + offset) - lastchar; #endlf} Int pixlitfX, Y) int X, Y; /* Запрос, не подсвечен ли пиксел (X, Y)*/ /* Inquire if pixel (X. V) is lit */ { int pattern; offset- 0x2000*(Y&c 1) + c2*(Y»c3) + (X»3); /* c1, c2 and c3 have been defined in initgr */ /* d, c2 и сЗ были определены в 'initgr' */ pattern - 0x80 »(X&7); #ifdef LATTICE_C peek(0xB800, offset. &lastchar, 1); #else lastchar - *(char far *X0xB8000000L + offset); #endif return ((lastchar & pattern)!- 0); } clearpage() /* Очистка экрана */ /* Clear the screen */ { int i, n; n-(colorgr 7 128:256); for (IK); i<n; I++) poke1(0xB800,1« 7, zeros, 128); } int iscolor() /* Find out which adapter is used */ /* Определение типа используемого адаптера */ { char chO, ch1, x; int86(0x11, &regs, &regs); if ((regs.x.ax & 0x30)!- 0x30) return 1; /* Color graphics */ /* Цветной графический адаптер */ outp(0x3BF, 3); /* Configuration switch, see Section 2.6 */ /* Переключатель конфигурации, см. параграф 2.6 */ peek1(0xB800,0. &ch0, 1); /* Try to read chO from screen memory */ /* Попытка прочитать chO из экранной памяти */
120 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ ch1 -chO~ OxFF; /* Find some value different from chO */ /* Определение некоторого числа, отличного от chO */ poke1(0xB800, 0, &ch1, 1); /* Try to write this into screen memory */ /* Попытка записать это число в экранную память */ реек1(0хВ800, 0, &х, 1); /* Try to read the latter value */ /* Попытка прочитать это последнее значение */ роке1(0хВ800, 0, &ch0, 1); /* Restore the old value chO */ /* Восстановление старого значения chO */ return (x - - ch 1 ? 0 : -1); /* Has written value been read? */ /* Было ли прочитано записанное число? */ } fatal() /* Draw a diagonal, then wait until a key is pressed, */ /* and finally revert to text mode. */ /* Вычерчивание диагонали на весь экран, затем */ /* ожидание нажатия на любую клавишу для */ /* окончательного перехода в текстовый режим. */ { drawjine(0, Y max, X max, 0); endgr(); } check(X, Y) int X, Y; /* If point (X, Y) lies outside the screen boundaries, */ /* then call fatal, print the wrong coordinates, and stop */ /* Если точка (X, Y) лежит вне пределов экрана, то вызов */ /* функции fatal, отображение неверных координат и останов */ { if(X<0 II Х>Х_тах II Y<0 II Y>Y__max) { fatal(); printf ("Точка вне экрана (X и Y в пикселных координатах):\п"); /* "Point outside screen (X and Y are pixel coordinates" */ prlnttf X - %d Y - %d\n", X, Y); printf("x - % 10.3f у - % 10.3f\n", X/horfact, (Y max-Y)/vertfact); exit(1); } } int brfun() /* Used by onbreak, to specif what to do */ /* with a console break */ /* Используется функцией onbreak для определения, */ /* что делать при консольном прерывании */ { if (!in_textmode) to_text(); exit(0); /* Before exit, return to text mode! */ /* Перед выходом возврат в текстовый режим ! */ return 1; /* This is only a formality */ /* Это только формальность */ }
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 121 fill(x, у) float х, у; Л Fill a closed area, starting in point (x, y) */ /* x and у are screen coordinates, In inches */ /* Заполнение замкнутой области, начиная с точки */ /* (х.у). гАе х и у — экранные координаты в дюймах */ { pixfill(IX(x), IY(y)); } pixfill(X, Y) int X, Y; /* Fill a closed region, starting in point (X, Y) */ /* Заполнение области, начиная с точки (X, У) */ { int Xleft, Xright, YY, i, dm; char ones4)xFF; dm-drawmode; drawmode-1; check(X, Y); checkbreak(); /* To make DOS check for console break */ /* Установка в ДОС консольного прерывания */ /* Light as many pixels as possible on line Y, */ /* and determine Xleft and Xright: */ /* Подсветка максимального количества пикселов */ /* в строке Y и определение значений Xleft и Xright: */ Xleft-Xright-X; while (pixlit(Xleft. Y) — 0 && Xleft >- 0) - { if (lastchar--0) { #ifdef LATTICE_C poke(0xB800. offset, &ones, 1); #else *(char far *X0xB8000000L + offset) - ones; #endif Xleft &-0xFFF8; if (Xright--X) Xright I-7; } else dot(Xleft. Y); Xleft- -; } Xright-н-; while (pixlit(Xright, Y) - - 0 && Xright <- X_max) { if (lastchar - - 0) { #ifdef LATTICE_C poke(0xB800, offset, &ones, 1); #else *(char far *X0xB8000000L + offset) - ones; #endif Xright 1-7; } else dot(Xright, Y); Xright-н-; }
122 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ /* Recursive calls of plxflll for at most */ /* two remote points: */ /* Рекурсивные обращения к функции plxflll */ /* для двух наиболее удаленных точек: */ X - (Xleft+Xright)» 1; for(l—1;i<-1;H-2) { YY-Y+I; while (plxllt(X, YY) - - 0) YY 4-1; YY-(Y+YY)»1; If (plxlit(X, YY) - - 0) pixflll(X, YY); } /* Recursive calls for all dark pixels next to line Y */ /* (with X values between XIeft and Xright): */ /* Рекурсивные вызовы для всех темных пикселов, */ /* находящихся рядом со строкой Y */ /* (при значениях X между Xleft и Xright): */ for (YY-Y-1; YY<-Y+1; YY-h-2) { X-Xleft+1; while (X < Xright) { l-plxllt(X, YY); If (lastchar - - ones) X I - 7; else if (I - - 0) pixfill(X, YY); X++; } } drawmode-dm; } prchar(ch) char ch; /* Посылка байта ch на параллельный порт принтера*/ { regs.x.dx-O; _ /* Выбор принтера */ regs.h.ah-О; /* Посылка байта из регистра AL на принтер */ regs.h.al-ch; /■* Байт, посылаемый на принтер */ Int86(0x17, &regs, &regs); } printgr(Xlo, Xhl, Ylo, Yhl) Int Xlo, Xhi, Ylo. Yhl; /* Print contents of rectangle on matrix printer */ /* Вывод содержимого прямоугольника на */ /* латричный принтер */ { Intnl. n2. ncols, I, X, Y, val; prchar(27); prcha^'V); /* Line spacing 7/72 inch . */ /* Расстояние между строками 7/72 дюйма */ ncols-Xhl-Xlo+1; n1-ncols%256; n2-ncols/256; for(l-Ylo;i<-Yhl;i-»-7) { checkbreak(); /* To make DOS check for console break */ /* Установка проверки консольного прерывания ДОС */ prchar(27); prcflarfL'); prchar(n1); prchar(n2); for(X-Xlo;X<-Xhl;X++) { val-O;
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 123 for(Y-i;Y<l+7;Y++) { val «- 1; val I - (Y>Yhi ? 0 : pixlit(X, Y)); } prchar(val); } prcharfW); } prchar(27); prcharC®'); setprdim() /* This function sets xmax and ymax such that graphics */ /* results will eventually be printed with correct */ /* dimensions, both horizontally and vertically. */ /* Эта функция устанавливает значения xmax и ymax V /* с таким расчетом, чтобы графическое изображение */ /* было выведено с точным соблюдением размеров */ /* как по горизонтали, так и по вертикали. */ { extern float x_max, ymax; extern int X max, Y max; setgrcon(iscolor()); x_max - (X__max + 1)/120.0; y_max-(Y__max + 1)/72.0; } /* The following program text discussed in Chapter 5. */ /* Текст следующей программы обсуждается в главе 5.*/ #define NASCII 256 charchlist[NASCIII11}- { {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}, /**/ {0}. /*!*/ {0x10,0x38,0x38,0x38,0x10,0x10,0x10, 0,0x10}, /*"*/ {0x48, 0x48, 0x48}, ' /*#*/ {0x24, 0x24, 0x64, OxFE, 0x44, OxFE, 0x4C, 0x48, 0x48}, /*$*/ {0x10, 0х7С, 0xD0, 0xD0, 0x7C, 0x16, 0x16, 0х7С, 0x10}, /*%*/{0хС2, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x86}, /*&*/ {0x30, 0x48, 0x48, 0x30, 0x50, 0x92, 0х8А, 0х8С, 0x72}, /*'*/ {0x18,0x18,0x18,0x10}, /*(*/ {0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x08}, /*)*/ {0x40, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10. 0x20, 0x40}, /***/ { 0, 0x82, 0x44, 0x28, OxFE, 0x28, 0x44, 0x82}, /*+*/ { 0, 0x10, 0x10, 0x10, OxFE, 0x10, 0x10, 0x10}, /*.*/ {0. 0, 0, 0, 0, 0, 0, 0x30. 0x30, 0x10, 0x20}, /*-*/ {0, 0, 0, 0, OxFE}, /*.*/ {0,0,0,0,0,0, 0,0x30.0x30}, /*/*/ { 0. 0x02. 0x04, 0x08, 0x10, 0x20, 0x40. 0x80},
124 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ /*0*/ {0x38, ОхбС. 0x44, 0x44, 0x44, 0x44, 0x44, ОхбС, 0x38}, /*1*/ {0x10, 0x30. 0x50, 0x10, 0x10, 0x10, 0x10. 0x10, 0x38}, /*2*/ {0х7С, ОхСб, 0x02, 0x06, ОхОС. 0x18. 0x30. 0x60, OxFE}, /*3V {0х7С, ОхСб, 0x02. 0x06. ОхОС. 0x06. 0x02. ОхСб. 0х7С}. /М*/ {0x04. ОхОС. 0х1С. 0x34. 0x64. 0хС4, OxFE, 0x04, 0x04}. /*5*/ {OxFE. 0x80. 0x80. OxFC, 0x06, 0x02, 0x02, ОхСб, 0х7С}, /*6*/ {Ох7С, ОхСб. 0x80, OxFC, ОхСб, 0x82, 0x82. ОхСб. Ох7С}. /*7*/ {OxFE, 0x02, 0x06, ОхОС, 0x18. 0x30. 0x60. ОхСО. 0x80}. /*8*/ {Ох7С. ОхСб. 0x82, ОхСб, 0х7С, ОхСб, 0x82, ОхСб, Ох7С}, /*9*/ {Ох7С, ОхСб, 0x82, ОхС2, 0х7Е, 0x02, 0x02, 0x06. 0х7С}. /*:*/ { 0,0x30,0x30.0,0,0,0.0x30.0x30}, /*;*/ {0,0.0x30.0x30,0,0, 0,0x30.0x30.0x10.0x20}, /*<*/ {0x04. 0x08. 0x10, 0x20. 0x40. 0x20. 0x10. 0x08. 0x04}. /*-*/ {0. 0. О, 0, OxFC. 0. 0. OxFC}, /*>*/ {0x40. 0x20. 0x10, 0x08, 0x04, 0x08. 0x10, 0x20. 0x40}. /*?*/ {0x78, OxCC, 0x84, ОхОС, 0x18. 0x10, 0x10. 0. 0x10}. /*@*/ {0х7С. ОхСб. 0х8Е. 0x92. 0x92, 0x92, 0х8С, ОхСО. Ох7С}, /*А*/ {0x10, 0x38. ОхбС. ОхСб. 0x82. 0x82. OxFE. 0x82. 0x82}. /*BV {OxFC, 0x86, 0x82, 0x86, OxFC. 0x86. 0x82, 0x86, OxFC}, /*С*/ {0х7С, ОхСб, 0x80, 0x80, 0x80. 0x80, 0x80. ОхСб. 0х7С}. /*D*/ {OxFC. 0x86, 0x82, 0x82, 0x82, 0x82, 0x82. 0x86. OxFC}. /*EV {OxFE. 0x80. 0x80. 0x80. 0xF8. 0x80. 0x80. 0x80. OxFE}. /*F*/ {OxFE. 0x80. 0x80, 0x80, OxFC. 0x80, 0x80. 0x80, 0x80}, /*G*/ {0x7C. ОхСб. 0x82, 0x80, 0x80. 0x8E. 0x82. ОхСб. Ох7С}. /*H*/ {0x82. 0x82. 0x82. 0x82, OxFE, 0x82, 0x82, 0x82. 0x82}. /*l*/ {0x38, 0x10, 0x10, 0x10, 0x10, 0x10. 0x10. 0x10. 0x38}. /*J*/ {0x02. 0x02. 0x02. 0x02. 0x02, 0x02, 0x02, ОхСб, Ox7C}, /*KV {0x86, 0x8C, 0x98, OxBO, OxEO, OxBO, 0x98, 0x8C, 0x86}, /*L*/ {0x80. 0x80. 0x80. 0x80. 0x80. 0x80, 0x80. 0x80. OxFE}. /*M*/{0x82. ОхСб. OxEE. OxBA, 0x92. 0x82. 0x82. 0x82. 0x82}. /*N*/ {0x82. 0xC2, 0xE2, 0xA2. 0xB2. 0x9A. 0x8E. 0x86. 0x82}. /*0*/ {Ox7C. ОхСб. 0x82, 0x82. 0x82, 0x82, 0x82. ОхСб, Ох7С}, /*P*/ {OxFC, 0x86, 0x82, 0x86, OxFC, 0x80, 0x80, 0x80, 0x80}, /*Q*/ {0x7C, ОхСб, 0x82, 0x82, 0x82, 0x82, 0x92, 0xD6. 0x7C, 0x08. 0x08}. /*R*/ {OxFC, 0x86, 0x82, 0x86, OxFC, 0x90, 0x98, 0x8C, 0x86}, /*S*/ {0x7C. ОхСб, 0x80, OxCO, 0x7C, 0x06, 0x02, ОхСб, 0x7C}, /*T*/ {OxFE, 0x10, 0x10, 0x10, 0x10, 0x10. 0x10. 0x10, 0x10}. /*U*/ {0x82. 0x82, 0x82, 0x82. 0x82, 0x82, 0x82. ОхСб, Ох7С}, /*VV {0x82. 0x82. 0x82. ОхСб. 0x44. ОхбС. 0x28. 0x38. 0x10}. /*W*/ {0x82. 0x82. 0x82, 0x92, 0x92. OxBA. OxEE. ОхбС, 0x44}, /*X*/ {ОхСб, 0x44, ОхбС, 0x28, 0x38, 0x28, ОхбС, 0x44, ОхСб}, /*YV {0x82, 0x82, ОхСб, ОхбС, 0x38, 0x10. 0x10. 0x10. 0x10}. /*ZV {OxFE, 0x04, ОхОС, 0x18, 0x10. 0x30, 0x60, 0x40, OxFE}, /*[*/ {0x7C, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7C}, /*W { 0, 0x80, 0x40, 0x20, 0x10. 0x08. 0x04. 0x02}. /*]*/ {0x78, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x78}, /*л*/ {0x10, 0x28, 0x44, 0x82}, /*_*/ {0,0, 0,0, 0,0, 0,0, OxFE},
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 125 /*'*/ {0x40,0x20,0x10,0x08}, /*а*/ {0, 0, 0, 0х7С, 0x06, Ох7Е, 0хС2, 0хС2, 0х7Е}, /*Ь*/ {0x80, 0x80, 0x80, OxFC, 0x86, 0x82, 0x82. 0x86, OxFC}, /*с*/ {0, 0, 0, Ох7С, ОхСб, 0x80, 0x80, ОхСб, 0х7С}, /*d*/ {0x04, 0x04, 0x04, 0х7С, 0хС4, 0x84, 0x84, 0хС4, 0х7С}, /*е*/ {0, 0, 0, Ох7С, ОхСб, OxFE, 0x80, ОхСО, 0х7С}, М*/ {0x1 С, 0x30, 0x20, OxFC, 0x20, 0x20, 0x20, 0x20, 0x20}, /*д*/ {0, 0, 0, 0х7А, ОхСЕ, 0x82, 0x82, ОхС2, 0х7Е. 0x06, 0х7С}, /*h*/ {0x80, 0x80, 0x80, OxFC, ОхСб, 0x82, 0x82, 0x82, 0x82}, /*i*/ { 0,0x30, 0,0x30,0x10,0x10,0x10,0x10,0x38}, /*J*/ { 0, ОхОС, 0, ОхОС, 0x04, 0x04, 0x04, 0x04, 0x04, ОхСС. 0x78}. /*к*/ {0x40, 0x40, 0x40, 0x46, 0х4С, 0x78, 0x58, 0х4С, 0x46}, /*!*/ {0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38}, /*т*/ {О, О, О, ОхЕС, 0x92, 0x92, 0x92, 0x92, 0x92}, /*п*/ {О, О, О, ОхВС, ОхСб, 0x82, 0x82, 0x82. 0x82}, /*о*/ {0, 0, 0, 0х7С, ОхСб, 0x82, 0x82, ОхСб, 0х7С}, /*р*/ {О, О, О, OxFC, 0x86, 0x82, 0x82, 0x86, OxFC. 0x80, 0x80}, /*q*/ {0, 0, 0, 0х7С, 0хС4, 0x84, 0x84, 0хС4, Ох7С, 0x04, 0x06}, /*г*/ {0,0, 0. ОхВС, ОхЕб, 0x80, 0x80, 0x80, 0x80}, /*s*/ { 0, 0, 0, Ох7С, ОхСО, Ох7С, 0x06, 0x06, 0х7С}. /*\*1 { 0, 0x40, 0x40. OxFO. 0x40. 0x40. 0x40. 0x66. ОхЗС}, Ли*/ {О, О, О, 0x84, 0x84, 0x84, 0x84, 0хС4, 0х7Е}, /*v*/ {О, О, О, 0x82, 0x82, ОхСб, ОхбС, 0x38, 0x10}, /*w*/ {О, О, О, 0x82, 0x82, 0x92, ОхВА, ОхЕЕ, 0x44}, /*х*/ {0, 0, 0, ОхСб, ОхбС, 0x28, 0x38, ОхбС, ОхСб}, /*у*/ {0. 0. 0. 0x82, 0x82, 0x82, 0x82, ОхСб, 0х7Е, 0x06, Ох7С}, /*z*l {О, О, О, OxFE, ОхОС, 0x18, 0x30, 0x60, OxFE}, /*{*/ {0x10, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x10}, /* Г*/ {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, /*}*/ {0x20, 0x10, 0x10, 0x10, 0x08, 0x10, 0x10, 0x10, 0x20}, /*~*/ {0, 0, 0. 0x60. 0x92, ОхОС}. {0}, /*А*/ {ОхЗЕ, 0x42, 0x82, 0x82, 0x82, OxFE, 0x82, 0x82. 0x82}. /*Б*/ {OxFC. 0x80. 0x80. OxFC. 0x82. 0x82. 0x82. 0x82, OxFC}, /*В*/ {OxFC, 0x82, 0x82, 0x84, OxFC. 0x82, 0x82, 0x82, OxFC}, /*Г*/ {OxFC, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, /*Д*/ {0x1 С, 0x24, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, OxFE. 0x82}, /*E*/ {OxFC, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, OxFC}, /*Ж*/{0х92, 0x92, 0x54, 0x38, 0x38, 0x54, 0x92, 0x92, 0x92}, 1*3*/ {ОхЗС, 0x42, 0x02, 0x04, ОхЗС, 0x02, 0x02. 0x82, Ox7C}, /*\A*/ {0x82, 0x82, 0x86, 0x8E, 0x9A, 0xB2, OxE2, 0xC2, 0x82}, /*Й*/ {ОхВА, 0x82, 0x86, 0x8E, 0x9A, OxB2, 0xE2, OxC2, 0x82}, /*K*/ {0x84, 0x84, 0x84, 0x88, OxFO, 0x88, 0x84, 0x82, 0x82}, /*Л*/ {0x1 E, 0x22, 0x42, 0x42, 0x42, 0x42, 0x42, OxC2, OxC2}, /*M*/{0x82, ОхСб, ОхЕЕ, ОхВА, 0x92, 0x82, 0x82, 0x82, 0x82}, /*H*/ {0x82. 0x82. 0x82. 0x82. OxFE. 0x82, 0x82, 0x82, 0x82}, 1*0*/ {0x7C, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, Ox7C}, /*П*/ {OxFE, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82},
126 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ /*Р*/ {OxFC. 0x82, 0x82, 0x82. 0x82, OxFC, 0x80, 0x80, 0x80}, /*С*/ {0х7С, 0x82, 0x80. 0x80, 0x80, 0x80, 0x80, 0x82, 0х7С}, /*J*/ {OxFE, 0x10, 0x10. 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, /*У*/ {0x82, 0x82, 0x82, 0x82, 0x82, 0х7Е, 0x02, 0x02, 0х7С}, /*Ф*/{0хЮ, 0х7С. 0x92. 0x92, 0x92, 0x92, 0х7С, 0x10, 0x10}, /*Х*/ {6x82, ОхСб, ОхбС. 0x38. 0x10. 0x28. ОхбС. ОхСб, 0x82}. /*Ц*/ {0x84. 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0х7Е, 0x06}, /*Ч*/ {0x82, 0x82. 0x82. 0x82. 0x82, 0х7Е, 0x02, 0x02, 0x02}, /*Ш*/ {0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0х7Е}, /*Щ*/ {0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,0x92, OxFE, 0x06}, /*Ъ*1 {ОхСО, ОхСО, 0x40, 0х7С, 0x42, 0x42, 0x42, 0x42, 0х7С}, /*Ы*/{0х82, 0x82, 0x82, 0xF2, 0x8A, 0x8A, 0x8A, 0x8A, 0xF2}, ЛЬ*/ {0x80, 0x80, 0x80, OxFC, 0x82, 0x82, 0x82, 0x82, OxFC}, 1*3*1 {0х7С, 0x82, 0x02, 0x02, ОхЗЕ, 0x02, 0x02, 0x82, 0х7С}, /*Ю*/ {Ох9С, ОхА2, 0хА2, ОхА2, ОхА2, ОхЕ2, 0хА2, ОхА2, 0х9С}, /*9\*1 {ОхЗЕ, 0x42. 0x42. 0x42. 0x42. ОхЗЕ, 0x22, 0x42, 0x82}, /*а*/ {0, 0, 0, 0x78, 0x04, Ох7С, 0x84, 0x84, 0х7А}, /*б*/ {О, О, 0x78, 0x80, 0xF8, 0x84, 0x84, 0x84, 0x78}, /*■*/ {0, 0. О, 0xF8. 0x84, 0xF8. 0x84. 0x84. 0xF8}, /*г*/ {0. 0. О, OxFC. 0x80, 0x80, 0x80. 0x80, 0x80}, /*д*/ {0, 0, 0x78, 0x04, 0х7С, 0x84, 0x84, 0x84, 0x78}, /*е*/ {0, 0, 0, 0x78, 0x84, 0x84, 0xF8, 0x80, Ох7С}, /*ж*/ {О, О, О, 0x92, 0x54, 0x38, 0x38, 0x54, 0x92}, /*з*/ {0, 0. О, 0x78. 0x84, 0x18. 0x04. 0x84, 0x78}, /*и*/ {0, 0, 0, 0x84. 0х8С, 0x94. 0хА4. 0хС4, 0x84}, /*й*/ {0, 0, 0x30, 0x84, 0х8С, 0x94, 0хА4, 0хС4, 0x84}, /*к*/ {О, О, О, 0x84, 0x88, OxFO, 0x88, 0x84, 0x82}, /*п*/ {О, О, О, ОхЗС, 0x44, 0x44, 0x44, 0x44, 0x84}, /*м*/ {0, 0, 0, 0x82, ОхСб, ОхАА. 0x92, 0x82, 0x82}, /*н*/ {0, 0, 0, 0x84, 0x84, 0x84. OxFC, 0x84, 0x84}, 1*о*/ {О, О, О, 0x78, 0x84, 0x84, 0x84, 0x84, 0x78}, /*п*/ {О, О, О, OxFC, 0x84, 0x84, 0x84, 0x84, 0x84}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}. {0}, {0}, {0}, {0}, {0}, {0}, {0}, /*р*/ {0. 0. О, 0xF8, 0x84, 0x84, 0x84, 0x84, 0xF8, 0x80, 0x80}, /*cV {0, 0, 0, 0x78, 0x84, 0x80, 0x80, 0x84, 0x78}, /*i*l {0, 0, 0. OxFE. 0x10, 0x10, 0x10, 0x10, 0x10}, /*у*/ №. 0. 0. 0x84, 0x84, 0x84, 0x84, 0x84, 0х7С, 0x04, 0x78}, /*ф*/ {0,0, 0x10. 0х7С. 0x92, 0x92,0x92, 0x92, Ох7С, 0x10, 0x10}, /*х*/ {0, 0, 0, 0x84, 0x48, 0x30, 0x30, 0x48, 0x84}, /*ц*/ {0, 0, 0, 0x84, 0x84, 0x84, 0x84, 0x84, 0х7Е, 0x06}, /*ч*/ {О, О, О, 0x84, 0x84, 0x84, Ох7С, 0x04, 0x04}, /*ш*/ {О, О, О, 0x92, 0x92, 0x92, 0x92, 0x92, 0х7Е}, /*щ*/ {0, 0, 0. 0x92. 0x92. 0x92, 0x92, 0x92, 0х7Е, 0x03},
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 127 /*•*/ {0. 0, 0, ОхСО, 0x40. 0x78, 0x44, 0x44, 0x78}, /*ы*/ {0, 0. О, 0x82, 0x82, 0xF2, 0x8A, 0x8A, OxF2}, Ль*/ {О, О, О, 0x80, 0x80, 0xF8, 0x84, 0x84, 0xF8}, /*э*/ {0. О, 0. 0x78. 0x84, 0x04, ОхЗС, 0x84, 0x78}, /*ю*/ {0, 0, 0. 0x98. 0хА4, 0хА4. 0хЕ4, 0хА4. 0x98}, /*я*/ {0, 0, 0. ОхЗС, 0x44,6x44, ОхЗС, 0x44, 0x84}. {0}. {0}. {0}. {0}, {0}. {0}, {0}, {0}. {0}. {0}. {0}, {0}, {0}, {0}, {0}, {0}}; textXY(X, Y, str) int X, Y; char *str; /* Display string str in graphics mode, */ /* starting at point (X, Y). */ /* Отображение текстовой строки str в гра- */ /* фическом режиме, начиная с точки (X, Y). */ { char *p; int offset, i, j. len, hpos, vpos; len-strlen(str); hpos - X»3; check(8*(hpos+len)-1, Y+10); for(i-0;i<len;l++) { p-chlistfstrfjj; forG-0;j<11;j++) { vpos-Y+j; offset- 0x2000*(vpos&c1) + c2*(vpos»c3) + hpos + i; poke1(0xB800, offset, (char *Xp+J). 1): } } text(str) char *str; /* Display string str, starting at */ /* the current point (X1.Y1). */ /* Отображение текстовой строки str, */ /* начиная с текущей точки (Х1, Y1). */ { textXYpd, Y1,str); Х1 4-strlen(str) * 8; check(X1,Y1); } imove(X, Y) int X, Y; /* Let (X, Y) be the new current point */ /* Точка (X, Y) будет теперь новой текущей точкой */ { check(X,Y);X1-X;Y1-Y; } idraw(X, Y) Int X, Y; /* Draw a line segment from the current point to (X, Y) */ /* Вычерчивание отрезка прямой линии из текущей */ /* точки в точку (X, Y). */
128 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ { check(X, Y); draw_line(X1,Y1,X,Y); Х1-Х; Y1-Y; } circle(xC, yC, R) float xC, yC, R; /* Display a circle with given center and radius */ /* Изображение окружности по заданным центру и радиусу */ { double cosd, sind, costh, slnth, cO, sO; Int I, XC, YC, H, HO, V, VO; /* delta - pi / 40 */ cosd - 0.996917333733120; /* cosd - cos delta */ sind - 0.073459095727844; /* sind - sin delta */ costh - 1.0; H - (intXR * horfact + 0.5); slnth-0.0; V-0; /* costh - cos theta, slnth - sin theta */ /* theta - i * delta */ XC - IX(xC); YC - IY(yC); If (drawmode - - 0) { dot(XC+H, YC); dot(XC-H, YC); } for(i-1;i<-20;i++) { cO - costh; sO - sinth; HO - H; V0 - V; costh - cO * cosd - sO * sind; sinth - sO * cosd + cO * sind; H - (intXR * costh * horfact + 0.5); V - (intXR * sinth * vertfact + 0.5); draw_line(XC+H0, YC+V0, XC+H, YC+V); draw_line(XC-H0, YC+V0, XC-H. YC+V); draw_line(XC+H0, YC-V0, XC+H, YC-V); draw_line(XC-H0, YC-V0, XC-H, YC-V); } if (drawmode - - 0) { dot(XC, YC+V)>dot(XC, YC-V); } } #ifndef LATTICE_C /* The following applies to Microsoft С and Turbo C: */ /* Эта часть применима для Майкрософт Си и Турбо Си: */ poke1(segment, offset, buffer, n) unsigned segment, offset, n; char *buffer; { unsigned srcseg, srcoff; char far *p - (char far *)buffer; srcseg - FP_SEG(p); srcoff- FP_OFF(p); movedata(srcseg, srcoff, segment, offset, n); peek1(segment, offset, buffer, n) unsigned segment, offset, n; char *buffer; { unsigned destseg, destoff; char far *p - (char far *)buffer; destseg - FP_SEG(p); destoff-FP_OFF(p);
4.5. ТЕКСТЫ ПРОГРАММ МОДУЛЯ GRPACK.C 129 movedata(segment, offset, destseg, destoff. n); } #endif #ifdefMICROSOFT_C int onbreak(brfun) int (*brfunX); { if (brfun - - 0) signal(SIGINT, SIG_DFL); else sjgnal(SIGINT, brfun); } #endif #ifdef TURBO_C int onbreak(brfun) int (*brfunX); { ctrlbrk(brfun); } #endif #ifdef LATTICE_C poke1(segment, offset, buffer, n) unsigned segment, offset, n; char *buffer; { poke(segment, offset, buffer, n); } peek1(segment, offset, buffer, n) unsigned segment, offset, n; char *buffer, { peek(segment, offset, buffer, n); }• #endif 5—275
Глава 5 ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ 5.1. БИТОВЫЕ КОМБИНАЦИИ ДЛЯ СИМВОЛОВ При выводе графической информации очень часто встреча- ются с необходимостью включения текстовых надписей. В персо- нальных компьютерах символы обычно генерируются аппарат- ным способом, с помощью так называемого генератора символов. Но такой генератор не будет работать, когда система находится в графическом режиме. Теперь, когда мы можем подсвечивать и гасить пикселы на экране в любом месте, в принципе ясно, что можно самостоятельно сформировать символы на экране. Как это часто бывает в программировании, понимание в принципе, что нечто может быть сделано, и фактическая реализация этого — совершенно разные вещи. Как и при ручном выполнении над- писей, мы должны иметь свободу в выборе формы каждого сим- вола или, другими словами, мы можем разработать свой собст- венный шрифт. Есть три способа вычерчивания символов и ото- бражения их на экране: 1) путем вычерчивания нескольких отрезков прямых линий (этот способ применяется в перьевых графопостроителях); 2) выбором отдельных пикселов и установкой в единицу соот- ветствующих бит в экранной памяти; 3) копированием строк пикселов, из которых составляются символы. Хотя способ (3) и менее общий, чем (1) и (2), мы будем его использовать вследствие его эффективности как по занимаемой памяти, так и по затратам времени. Каждый символ, включая
5.1. БИТОВЫЕ КОМБИНАЦИИ ДЛЯ СИМВОЛОВ 131 78 ее...е*. .§§§**.. Рис. 5.1. Буква С маленький пробел между двумя соседними символами, будет иметь по ширине восемь пикселов. Это дает нам возможность ко- пировать по одному байту для каждой строки пикселов из неко- торого массива в экранную память и вполне очевидно, что копи- рование байтов более эффективно, чем копирование битов. При- мем два условия, диктуемых этим способом: во-первых, ширина каждого символа составляет восемь пикселов (или число, крат- ное восьми) и, во-вторых, символы в экранной памяти могут на- чинаться только на границах байта. Для каждого символа будем использовать одиннадцать строк, причем последние две строки будут использоваться только в исключительных случаях для вы- носных элементов, например, для нижней части строчной буквы V. Таким образом данные для каждого символа записываются в массив из одиннадцати элементов. На рис. 5.1. показано, что буква С вписывается в сетку 11x8 пикселов. Одиннадцать строк могут быть считаны как числа в двоичном представлении с буквой @ для 1 и точкой для 0. Например верх- няя строка может рассматриваться как 0111 1100 = 0х7С=124 Массив данных для буквы С содержит последовательность чисел: 124, 198, 128, 128, 128, 128, 128, 198, 124,0,0 Хотя эти элементы массива выражаются в виде чисел, факти- чески они имеют тип char. Поскольку кроме буквы С есть еще очень много других символов, будем использовать двухмерный массив, то есть массив, элементы которого являются в свою оче- редь массивами и инициализируются следующим образом: 5**
132 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ static char chlist[128l11]- { { • }. { }. { ' •} }; где i-я последовательность в виде { } представляет собой массив данных длясимволов с кодами ASCII i 0 = 0,..., 127). Объем памяти, требующейся для хранения обра- зов всех символов составляет теперь всего 128 х 11 = 1408 байтов (или 256 * 11 = 2816 байтов с учетом русского шрифта). 5.2. ФУНКЦИЯ ДЛЯ ВЫВОДА ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ Предположим, что массив chlist уже доступен (не будем пока беспокоиться, как получить все 128 или 256 массивов данных, к этому скоро придем). Тогда сравнительно просто отобразить на экране строку символов ASCII, начиная с заданной позиции. Эта начальная позиция будет располагаться в левом верхнем углу первого символа в строке и мы составим две функции textXY и text, которые различаются способом задания этой позиции. 1. Функция textX Y принимает целочисленные пикселные ко- ординаты X и Y начальной позиции в качестве первого и второго аргументов, а третьим аргументом будет строка, предназначенная для отображения. 2. Функция text использует "текущую позицию" точно также, как она используется в функции draw. В модуле GRPACK.C эта точка имеет координаты XI, Y\. После отображения строки текущая позиция переносится в верхний левый угол предполагаемого нового символа, немедленно следующего после последнего символа в заданной строке. Заданная стро- ка является единственным аргументом функции text. В большинстве случаев будем использовать функцию text (которая в свою очередь вызывает функцию textXY). Очень хорошим свойством функции text является то, что в случае не- посредственного следования одной строки за другой необходимо
5.2. ФУНКЦИЯ ДЛЯ ВЫВОДА ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ 133 просто указывать два последовательных обращения к этой функ- ции. Например, если строковая переменная str содержит ваше имя, и начальная позиция должна располагаться в точке ОсО, >0), то можно воспользоваться последовательностью операторов: move(xO, yO); text("Moe имя - "); text(str); Заметим, что значения координат хОу >0 представляются в виде вещественных чисел, выраженных в дюймах. В связи с ото- бражением текста иногда более удобно пользоваться пикселны- ми координатами, поскольку известно, что каждый символ ото- бражается в прямоугольнике 11 * 8 пикселов. Поэтому явно чув- ствуется необходимость в функции move, но с пикселными координатами в качестве аргументов вместо экранных коорди- нат. Напишем такую функцию и, поскольку она имеет целочис- ленные аргументы, назовем ее imove. С целью симметрии жела- тельно иметь целочисленный эквивалент idraw для известной функции draw. Ниже приводятся тексты новых функций: textXY(X, Y, str) int X. Y; char *str; /* Display string str in graphics mode, starting at point (X, Y). */ /* Отображение текстовой строки str в графическом режиме */ /* начиная с точки (X, Y). V { char *p; int offset, i, j. len, hpos, vpos; len-strlen(str); hpos - X»3; check(8*(hpos+len)-1, Y+10); for(i-0, i<len; i++) { p-chlist[str[i]]; for(j-0;j<11;j++) { vpos-Y+j; offset- 0x2000*(vpos&c1) + c2*(vpos»c3) + hpos + I; poke(0xB800. offset, (char *Xp+J). 1): } } } text(str) char *str; /* Display string str, starting at the current point (X1, Y1). */ /* Отображение текстовой строки str, */ /* начиная с текущей точки (Х1, Y1). */ { textXY(X1, Y1,str); Х1 -н- strlen(str) * 8; check(X1,Y1); }
134 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ imove(X, Y) int X, Y; /* Let (X, Y) be the new current point */ /* Точка (X, Y) будет теперь новой текущей точкой */ { check(X, Y); X1-X; Y1-Y; } idraw(X, Y) int X, Y; /* Draw a line segment from the current point to (X, Y) */ /* Вычерчивание отрезка прямой линии из текущей точки */ /*вточку(Х,У). */ { check(X, Y); drawJlne(X1,Y1.X.Y); X1-X;Y1-Y; } Эти функции уже были включены в модуль GRPACK.C, при- веденный в конце главы 4. Функция text обращается к функции textXY, после чего изменяются координаты XI и У1 "текущей точки". Кроме отображения текста эта функция изменяет значе- ния координат текущей точки (XI, У1), как и в функции move, a в этой последней функции проверяется, не будет ли текущая точка выходить за пределы границ экрана. Поэтому в функции text также выполняется такая проверка. Функция textXY выпол- няет всю работу по отображению заданной строки. Переменная len обозначает длину строки, то есть количество отображаемых символов. Затем вычисляется hpos путем сдвига значения X на три бита вправо, то есть значение координаты X делится на 8 в смысле целочисленного деления. Полученное число будет номе- ром байта относительно левого края строки на экране. Нижняя правая позиция отображаемой строки будет иметь координаты; Xl=S(hpos + len) У1-У+10 и эта точка проверяется на попадание внутрь границ экрана. Как и ранее, заданная начальная точка определяет положение левого верхнего угла первого символа в строке sir. В самом внешнем цикле мы выбираем /-тый символ str[i] в заданной строке и это значение будет использовано в качестве индекса для массива Mist. Поскольку этот массив двухмерный, то элемент массива р = chkist [ str[i ] ] является указателем на Mist [str[i ] ] [0 ]. В об- щем случае р + у будет адресом элемента массива Misr[str[i ]][/], который определяет третий аргумент в обращении к функции роке (у = 0> 1» •••> Ю). у-тая строка символа имеет У координату
5.3. ФОРМИРОВАНИЕ ПЕЧАТАЕМЫХ СИМВОЛОВ 135 vpos = У + /'. В главе 2 уже обсуждалось, как вычисляется смеще- ние offset, так что здесь это не будем снова объяснять. Функция dot, использовавшаяся до сих пор всегда, когда надо было что-то занести в экранную память, теперь будет обойдена. Ее можно было бы использовать и здесь, но это было бы гораздо менее эф- фективно. Заметим, что новые функции imove и idraw полезны не только в связи с отображением текста, но также и в тех слу- чаях, когда нужно вычертить отрезок прямой линии, если его концевые точки заданы в пикселных координатах. Напомним, что мы уже имели возможность вычерчивать отрезки прямой ли- нии, задавая пикселные координаты в функции drawjine. Но в функциях imove и idraw применяется та же методика, что и в функциях move и draw, так что они также желательны с точки зрения логичности. Хотя в результате выполнения операторов: draw_line(XA, YA, ХВ, YB); и imove(XA, YA); idraw(XB, YB); вычерчивается совершенно одинаковый отрезок прямой линии, состояние программы будет совершенно различным. Обращение к.функции drawjiine оставляет текущую точку (XI, У1) без из- менения, но вызовы функций imove и idraw изменяют текущие значения координат XI и У1. Аналогичные различия существу- ют и между операторами: textXY(XA, YA, str); и imove(XA, YA); text(str); Будем применять функции drawjine и textXY только тогда, когда координаты XI, У\ не должны изменяться. 5.3. ФОРМИРОВАНИЕ ПЕЧАТАЕМЫХ СИМВОЛОВ Перед тем, как обращаться к новым функциям text и textXY необходимо побеспокоиться, чтобы массив Mist был инициали- зирован соответствующим образом. Для каждого символа нужно найти одиннадцать чисел таким же образом, как это было сдела- но для буквы С, изображенной на рис. 5.1. Чтобы не выполнять эти действия вручную, составим специальную программу. Вход-
136 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ ными данными для этой программы будет файл с именем CHARS.TXT, в котором очертания каждого символа определены в прямоугольнике 11x8 как на рис. 5.1 изображена буква *С\ Было бы неплохо, если каждому такому прямоугольнику пред- шествовало обозначение определяемого символа. Тогда програм- ма, которая читает файл CHARS.TXT будет использовать этот предшествующий символ, чтобы узнать символ, определяемый в данный момент, освобождая нас от обязанности строго соблюдать последовательность кодов ASCII. Программа должна быть по возможности проще и не следует размещать все прямоугольники друг под другом в один длинный столбец, требующий для вывода на печать много бумаги и непригодный в таком виде для включе- ния в книгу. Поэтому допустим некоторое усложнение и разме- стим в один ряд несколько прямоугольников. На рис. 5.2. показа- но полное содержимое файла CHARS.TXT. ...8 ..888... .88.88.. 88...88. 8 8. 8 8. 8888888. 8 8. 8 8. в 888888.. 8 88. 8 8. 8 88. 888888.. 8 88. 8 8. 8 88. 888888.. .88888.. 88...88. 8 8 8 8 8 88...88. .88888.. 888888.. 8 88. 8 8. 8 8. 8 8. 8....8. 8 8. 8 88. 888888.. Е 8888888. 8 8 8 88888... 8 8 8 8888888. 8888888. 8 8 8 888888.. 8 8 8 8 .88888. 88...88 8 8 8 8 8...888 8 8 88...88 .88888. н 8 8 8 8 8 8 8 8 8888888 8 8 8 8 8 8 8 8 888 8 8 8 8 8 8 8 888 88 888 ..8 ..8 ..8 ..8 ..8 ..8 ..8 .88 88. 8 88. 8...88. 8..88. 8.88.. 888... 8.88... 8..88. 8...88. 8 88. 8.... 8.... 8.... 8.... 8.... 8.... 8.... 8.... 8888888. 8 88...f 888.8* 8.888 8..8. § 8 8 @ 8 >8 18 8 8 8 8 8 8 8 8 88 8 888...8 8.8...8 8.88..8 8..88.8 8...888 8 88 8 8 .88888. 88...88 8 8 8 8 8 8 8 8 8 8 88...88 .88888. 888888. 8 88 8 8 8 88 888888. 8 8 8 8 .88888. 88...88 8 8 8 8 8 8 8 8 8..8..8 88.8.88 .88888. 8.. 888888.. 8....88. 8 8. 8 88. 888888.. 8. .8.... 8..88..V 8...88.. 8... .88. Рис. 5.2. Содержимое файла CHARS.TXT
5.3. ФОРМИРОВАНИЕ ПЕЧАТАЕМЫХ СИМВОЛОВ 137 s .88888. 88...88 её!!!!! .88888. 88 88!!!88 .88888. т 8888888 ..8.. ..8.. ..8.. ..8.. ..8.. ..8.. и 8 8 8 8 8 8 8 8 8 8 8 8 8 8 88...88 .88888. 8 8 8 8 8 8 88...88 8...8 88.88 8.8 888 .8. w 8 8 8 8 8 8 8..8..8 8..8..8 8.888.8 888.888 .88.88. .8...8. 88...88 8...8. 88.88. .8.8.. .888.. .8.8.. 88.88. 8...8. 88...88 8 8 8 8 88...88 88.88 .888. ..8.. ..е.. ..е.. ..е.. Z 8888888 ...8 ..88 .88. .8.. 88.. 88... 8888888 88888. 88 888888 88 8 88 8 .888888 8 888888! 8 88 с ..... 6 6 ..... с 8 88 888888. .88888 88...88 8.... 88...88 88888 8.. 8.. .88888.. 88...8.. 8 8.. 8 8.. 88:. .8.. 88888.. !88888! 88...88 8888888 8 88 .88888. ...888.. ..88.*.. . .8 888888.. ..8 ..8 ..8 ..8 ..8 .8888.8 88..888 8 8 8 8 88 8 .888888 88 .88888. m 888888 88...88 8 8 8 8 .8 .8 .8 .8...88. .8..88.. .8888... .8.88... .8..88.. .8...88. .ее... ..е... ..е... ..е... ..е... ..е... ..е... ..е... .еее.. ....... eee.ee. е..е..е е..е..е е..е..е е.. е.. е е..е..е •*■•••*. е.ееее.. ее...ее. е е. е е. е е. е е. ее .еееее. ее...ее е е е е ее...ее .еееее. ее ее .ееее. ёёёёёё!! е — ее. е е. е е. е — ее. ееееее.. е е .еееее ее...е е — е е — е ее...е .еееее • • !! ё!ёёёё! .. еее..ее .. е .. е .. е .. е е !ёёёёё! ее...,, .еееее. ее ее .еееее. .§ .е ееее — .е .е .е .ее..ее. ..ееее.. е. е. е. е. ее. .ееееее е. е. ее .ее .е .е ее ее. .еее. .е. Рис. 5.2. Содержимое файла CHARS.TXT (продолжение)
138 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ е.. е. е.еее.е. еее.еее. .е...е. ее...е .е.е. .еее. ee.ee ее...ее § е е § ее .ееееее ее .еееее. еееееее ...ее ..ее. .ее., ее... еееееее .еее. ee.ee е...е ee.ee .еее. еее .еееее.. ее...ее. е. — ее. ...ее. ..ее... еР" еееееее. 8 .еееее. ее...ее. е е. ее...ее. .еееее. ее...ее. е е. ее...ее. .еееее. % ее. .е. .е. .е.. .ее. еееееее. .е. .е. .е. .еееее.. ее...ее. !!!ее! ..ее.. ...ее. ...е. ее...ее. еееее.. .еееее. ее...ее е.,...е е§. -. .§ ееееее еееее. ..ее. :!::! ..ее. .е.е. е..е. . е...е.е е...ее. .еее .ее. — е ...ее ..еее .ее.е ее..е ее...е еееееее е е .е. еее еее еее е е е еееееее еееееее е ееееее! .. •.. в в е е ее...ее .еееее. .ее. .ее. .еееее. ее...ее е...... ееееее. ее...ее е е е е ее...ее .еееее. еееееее. е. ее. •••J*-- ...ее... ..ее.... .ее ее * ..е..е. ..е..е. .ее..е. еееееее . в-... в. еееееее .ё '" .е ) е.. е. е .еееее ее.е ее.е ееее ..е. ёёее ..е е е. .е...е.. ..е.е... еееееее. ..е.е... .е...е.. е е. :*ё: .ее. .ее. .ее. Рис. 5.2. Содержимое файла СИARS.TXT (продолжение)
J.J. ФОРМИРОВАНИЕ ПЕЧАТАЕМЫХ СИМВОЛОВ 139 . . . . ..88 ..§8. . . . . • . . . . . . . ..88 ..88 ...8 ..§.. — 8. ...8.. ..8... .8— § .8— ..§... ... 8.. . •. * §. ....*... •••«•••• .. 888888.. ........ ........ 888888.. ........ 8 .8— ..§... ...е.. — §. ...е.. ..8... .8— § .8888... 88..88.. р. ..§.. .88.. 88... § — § — .,... 8— [ 88888 8... 8... 8... 8... 8... 8... 8... 88888 ] 8888. ..8. ..8. ..8. ..8. ..8. ..8. ..8. 8888. .8. 8.8 8888888 .88888. 88...88. ,888. .8..8. .8..8. .8..8. ..88. i88888'. .8.. .8. ..88888 .8 8 8 8 8 8 8 8 8888888 8 8 8 8 8 8 888888 8... 8... 888888 8... 8... 8... 8... 888888 В 888888. 8 8 8 8 8 8 888888! 8 8 8 8 8 8 888888. 88 88 888888 Д .888. .8. .8. .8. .8. .8. .8. .8. 8. 8. 8. 8. 8. 8888888. 8 8. 888888. 8... 8... 8 88888. 8... 8... 8... 888888. Ж 8..8. -8 8..8..8 .8.8.8. ..888.. ..888.. .8.8.8. 8..8..8 8..8..8 8..8..8 . ..8888. . .8 8 8 8. . ..8888. 8 8 . 8 8 . .88888. 8 8. 8 8. 8....88. 8...888. 8..88.8. 8.88..8. 888...8. 88 8. 8 8. е.. 8.8 888. 88.. 8... 88.8 ...8 ..88 .888 88.8 " 8 8 8 8. 8. 8. 8888 8. 8. .8. ..8 ..8 .8. .8. .8. .8. .8. 88. 88. ..8888. .8...8. .8. .8. .8. .8. .8. .8. .8. Рис. 5.2. Содержимое файла CHARS.TXT (продолжение)
140 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ м е.. 88. 888 8.8 8.. 8.. 8.. 8.. ...8. ..88. .888. 38.8. а..8. ...8. .. .8. ...8. ...8. Н 8 8. 8 8. С.....С. 8888888. 8 8. 8 8. 8 8. 8 8. 0 .88888.. 8 8. £•••••£• 8 8. 8 8. 8 8. Щ т • • • • С • .88888.. П 88 8. 8. 8. 8. 8. 8. 8. 88888. 8. !! !!е! — е. — е. — е. — е. р 888888.. 8 8. 8 8. с*....с* с... ..в. 888888.. 8 С .88888.. 8! '.У.'.!! 8 е 8 8. .88888.. 8888888 8 8 8 8 8 8 8 8 8 8. '.'..'/.%: 8. .....е. 888888. 8. 88888.! Ф .88888! 8..8..8 8..8..8 8..8..8 8..8..8 88888 ..8.. 8 88...88 .88.88. ..888.. !!8.ё!! .88.88. 88...88 8 8 ц ..8. .8.. .8.. .8.. .8.. .8.. .8.. 8 в.. .888888. 8.. 8.. 8.. 8.-. 8 8 8 8 8 8 6..... с с..... с 888888 ш 8..8..8 8..8..8 8..8..8 8..8..8 8..8..8 8..8..8 8..8..8 8..8..8 .888888 щ е..8..8. 8..8..8. 8..8..8. 8..8..8. 8..8..8. 8..8..8. 8..8..8. 8..8..8. 8888888. ' !:::!??■ ъ 88 88 .8 .88888. .8....8 .8 8 .8 8 .8 8 .88888. Ы 8 8. 8 8. 8 8. 8888..8. 8...8.8. 8...8.8. 8...8.8. 8...8.8. 8888..8. Ь 8 8 § 888888. 8 8 8 8 8 8 8 8 888888. • Э .88888.. 8 8. §. §. ..88888. §. . 8 8. . .88888.. • • • • ю 8..888. 8.8...8 8.8 8.8 8.8 888 8.8 8.8 8..888 .88888 8 8 8....8 8 8 8 8 .88888 .8...8 8 8 8888. 88888 8 8 8 8 .8888.8 .8888 8 88888 8 8 8 8 8 8 .8888. 88888. 8....8 88888. 8 8 8 8 88888. .8888.. !88888! 8 8. 8....8. 8 8. .8888.. . .8888.. . 8....8. . 8....8. . 88888.. . 8 . .88888. Рис. 5.2. Сод( . 8..8..8 . .8.8.8. . ..888.. . ..888.. . .8.8.8. . 8..8..8 гржимое файл .8888.. 8 8. ...88.. 8. 8 8. .8888.. aCHARS.T) . 8 8. . 8...88. . 8..8.8. . 8.8..8. . 88...8. . 8 8. <Т (продолжи . ..88 . 8 8.. . 8...88.. . 8..8.8.. . 8.8..8.. . 88...8.. . 8 8.. 'ние)
5.3. ФОРМИРОВАНИЕ ПЕЧАТАЕМЫХ СИМВОЛОВ 141 8 8. 8...8.. 8888... 8...8.. 8 8. 8 8 i .8888. 8...8. 8...8. 8...8. 8...8. » 8. . 8 8 . 88...88 . 8.8.8.8 . 8..8..8 . 8 8 . 8 8 8 8. 8 8. 8 8. 888888. 8 8. 8 8. . .8888.. . 8 8. . 8 8. . 8 8. . 8....8. . .8888.. . 888888.. . 8 8.. . 8 8.. . 8 в.. . 8....8.. . 8....8.. 88888 8.. 8.. 8.. 8.. 88888 8.. 8.. 8888 8888 8888888 ..8. ..8. ..8. ..8. ..8. 8 8 8 8 8 88888 8 8888. ...8... .88888. 8..8..8 8..8..8 8..8..8 8..8..8 .88888. .88. .88. 8..8 Щ 8 8. 8....8. 8 8. 8 8. 8....8. .888888 88 8 8. 8....8. 8 8. .88888. 8. .8. . 8..8..8 . 8..8..8 . 8..8..8 . 8..8..8 . 8..8..8 . .888888 8..8..8 8..8..8 8..8..8 8..8..8 8..8..8 .888888 8( 88 .8 .8888.. .8...8. .8...8. .8888.. 1 8 8. . 8 8. . 8888..8. . 8...8.8. . 8...8.8. . 8888..8. ю 8 8 88888.. 8 8. 8 8. 88888.. .8888.. 8 8. 8. . ..8888. . 8 8. . .8888.. . 8..88.. . 8.8..8. . 8.8..8. . 888..8. . 8.8..8. . 8..88.. '. ( .8888.. 8...8.. 8...8.. .8888.. 8...8.. » 8.. Рис. 5.2. Содержимое файла CHARS.TXT (окончание) Этот файл будет использован для получения программного модуля, который назовем CHARS.C, он будет иметь формат: static char chlist[256] [11]- { { .. }. { ... }
142 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ 5.4. ПРОГРАММНЫЙ ГЕНЕРАТОР ДЛЯ ШРИФТОВ Разработаем программу для генерации шрифта и присвоим ей имя CHARSGEN.C. Как показано на рис. 5.3, ее входными и вы- ходными данными являются файлы CHARS.TXT и CHARS.C со- ответственно. Файл CHARS.TXT Программа CHARSGEN.C Файл CHARS.C Рис. 5.3. Программный генератор CHARSGEN.C В программе CHARSGEN.C сначала прочитаем файл CHARS.TXT и соберем все данные в массиве chr> состоящем из 256 строк и 11 столбцов, идентичном массиву chlist, для которого должен быть сгенерирован определяемый исходный код. Как уже говорилось ранее, сложность этой задачи зависит в основном от необходимости размещения нескольких прямоугольников сим- волов в одном ряду (скажем, по п символов). Для рис. 5.2. приня- то п = 6, за исключением шестнадцатого и последнего рядов, где /1 = 4. Это следует из того, что в файле имеется 94 = 15 * 6 + 4 печатаемых символа кодов ASCII ( плюс 10 * 6 + 4 « 64 символов русского шрифта). Символы ASCII со значением кодов 0, 1, ..., 31 и 127 не печатаются, а пробел (код 32) вообще считается печа- таемым, но изображать его нет смысла. Таким образом из первой половины таблицы (128 символов) в файл включаются только 94 символа, а 34 символа не включаются. Теперь нам нужно заста- вить нашу программу прочитать файл CHARS.TXT и сделаем это чисто последовательным образом, что означает параллель- ную обработку одновременно п символов.
5.4. ПРОГРАММНЫЙ ГЕНЕРАТОР ДЛЯ ШРИФТОВ 143 Массив элементов: sym[Q], sym[l ],..., sym[n- 1 ] будет содержать описание (коды) символов. Они читаются из строки, предшествующей п прямоугольникам, определяющих их очертания. После этого считываются одиннадцать строк, содер- жащих эти прямоугольники. Заметим, что второй ряд (из восьми позиций) для символа sym [О ] может быть считан только после прочтения первого ряда для всех п символов. Это и оправдывает упомянутую выше фразу, что все п символов читаются парал- лельно. Конечно, требуется некоторая осторожность в пропуске точного числа пробелов и символов новой строки, но практиче- ски все это осуществить нетрудно. Прежде, чем продолжать дальше, неплохо бы взглянуть на первую часть программы CHARSGEN.C, то есть вплоть до оператора fclose if pin). /* CHARSGEN.C: This program reads the file CHARS.TXT, which */ /* displays the shapes of characters. It generates V /* the program module CHARS.C, containing the */ /* same Information In coded form. */ /* Эта программа считывает файл CHARS.TXT, который */ /* содержит описание литер. В результате будет */ /* получен программный модуль CHARS.C, содержащий */ /* ту же информацию, но в закодированном виде. */ ♦Include "stdio.h" #define NASCII 128 FILE *fpln, *fpout; main() { static unsigned char chr[NASCI 1111]; /* Initialized to zero . */ /* Инициализируется нулями */ unsigned char sym[10], k, ch; Int s, I, J, h, I, Ich, n, m; fpin - fopenCchars.txt", V); If (fpin- -NULL) { prlntf ("Нет входного файла chars.txt."); /* "No Input file chars.txt." */ exlt(1);} while (lch-getc(fpln), ichM)) { n-0; do { sym[n}Hch; n++; sklp(W); Ich - getc(fpln); } while (Ich !- An');
144 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ /* The n characters sym[0] sym[n-1] have now */ /* been read on one line; their tables follow! */ /* Теперь прочитаны n литер sym[0] sym[n-1], */ /* в одной строке; ниже следует их таблица ! */ sklpnewllne ("в начале таблицы"); /* "at the beginning of a table" */ for(M);K11;l++) { for(h-0; h<n; h++) { s-0; forG-0;j<8;j++) { ch-getc(fpln); s - 2*s + (ch - - '©'); } chr[sym[h]Ii}-s; sklp(3); } sklpnewllne ("внутри таблицы"); /* "within a table" */ skipnewline ("(в первой строке после таблицы)"); /* "(first after table)" */ skipnewline ("(во второй строке после таблицы)"); /* "(second after table)" */ } fclose(fpin); fpout-fopen("chars.c", "w"); fprlnttXfpout. "#define NASCII 128\n"); fprinttXfpout. "charchlist[NASCIII11]-\n{\n "); for (К); К NASCII-1J++) { fprintf(fpout,"{"); m-10; while (m>0 && chrfllm}- -0) m- —, /* Any zero elements following chrfllm] can be omitted */ /* Нулевые элементы после chrfllm] могут быть опущены */ for (i-0; i<-m; i++) { k-chrflll]; If (k- -0) putc('0', fpout); else fprlntf(fpout, "0x%1X%1X", k»4, k&15); if(i<m)fprintf(fpout, ", "); } putc('}', fpout); fprintf(fpout, ","); If (I - - 10 11 I - - 21) fprint^fpout, "\n "); else if (I >- 31 && К NASCII - 2) /* Display next character: */ fprintf(fpout, "\n/*%c*/ ", 1+1); /* Вывод следующего символа */ } fprintf(fpout, "\n{0}};\n"); fclose(lpout); }
5.4. ПРОГРАММНЫЙ ГЕНЕРАТОР ДЛЯ ШРИФТОВ 145 skip(n) int n; /* Skip at most n characters of the same line */ /* Пропуск по крайней мере п литер в той же строке */ { inti, ch; for (Ю; i<n; i++) { ch-getc(fpin); if (ch--'\n'){ ungetc(ch, fpln); break; } } } skipnewline(s) char *s; /* Skip a newline character, which must be present */ /* Пропуск пустой строки литер, которая должна быть */ { charch; ch-getc(fpin); if(ch!-'\n') { printf ("Ожидается пустая строка %s", s); /* "Newline character expected " */ } } Эта программа требует, чтобы входной файл CHARS.TXT имел точный формат: пропущенный или лишний пробел или символ новой строки вызовут дополнительные сложности. Если бы программа применялась часто и различными пользовате- лями, то ее лучше было бы отработать более тщательно, но поскольку это лишь вспомогательная программа и выполняется только один раз, то она годится и в таком виде. В языке Си для всех элементов статических или внешних массивов гарантирует- ся, что они будут иметь нулевые начальные значения, если им явно не присвоены иные значения. Это относится как к первой, так и ко второй частям программы. После сбора всей информации в массиве сйг, вторая часть ге- нерирует текст программы, которая определяет и инициализи- рует массив Mist. Здесь мы опять реализуем вариант, несколько более сложный, чем строго необходим. Проследим, чтобы длина строки была разумной и исключим из последовательности пос- ледние нули. Приведем простой пример, чтобы пояснить сказан- ное на примере. В языке Си некоторый массив А инициализиру- ется как static int A[5]-{ 1,2, 3,0,0}; его объявление можно заменить на static int А[5]-{ 1.2. 3};
146 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ поскольку последние два элемента массива Л неявно инициали- зируются нулями. Сейчас каждый из первых 32 рядов массива Mist может состоять из одиннадцати нулей (или любых других значений), поскольку эти ряды соответствуют непечатным сим- волам и поэтому не будут использоваться. При публикации в книгах, подобных данной, было бы очень некрасиво, если бы ре- зультирующий файл CHARS.С начинался с 32 * 11 - 352 нуле- вых элементов, поэтому запись {О, 0, 0, 0, 0, 0, 0, 0, 0, 0. 0} сократим до {0} и поместим не более одной такой сокращенной записи в одну строку. Поскольку каждый из нулевых элементов двухмерного массива Mist должен интерпретироваться в виде ряда из восьми пикселов, то желательно их записывать в виде двух шестнадца- теричных цифр с предшествующим префиксом Ох, как этого тре- бует компилятор языка Си. Было бы также очень полезно вклю- чить комментарий в начале всех строк с кодами для печатаемых символов. Рекомендуется перед изучением программы, посмот- реть на фактический результат (то есть файл CHARS.C), кото- рый получен с помощью этой программы. Эту часть текста прог- раммы можно найти в модуле GRPACK.C, помещенного в конце главы 4. Она начинается со строки: # define NASCII 256 и кончается строкой: {0}}; Вместо включения этого файла в модуль GRPACK.C, его можно было бы компилировать отдельно, но тогда потребовалось бы всегда обязательно подключать его к редактору связей, даже в тех случаях, когда графические функции не требуются. Это не- обходимо из-за того, что идентификатор Mist встречается в функции textXY и редактор связи сообщил бы о неразрешенной внешней ссылке. Объединение функций графического текста и массива Mist в один файл может показаться вполне естествен- ным решением, но тогда некоторые переменные не могут иметь
5.4. ПРОГРАММНЫЙ ГЕНЕРАТОР ДЛЯ ШРИФТОВ 147 класс памяти 'static' (статический), поэтому последнее решение было бы нежелательным с точки зрения надежности. Поскольку различные надписи обычно включаются в состав графического изображения, то, по мнению автора, массив Mist будет исполь- зоваться достаточно часто и поэтому выбранное решение совсем уж не такое плохое. После этих замечаний относительно соответствующего места размещения файла CHAR.С, вернемся к нашему обсуждению процесса генерации такого файла. В программе CHARSGEN.C разрешим переменной / изменяться в диапазоне от 0 до 126. Наи- большее значение 127 определяет непечатаемый символ и не имеет соответствующего элемента текста программы text{0}, за которым следует запятая, поэтому оно будет обрабатываться специальным образом почти в самом конце основной программы. Для каждого значения /, то есть для каждого символа, найдем значение т, обладающее таким свойством, что для всех значе- ний i > т элемент массива chr [I] [i] равен нулю и поэтому нет необходимости его инициализировать. Таким образом, для получения значений, которые должны быть записаны в файл CHARS.C нам нужно заставить перемен- ную i изменяться от 0 до т и использовать значения к = chr [I ][i]. Можно заметить, что в программе несколько раз встречается ключевое слово unsigned. Напомним, что это позволяет исклю- чить перенос "знакового бита" символа влево при расширении, когда символьная переменная преобразуется в целочисленную переменную. Об этом уже говорилось в параграфе 1.2. Заметим, что в выражениях к » 4 и к & 15 применяются би- товые операции для получения двух шестнадцатеричцых цифр для к. Как и в первой части программы, нужно уделить некото- рое внимание лексическому аспекту, иными словами, нам нужно обеспечить включение пробелов, запятых и скобок там, где они требуются, но такие детали обсуждать не будем. Обычно пользователь обращается только к функции text (или textXY) и не имеет непосредственного доступа к массиву chlist, поэтому кажется, что лучше было бы добавить ключевое слово static для этого массива, чтобы предохранить его от случайного использования. Но в параграфе 5.6 мы обсудим полезное приме- нение возможного доступа к массиву chlist
148 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ 5.5. ДЕМОНСТРАЦИОННАЯ ПРОГРАММА Наступило время применить разработанные нами средства. В качестве примера отобразим вначале все символы в том их на- чертании, какой был нами разработан. Затем изобразим некото- рое вычисленное значение, скажем, квадратный корень числа 2, то есть теперь мы должны иметь в качестве исходного значения число, выраженное во внутреннем двоичном представлении. В языке Си предусмотрена очень полезная функция для преобра- зования любого числового значения в строку, которая должна быть напечатана. Вопреки своему названию, эта функция sprint/ не выполняет никаких действий по выводу, а вместо этого выдает результат в виде строки. Также будем использовать новые функ- ции imove и idraw. На рис. 5.4. показан результат работы прог- раммы TEXTDEMO.C. /* TEXTDEMO.C: This program displays and prints all characters */ /* of which the font is coded In array chllst. */ /* It also shows how a computed value is displayed */ /* and printed In graphics output. */ /* Эта программа отображает на экране и выводит на*/ /* принтер все литеры для шрифта, описанного в */ /* массиве chllst. Также показывается отображение */ /* вычисленных значений в графическом режиме. */ #include "math.h" main() {int I; extern float x_max, ymax; extern int X max, Y max; IntXmiddle; float xO, yO; static char s[20]; setprdim(); s[1] -''; Xmiddle - (X_max + 1)/2; InitgrO; /* Draw the screen boundaries: */ /* Вычерчивание границ экрана */ imove(0, 0); ldraw(X max, 0); idraw(X max, Y max); idraw(0, Y__max); idraw(0, 0); /* Display the ASCII sequence: */ /* Отображение символов 'аскии' */ imove(16, 8); text /* "ASCII characters in our own font:" */ ("Символы ASCII в нашем начертании шрифта:"); for(i-32;i<240;i4+)
5.5. ДЕМОНСТРАЦИОННАЯ ПРОГРАММА 149 { s[OH if(i>175&&i<224)continue; if (i % 32 — 0 && K175) imove(Xmiddle - 32 * 8, 20 + i/32 * 12); text(s); } imove(Xmiddle - 16*8, Y__max - 32); text("RIGHT-ANGLED ISOSCELES TRIANGLE"); lmove(Xmlddle - 16*8, Y__max - 18); text( "(with two sides of length 1)"); xO - 0.5 * x_max - 0.7; /* (xO, yO) is the bottom-left */ yO - 0.5 * y_max - 1.0; /* point of the triangle */ /* Точка (xO, yO) в нижнем */ /* левом углу треугольника */ move(xO, yO); draw(xO + 1.0, yO); draw(xO, yOH.O); draw(xO, yO); move(x0-0.1, yO + 0.6); text("r); /* Length of vertical side */ ч /* Длина вертикальной стороны */ move(xO + 0.45, yO - 0.08); text(" 1"); /* Horizontal side */ /* Горизонтальная сторона . */ sprintf(s,"% 12.10Г, sqrt(2.0)); move(x0 + 0.7, yO + 0.7); text("sqrt(2) -"); text(s); /* Side with length sqrt(2) */ /* Сторона с длиной sqrt(20) */ printgr(0, X max, 0, Y max); endgrQ; Символы ASCII в нашем начертании ирифта: ♦ " #$Xi' ()*+,-. /0123456789:;< = >? ffABCDEFGHIJKLriNOPQRSTUVWXYZC\]A- 4abcdefghijklmnopqrstuvwxuz< I ) * РБВГаЕЖЗИИКПМНОПРСТУФХЦЧШШЪЫЬЭЮЯ абвгЭежзийклмиопрстуфхцчшщъыьэюя sqrt(2) = 1.4142135624 RIGHT-ANGLED ISOSCELES TRItfOE (with two sides of length 1) Puc. 5.4. Вывод программы TEXTDEMO.C
150 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ Заметим, что самым первым "печатаемым" символом будет символ пробела, то есть символу "восклицательный знак" (!) предшествует пробел. Между двумя последовательными строка- ми текста мы можем оставить любое свободное пространство, или, другими словами, координата У каждой строки может быть выбрана произвольно. Каждая строка занимает на экране по крайней мере 11 пикселов или на бумаге 11/72 дюйма, но вместо этого можно выбрать и любое другое расстояние. Для примера на рис. 5.4 строка RIGHT-ANGLED ISOSCELES TRIANGLE размещена на 14 пикселов выше строки (with two sides of length 1) Координата Х отсекается, чтобы она была кратной восьми пикселам. Поскольку имеем плотность 120 точек на дюйм (ис- пользуя двойную плотность), то будем печатать 120/8 = 15 сим- волов на дюйм. 5.6. РАЗРАБОТКА НОВЫХ СИМВОЛОВ В противоположность другим переменным, как, например, XI и Y\, объявление массива Mist не начинается со слова static. Это означает, что мы можем иметь доступ к этому массиву, поз- воляющему изменять его содержимое. При таком способе можно добавлять новые символы без применения "тяжеловесного" средства в виде программы CHARGEN.C и файла CHARS.TXT. В качестве примера добавим знак интеграла, который часто встречается в математических формулах, разместив его в 11 ря- дах по 8 пикселов в каждом в элемент массива chlist[\ ]. Сначала сконструируем форму этого символа, которая может выглядеть как изображено на рис. 5.5. ...ее. ..е.ее .ее. .ее. .ее. .ее. .ее. .ее. .ее. ее.е.. .ее... Рис. 5.5. Знак интеграла
56. РАЗРАБОТКА НОВЫХ СИМВОЛОВ 151 Для каждого ряда из восьми точек справа показаны одиннад- цать нужных нам числовых значений. Продемонстрируем ис- пользование вновь разработанного символа в программе NEWCHAR.C, печатающей хорошо известную формулу, в кото- рой знак интеграла появляется дважды. Поскольку мы использу- ем ряд 1 в двухмерном массиве Mist, то для обозначения знака интеграла будем применять восьмеричную запись \001 в строко- вой константе "\001 f(x)dx--\001 f(x)dx" Результат работы программы NEWCHAR.C изображен на рис. 5.6. Ь а J f(x) dx = - Г f(x) dx a b Рис. 5.6. Результат работы программы NEWCHAR.C /* NEWCHAR: * /* This program uses the Integral sign In Its output, *( /* both on the screen and on the printer * /* Эта программа'формирует знак интеграла для вывода *, /* как на экран монитора, так и на принтер *, main() { extern char chllst[256l11]; static char s[ 11]- {0x06, OxOB, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xD0, 0x60}; Int I, X0, Y0; for (1-0; K11; i++)chllst[1ll]-s[l]; setprdlm(); lnltgr(); XO-200; Y0-100; lmove(X0, Y0); textfVOOl tXx)dx--\001 f(x)dx"); lmove(XO+e, YO+9); textf'a"); lmove(Xf>8, Y0-11); textCb"); lmove(XOf 128, YO*9); text(nbM); imove(X0+128, Y0-11); textfa"); printgnj), 600, Y0-30, YO*30); endgnf);
Глава 6 DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 6.1. ВВЕДЕНИЕ В этой главе мы будем иметь дело с областью применения, в которой машинная графика особенно полезна, а именно — в сис- темах автоматизированного проектирования, с сокращенным обозначением в английском языке CAD. Эта же аббревиатура применяется также для обозначения систем автоматизированно- го черчения. Книги по системам автоматизированного проекти- рования (черчения) обычно предназначаются для проектиров- щиков, которые до сих пор пользуются карандашом и бумагой и их все еще нужно убеждать в пользе применения компьютеров. Наш подход будет совсем иным. Эта книга предназначена для программистов и эта глава не является исключением, поэтому рассмотрим здесь некоторые внутренние аспекты систем автома- тизированного черчения, обычно не раскрываемые для проекти- ровщиков. Мы не будем обсуждать коммерчески доступные пакеты программ автоматизированного проектирования, а разработаем свою собственную интерактивную графическую систему. Автор назвал ее DIG, или система черчения с интерак- тивной графикой. Эта программа не может конкурировать с про- мышленными программными системами автоматизированного проектирования по многим аспектам. Но она может быть полез- ной, если нужны только небольшие чертежи или эскизы. Во вся- ком случае, у нашей программы есть три явных преимущества: 1. Работает на любом персональном компьютере фирмы IBM (или совместимом с ним), имеющим цветной или монохром- ный графический адаптер. Для получения твердой копии
6.2. ПЕРЕМЕЩЕНИЕ КУРСОРА 153 графического результата вполне достаточно простого мат- ричного принтера. 2. В книге приведен полный исходный текст программы, что совершенно необычно для коммерческих программ автома- тизированного черчения. 3. По сравнению с другими системами автоматизированного черчения наша программа очень дешевая, особенно с учетом пп. 1 и 2. 6.2. ПЕРЕМЕЩЕНИЕ КУРСОРА Точку на экране будем отмечать специальным символом, на- зываемым курсором. Форма курсора не имеет особого значения, поэтому изобразим его в виде небольшого квадрата с одной точ- кой в центре. Мы должны обеспечить возможность перемещения курсора в любое место на экране. Для графического ввода суще- ствуют специальные устройства, например, мышь, графический планшет, световое перо. Однако не будем надеяться, что все чи- татели этой книги приобретут такое оборудование. Поэтому вос- пользуемся четырьмя клавишами со стрелками, размещенными с правой стороны нормальной клавиатуры, и попытаемся разра- ботать такое программное обеспечение, которое упростит приме- нение этих клавиш для наших целей. Если же кто-то будет нас- таивать на применении мыши, то полезную информацию об этом можно найти в Приложении Б. Форма нашего курсора показана на рис. 6.1. ххххххххх XX XX XX X XX XX XX ххххххххх 4 Рис. 6.1. Курсор Один пиксел в центре квадрата совпадает с той точкой на эк- ране, которую мы желаем отметить с помощью курсора. Вызывая ее пикселные координаты Хсиг и Ycur, будем "чертить" курсор с помощью функции сиг. cur<) { inti.j, dm; dm-drawmode; drawmode-O; forO—2; j<-2; j-H-4)
154 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ for (i—4; i<«4; i++) dot(Xcur+i, Ycur+J); dot(Xcur-4, Ycur-1); dot(Xcur-3, Ycur-1); dot(Xcur+3, Ycur-1); dot(Xcur+4, Ycur-1); dot(Xcur-4, Ycur); dot(Xcur-3, Ycur); dot(Xcur+3, Ycur); dot(Xcur+4, Ycur); dot(Xcur, Ycur); dot(Xcur-4, Ycur+1); dot(Xcur-3. Ycur+1); dot(Xcur+3, Ycur+1); dot(Xcur+4, Ycur+1); drawmode-dm; } Заметим, что внутри этой функции используется значение drawmode=0, означающее "переключение" состояния пикселов, что уже обсуждалось в параграфе 3.1. Поэтому, после обращения к этой функции дважды для одной и той же точки (Xcur, Ycur), экран останется в том же состоянии, которое было перед первым обращением. Это позволяет сохранить существующее изображе- ние неповрежденным, если сначала на него наложить курсор, а затем его удалить. Поскольку нам нужно перемещать курсор, то рассмотрим действие четырех клавиш со стрелками. Довольно курьезно, но одно нажатие на такую клавишу вызывает ввод в компьютер двух символов, первый из которых является нулевым (пустым) символом. Этот символ состоит из восьми нулевых бит, так что его значение равно 0 (нулю), но его не следует путать с символом "О", код которого равен 48. Значение второго символа зависит от конкретной нажатой клавиши следующим образом: Нажата Значение Значение клавиша первого символа второго символа Т 0 72 О 75 О 77 I 0 80 При нажатии клавиши со стрелками курсор нужно передви- нуть на несколько шагов в указанном направлении. Это делается в три этапа: 1) вызывается функция сиг для удаления курсора в позиции (Хсиг, У сиг) (напомним, что для этого используется режим
6.2. ПЕРЕМЕЩЕНИЕ КУРСОРА 155 draw/node = 0, который обеспечивает "переключение" со- стояния пикселов); 2) в зависимости от нажатой клавиши изменяется на опреде- ленный текущий размер шага значение переменной Хсиг или Ycur; 3) снова вызывается функция сиг для вычерчивания нового курсора. Предусмотрим возможность изменения размера шага. Пере- менной stsize, используемой для этой цели, вначале присваи- вается значение 8, которое означает, что при нажатии клавиши со стрелками курсор перемещается только на 8 позиций в задан- ном направлении. Иногда желательно иметь более крупные шаги. Для этой цели будем применять символ "больше, чем" — нажатие на клавишу (>) приведет к эффекту удвоения значения переменной stsize. Аналогичным образом это значение будет уменьшено вдвое при нажатии клавиши "меньше, чем" (<). Однако нельзя допускать неограниченное увеличение размера шага. Будем полагать, что число 200 является максимально допустимым значением. Также и наименьшее значение не может быть меньше одного пиксела. Аналогичные ограничения налагаются на значения пикселных координат Хсиг и Ycur, для предотвращения выхода курсора за границы экрана. Фактически будем использовать окно с размером несколько меньшим, чем полный экран, чтобы можно было отобразить в виде текста неко- торую полезную информацию вне этого окна — о клавишах, которые могут быть нажаты, о текущем размере шага и о теку- щих координатах курсора. В случае нажатия любой неразрешен- ной клавиши появится надпись "Invalid" ("Неверно"). Заметим, что введение окна позволяет легко разрешить проблему размера курсора. Например, нельзя задавать значение Хсиг = 0, поскольг ку это означало бы, что левая половина курсора должна распола- гаться левее пикселного столбца Х=0. Кроме клавиш со стрелка- ми и символов ">" и "<", будем считать также доступными симво- лы 'Q' и V- Они будут интерпретироваться как команда 'Quit' ("Выход"), которая будет определять завершение выполнение программы и переход компьютера в текстовый режим. Програм- ма CURSOR.C выполняет все эти операции, при условии, что по- сле компиляции она будет связана с модулем GRPACK.OBJ.
156 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ /* CURSOR.С: A demonstration program for cursor movements */ /* Демонстрационная программа перемещения курсора */ extern int X max, Y max, drawmode; int Xminl, Xmaxl, Yminl, Ymaxl, Ybottext, Xcur, Ycur, stsize-8; main() { char ch; initgr(); window(); prnum(120, stsize); Xcur-Xminl; Ycur-Yminl; cur(); prnum(200, Xcur); prnum(280, Ycur); while (ch - getch(), ch - toupper(ch), ch !- 'Q') { message(" "); /* Clear message area */ /* Очистка области сообщений */ if (ch — — 0) /* This happens if an arrow key is pressed */■ /* Это происходит при нажатии клавиш со стрелками */ { ch-getch(); switch (ch) { case 75: cur(); Xcur — stsize; if (Xcur < Xminl) Xcur-Xminl; cur(); prnum(200, Xcur); break; /* Left */ /* Влево */ case 77: cur(); Xcur-ь- stsize; if (Xcur > Xmaxl) Xcur - Xmaxl; cur(); prnum(200. Xcur); break; /* Right */ /* Вправо */ case 72: cur(); Ycur ^-stsize; if (Ycur < Yminl) Ycur - Yminl; cur(); prnum(280, Ycur); break; /* Up */ /* Вверх */ case 80: cur(); Ycur +- stsize; if (Ycur > Ymaxl) Ycur - Ymaxl; cur(); prnum(280, Ycur); break; /* Down */ /* Вниз */ default: message ("Неверно"); /* "Invalid" */ } } else if (ch - - V) { stsize *- 2; if (stsize > 200) stsize - 200; /* Maximum step size - 200 */ /* Максимальный размер шага - 200 */ prnum(120, stsize); } else
6.2. ПЕРЕМЕЩЕНИЕ КУРСОРА 157 if (ch -«'<') { stsize /- 2; if (stsize--0) stsize-1; prnum(120, stsize); } else message ("Неверно"); } to_text(); /* "Invalid" */ } window() { Xminl -4; Xmaxl -X_max-4; Yminl -22; Ymaxl -Y__max- 14; imove(Xmin1, Yminl); idraw(Xmax1, Yminl); idraw(Xmax1, Ymaxl); idraw(Xmin1, Ymaxl); idraw(Xmln1, Yminl); textXY(8, 0, /* "Use the four arrow keys to move the cursor." */ "Используйте четыре клавиши со стрелками для перемещения курсора,"); imove(8, 11); text /* "Use > to increase, or < to decrease the stepsize." */ ("клавиши > для увеличения или < для уменьшения шага,"); text /* " Press Q to quit." */ (" клавишу Q для выхода"); Ybottext - Ymaxl +3; textXY(8, Ybottext, /* "Stepsize:" */ "Размер шага:"); textXY(168, Ybottext, "X -"); textXY(248, Ybottext,"Y -"); } cur() { int i, j, dm; dm-drawmode; drawmode^); /* change color, using xor /* изменение цвета операцией XOR for 0—2;J<-2;j+-4) for(i—4; i<-4; I++) dot(Xcur+i, Ycur+j); dot(Xcur-4, Ycur-1); dot(Xcur+3. Ycur-1); dot(Xcur-4, Ycur); dot(Xcur+4, Ycur); dot(Xcur-4, Ycur+1); dot(Xcur+3, Ycur+1); drawmode-dm; dot(Xcur-3, Ycur-1); dot(Xcur+4, Ycur-1); dot(Xcur-3, Ycur); dot(Xcur, Ycur); dot(Xcur-3, Ycur+1); dot(Xcur+4, Ycur+1); dot(Xcur+3, Ycur); } prnum(X. num) int X, num; { char str[4]; sprintf(str, "%3d", num); textXY(X, Ybottext, str); } message(str) char *str; { textXY(Xmax1 - 64, Ybottext, str); }
158 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ Центральной частью основной программы является цикл, в котором каждый раз считываются и обрабатываются один или два символа. Внешние переменные X max, Y max, drawmode определены в модуле GRPACK (описанном в предыдущих гла- вах), это же относится к основным "графическим" функциям initgr, tojext, imove, idraw, text, textXY. Эта программа не только имеет практическое значение, но на ее примере можно более ясно показать работу базовых элементов интерактивной машин- ной графики, чем это можно сделать на основе сложной програм- мы. На рис. 6.2. показано изображение на экране в начале вы- полнения программы. Поскольку текущая позиция курсора оп- ределяется операторами: Xcur-Xmin1; Ycur-Ymin1; курсор вначале располагается в верхнем левом углу экрана. Если переместить курсор путем нажатия на клавишу со стрелкой вниз, то линия верхней границы окна снова появится в виде не- прерывной линии, не поврежденной предыдущим наложением курсора. Используйте четьре клавиш со стрелкам для перемещения курсора, клавиши > Оля увеличения ипи < Оля уменьшения мага, клавишу Q Оля выхода Размер шага: 8 X = 4 Y = 22 Рис 6.2. Исходное положение курсора
6.3. ОПЕРАЦИИ ПО ЭСКИЗИРОВАНИЮ 159 6.3. ОПЕРАЦИИ ПО ЭСКИЗИРОВАНИЮ Расширим программу CURSOR.С и предусмотрим удобный способ для вычерчивания и удаления отрезков горизонтальных и вертикальных линий. Другим предметом обсуждения в данной главе будет интерактивный ввод и удаление текстовых строк. Перемещение курсора будет осуществляться таким же образом, как и в предыдущем параграфе — путем нажатий на клавиши со стрелками, но будет также обеспечена возможность сохранения на экране следа перемещения курсора. Будем считать курсор как бы пером, кончик которого либо касается, либо поднят над лис- том бумаги. Для установки этих двух положений пера будем использовать две команды: PD Перо опустить PU Перо поднять Очевидно, что перемещающееся перо ничего не начертит, ес- ли оно поднято. Но если оно опущено, то ситуация намного инте- реснее. В противоположность обычному перу наше воображае- мое перо может,быть также использовано для стирания того, что было начерчено ранее. Будем использовать такие же три "режи- ма черчения", как и в параграфе 3.1 со следующими ассоцииро- ванными командами: РР Положительное перо (режим черчения = 1) PN Отрицательное перо (режим черчения = -1) РА Переменное перо (режим черчения » 0) Напомним, что при режиме черчения 0 перо попеременно за- писывает и стирает, что является следствием выбранного имени пера. Итак, всего будем иметь 2x3 = 6 состояний пера, поскольку последние три команды не зависят от положений пера, установ- ленного командами PD и PU. Однако, если перо поднять, то оно считается отключенным, поэтому реально существуют только четыре интересных случая: PU, (PD, РР), (PD, PN), (PD, РА) Человек может забыть, какая команда была задана послед- ней, так что будет полезно отобразить положение пера и режима черчения на левой стороне экрана, вне окна. Для обозначения режимов черчения будем использовать буквы Р, N, А, которые
160 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ означают "положительный", uотрицательныйu и "переменный" режимы соответственно. Функция dmode обеспечит вывод этих символов на экран: dmode(m) int m; /* 1 - Positive, -1 - Negative, 0 - Alternate */ /* 1 - Черчение, -1 - Стирание, 0 - Переключение */ { textXY(0. 55, m - - 1 ? "P" : m - - -1 ? "N": "A"); drawmode- m; } Положение пера также можно было обозначить таким же об- разом, отображая букву 'D' для опущенного, или букву 'U' для поднятого пера. Однако лучше для этой цели использовать кар- тинку, подобно показанной на рис. 6.3. (а) (б) Рис. 6.3. Положение пера' (а) — Опущено, (б) — Поднято Индикация положения пера появится также на левом краю экрана. Такую картинку можно легко получить, изображая бук- ву Н над буквой V с горизонтальным отрезком под буквой V, что делается в следующей функции: penposition(p) int p; /* Display pen position: 1 - down, 0 - up */ /* Отображение положения пера: 1 — опущено */ /* 0—-поднято */ { int dm; dm-drawmode; drawmode-1; textXY(0, p ? 27 : 40, " "); /* clear old pen portion */ /* стирание части старого пера */ textXY(0, p? 32:27, "Н"); textXY(0. p ? 40 : 35, "Vм); /* a picture of a pen */ /* изображение пера */
6.3. ОПЕРАЦИИ ПО ЭСКИЗИРОВАНИЮ 161 draw_line(0, 48, 6, 48); /* the paper on which the pen writes */ /* бумага, на которой пишет перо */ pendown-p; drawmode-dm; } Наилучшим способом разобраться в работе этой программы является ее самостоятельное применение. После запуска про- граммы на выполнение может оказаться желательным изменить размер шага курсора (нажатием на клавиши '>' или '<'), чтобы при вычерчивании каждого отрезка прямой линии можно было использовать соответствующее количество шагов. Чем больше величина шага, тем легче начертить несколько отрезков прямой точно одинаковой длины и вернуться точно на конечную точку ранее вычерченного отрезка (для этой последней цели в парагра- фе 6.4 будет описан более мощный способ). В тех случаях, когда часто удаляются части отрезков линий сразу же после их вычер- чивания, следует рекомендовать применение "переменного" режима черчения линий, хотя с этим связано появление неболь- ших программистских трудностей. Напомним, что в функции draw_ line мы ввели дополнительное обращение к функции dot, чтобы предотвратить появление небольших пробелов при вычер- чивании последовательности отрезков в режиме drawmode = О, о чем было сказано в параграфе 3.1. Теперь предположим, что в этом режиме был вычерчен отре- зок АВ и сразу же его удалим путем "вычерчивания" отрезка ВА. Тогда для точки В функция dot будет вызвана три раза из-за только что упомянутого дополнительного обращения и поэтому эта точка не исчезает, как бы ей полагалось. Для решения этой проблемы будем сравнивать направление вычерчивания каждого отрезка прямой линии с направлением предыдущего отрезка и, если он направлен в противоположную сторону, будем вызывать функцию dot четвертый раз в той же точке (В), в которой на- правление меняется на обратное. При каждом перемещении курсора на один шаг координаты Xcur, Ycur точки его предыдущего положения запоминаются в переменных Xold, Yold. После изменения либо Xcur, либо Ycur как следствия перемещения курсора, основной задачей функции Iseg, в случае опущенного пера, будет вычерчивание отрезка прямой линии обращением к функции draw_ line: draw_line(Xold, Yold, Xcur, Ycur) 6-275
162 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ В дополнение к этому эта функция проверяет направление отрезка и при необходимости еще раз вызывает функцию dot в зависимости от соотношения направлений нового и старого от- резков прямой линии. Программа SCETCH.C отличается от своей предшественницы CURSOR.C в параграфе 6.2 как добав- лением новых функций Iseg, penposition и dmode, так и некото- рыми изменениями в основной программе. /* SKETCH.С: A demonstration program for sketch operations */ /* Демонстрационная программа для эскизирования */ extern int X max, Y max, drawmode; int Xminl, Xmaxl, Yminl, Ymaxl, Ybottext, Xcur, Ycur, stslze-8, pendown, Xold, Yold; main() { char ch; inltgrO; windowQ; dmode(1); /* drawmode -1 */ /* режим черчения */ penposition(O); /* pendown -0 */ /* перо поднято */ pmum(120, stsize); Xcur-Xminl; Ycur- Yminl; cur(); pmum(200, Xcur); prnum(280, Ycur); while (ch - getch(), ch - toupper(cn), ch !- 'Q') { messageC H); /* Clear message area */ /* Очистка области сообщений */ if (ch - - 0) /* This happens if an arrow key is pressed */ /* Это происходит при нажатии клавиш со стрелками */ { ch - getch(); Xold - Xcur; Yold - Ycur; switch (ch) { case 75: cur(); Xcur — stsize; If (Xcur < Xminl) Xcur -Xminl; lseg(); cur(); prnum(200, Xcur); break; /* Left */ /* Влево */ case 77: cur(); Xcur +— stsize; if (Xcur > Xmaxl) Xcur - Xmaxl; lseg();cur(); prnum(200, Xcur); break; /* Right */ /* Вправо */ case 72: cur(); Ycur — stsize; if (Ycur < Yminl) Ycur - Ymin 1; lseg();cur(); prnum(280, Ycur); break; /* Up */ /* Вверх */
6.3. ОПЕРАЦИИ ПО ЭСКИЗИРОВАНЙ'Ю 163 case 80: cur(); Ycur -н- stsize; if (Ycur > Ymaxl) Ycur - Ymaxl; lseg();cur(); 4 prnum(280, Ycur); break; --/* Down V /* Вниз */ default: message ("Неверно"); /* "Invalid" */ } } else if (ch--'>') { stsize *- 2; if (stsize > 200) stsize prnum(120, stsize); } else if (ch --'<') { stsize/-2; if (stsize - - 0) stsize - 1; prnum(120, stsize); } else if(ch--JP') { ch - getch(); ch - toupper(ch); switch (ch) { case 'D': penposition(l); break; case 'U': penposition(0); break; case 'P': dmode(1); break; case 'N': dmode(-1); break; case 'A': dmode(0); break; default: message("P ???"); } } else message ("Неверно "); /* "Invalid" */ } to_text(); } window() { Xminl - 12; Xmaxl -X_max-4; Yminl -22; Ymaxl - Y_max- 14; imove(Xmin1, Yminl); idraw(Xmax1. Yminl); idraw(Xmax1, Ymaxl); idraw(Xmin1, Ymaxl); idraw(Xmln1, Yminl); textXY(8, 0, /* "Use the four arrow keys to move the cursor." */ "Используйте четыре клавиши со стрелками для перемещения курсора,"); imove(8, 11); text /* "Use > to increase, or < to decrease the stepslze." */ ("клавиши > для увеличения или < для уменьшения шага,"); text ("клавишу Q для выхода"); /* "Press Q to quit." */ Ybottext-Ymaxl +3; textXY(8, Ybottext. "Размер шага:"); /* "Stepslze:" */ textXY(168, Ybottext, "X -"); textXY(248, Ybottext, "Y -"); } - 200; /* Maximum step size - 200 */ /* Максимальный размер шага - 200*/ 6»»
164 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ сиг() { int i, j, dm; dm-drawmode; drawmode-O; /* change color, using xor */ for (j—2; j<-2; j+-4) /* изменение цвета операцией XOR*/ for (i—4; i<-4; i++) dot(Xcur+l, Ycur+j); dot(Xcur-4, Ycur-1); dot(Xcur-3, Ycur-1); dot(Xcur+3, Ycur-1); dot(Xcur+4, Ycur-1); dot(Xcur-4, Ycur); dot(Xcur-3, Ycur); dot(Xcur+3, Ycur); dot(Xcur+4, Ycur); dot(Xcur, Ycur); dot(Xcur-4, Ycur+1); dot(Xcur-3, Ycur+1); dot(Xcur+3, Ycur+1); dot(Xcur+4, Ycur+1); drawmode-dm; } prnum(X, num) int X, num; { char str[4]; sprintf(str, "%3d". num); textXY(X. Ybottext, str); } message(str) char *str; { textXY(Xmax1 - 64, Ybottext, str); } lseg() /* If pendown, draw line segment from (Xold, Yold) to (Xcur, Ycur) */ /* Если перо опущено, то вычерчивается отрезок прямой линии из */ /* точки (Xold, Yold) в точку (Xcur, Ycur) */ { static int prevdown, /* Was previously a line segment drawn to old point? */ /* Был ли проведен отрезок прямой в старую точку? */ prevdirection, direction; if (pendown) { drawJinepCold, Yold, Xcur, Ycur); direction- Xcur < Xold ?-1 : Xcur > Xold? 1 : Ycur < Yold? -2: Ycur > Yold ? 2 : 0; /* If prevdirection + direction --0, and drawmode--0, a line */ /* segment just drawn is erased; beware of an extra call of dot */ /* in the point of reversal, performed in drawjine. */ /* Если следующее условие удовлетворяется, то только что */ /* вычерченный отрезок удаляется; при этом в функции */ /* draw_lineHeo6xoflHMo обязательно учитывать дополни- */ /* тельное обращение в точке возврата к функции dot. */ if (prevdown && drawmode- -0 && prevdirection + direction - - 0) dot(Xold, Yold); /* fourth call of dot in (Xold, Yold) */ /* четвертое обращение к функции dot в точке (Xold, Yold) */ } prevdown - pendown; prevdirection - direction; }
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 165 penposition(p) int p; /* Display pen position: 1-down, 0-up */ /* Отображение положения пера: 1 — опущено */ /* 0 — поднято */ { Int dm; dm-drawmode; drawmode-1; textXY(0, p ? 27 : 40, " "); /* clear old pen portion */ /* стирание части старого пера */ textXY(0, p? 32:27, "Н"); textXY(0, p ? 40 : 35, "V"); /* a picture of a pen */ /* изображение пера */ draw_llne(0, 48, 6, 48); /* the paper on which the pen writes */ /* бумага, на которой пишет перо */ pendown-p; drawmode-dm; } dmode(m) int m; /* 1 - Positive, -1 - Negative, 0 - Alternate */ /* 1 - Черчение, -1 - Стирание, 0 - Переключение */ { textXY(0. 55, m-- 1 ? "P": m---1 ? "N": "A"); drawmode - m; } 6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ Очень много свойств нашей программы автоматизированного проектирования еще предстоит выяснить и было бы просто ужас- но, если бы мы попытались обсудить длинную серию программ, каждая из которых является усовершенствованием предыдущей. И, например, программы CURSOR.C и SKETCH.С были бы пер- выми двумя программами в такой серии. Хотя такое развитие снизу-вверх и привело бы к созданию окончательной программы автоматизированного черчения, но теория рекомендует выпол- нять разработку сложного программного обеспечения сверху- вниз. Поэтому начиная с данного момента обсудим все возмож- ности нашего конечного продукта — системы с интерактивной графикой, сначала в терминах пользователя и только потом об- ратимся к исходному тексту программы. Остальную часть этого параграфа можно рассматривать как инструкцию для пользова- теля системы автоматизированного проектирования DIG. Некоторые аспекты, рассматривавшиеся в предыдущих пара- графах будут здесь повторены в общих чертах, поэтому эта инст- рукция для пользователя будет независимой от остальной книги.
166 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ Было бы очень неплохо все описываемые команды сразу ис- пытывать на практике во время изучения. Поэтому даже если намерения читателя чисто теоретические, автор рекомендует купить у издателя программный диск с программой DIG для по- вышения эффективности работы. 6.4.1. Начало и окончание программы. Рабочие состояния Начать выполнение программы можно двумя способами, а именно, напечатав на консоли либо DIG либо DIG-P (вместо прописных букв можно вводить и строчные) Дополнительный параметр -Р должен указываться в том слу- чае, если имеется матричный принтер (подключенный к парал- лельному порту компьютера) и есть намерение получать в про- цесссе работы твердую копию графических результатов, отобра- жаемых на экране. Действие этого параметра заключается в том, что вертикальные и горизонтальные размеры на принтере будут соответствовать друг другу или, другими словами, окружности будут печататься в виде окружностей, а квадраты — в виде квад- ратов. Это достигается путем переопределения размеров при вы- воде изображений на экран, так что на принтере они будут напе- чатаны точно. Цена, которую приходится платить за это, выра- жается в несоответствии вертикальных и горизонтальных размеров при выводе на экран, поэтому при указании параметра -Р окружности отображаются на экране в виде эллипсов. Если параметр -Р не вводится, то появляется противоположный эф- фект. В этом случае окружности выглядят как окружности на эк- ране, но будут отображаться как эллипсы на принтере. Заметим, что вывод на печать (заданием команд РО, PI, P2, описываемых ниже) может производиться и при отсутствии параметра ^-Р, только тогда соотношения размеров при выводе на принтер не будут выдержаны. После запуска программа запросит ввести имя файла. Сфор- мированная в процессе работы картинка будет впоследствии за- писана в этот файл, поэтому следует соблюдать осторожность и не указывать имя существующего файла с ценной информацией!
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 167 Лучшим способом предотвратить эту случайность является при- менение расширения имени файла, например, .PIC для файлов картинок. После ввода имени файла, например, DRAWING.PIC на экране появится изображение небольшого меню. Теперь мож- но ввести букву Н для вывода информационного сообщения помощи или перейти к одному из двух графических " рабочих состояний", имеющих название "черчение линий" или "альфа" (текст). Команды для перехода в эти состояния обозначены WL и WA соответственно. Состояние "альфа" нужно в тех случаях, когда на картинке желательно отобразить текстовую информа- цию. Предположим, что сначала мы хотим вычертить линии на картинке, поэтому введем команду: WL Эта команда изменяет содержимое всего экрана. На экране появляется рамка и некоторые текстовые надписи сверху и сни- зу, как показано на рис. 6.4. Надпись в верхней части экрана напоминает, что возврат в текстовый режим осуществляется по команде WT. После чего можно будет использовать команду Q для выхода из программы. Информация, содержащаяся в картинке, будет записана в файл, Нажмите WT вля перехода в текстоеьй режим Ражим черчения пиний. Шаг курсора: В X = 30 У = 30 Рис. 6.4. Исходное изображение на экране
168 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ имя которого указывалось в начале работы программы. Если нет необходимости в сохранении картинки, то вместо Q можно про- сто использовать комбинацию Ctrl-C. 6.4.2. Курсор, положение пера и режимы черчения Как правило, перед тем как будут введены команды WT и Q, мы пожелаем вычертить на экране некоторую картинку. Это может быть сделано различными способами. Как уже упомина- лось, вблизи левого верхнего угла окна изображается небольшой квадратик, называемый курсором. Он может перемещаться по экрану при нажатии одной из четырех клавиш со стрелками, рас- положенными в правой части клавиатуры. По умолчанию раз- мер шага перемещения курсора равен восьми пикселам. Это зна- чение можно увеличить или уменьшить путем нажатия на кла- виши *>' или '<' соответственно. Обратите внимание — текущее значение размера шага отображается в нижнем левом углу экра- на. Если теперь нажать на клавишу со стрелкой вправо, то кур- сор переместится на один шаг вправо и так далее. Слева на экра- не показано небольшое изображение пера с кончиком, располо- женном несколько выше короткого горизонтального отрезка прямой линии, условно изображающего лист бумаги. При таком положении пера любое перемещение курсора не будет вызывать появления на экране каких-либо линий. Но ситуация изменится, если ввести команду: PD которая означает 'Pen Dowa' ("перо опустить"). По этой команде изображение пера в левой части экрана опустится и его кончик будет соприкасаться с горизонтальным отрезком (как бы касаясь бумаги). Если теперь перемещать курсор, то перо будет вычер- чивать линию, подобно нормальному перу. Перо может быть снова поднято командой PU что означает 'Pen Up' (то есть "перо поднять"), затем его можно опустить где-то в другом месте и так далее. Но в противополож- ность обычному перу, это воображаемое перо может стирать линии, которые были начерчены ранее. Буква А, отображаемая как раз под изображением пера на левом краю экрана, означает, что включен режим "переключения" ("альтернативный"),
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 169 являющийся одним из трех "режимов черчения". Два остальных режима называются "положительный" (Р) и "отрицательный" (N). Соответствующие команды PA, PP, PN вызывают переход в режим черчения, обозначаемый второй буквой в названиях этих команд. После команды РР перо может только чертить аналогич- но обычному перу. Но после команды PN происходит нечто про- тивоположное — перо может только стирать. Поэтому в отрица- тельном режиме черчения перемещение курсора вызовет гаше- ние всех затрагиваемых пикселов. В режиме "переключения" состояния пикселов инвертируются. На темном экране его дейст- вие аналогично "положительному" режиму черчения, но когда путь курсора пролегает через пиксел, который уже был ранее подсвечен, этот пиксел будет переведен в темное состояние. Как и при черчении, стирание может иметь место только в том слу- чае, если перо опущено. Это описание может показаться не- сколько запутанным, но практически все очевидно, поскольку текущее положение пера (поднято/опущено) и текущий режим черчения (P/N/A) отображаются на левом краю экрана. В нижней части экрана отображается не только размер шага, но и пикселные координаты X и У в числовом виде. Это позво- ляет нам отмечать точное положение курсора, чтобы впоследст- вии можно было вернуться к некоторой точке. Однако для этого есть и более мощное средство, как увидим в параграфе 6.4.4., где будет введено понятие "маркированных точек". Для обсуждае- мого здесь способа черчения будем в дальнейшем использовать термин "эскизирование". Этот способ приемлем только для про- стых применений, поскольку он не обеспечивает вычерчивания наклонных линий. Перед рассмотрением более интересных воз- можностей вычерчивания линий обсудим сначала очень простой режим — рабочее состояние "альфа" (текст). 6.4.3. Рабочее состояние "альфа" (текст) Как упоминалось выше, переход в состояние "альфа" (текст) происходит по команде WA. Эта команда как для перехода из текстового режима системы, так и из состояния "вычерчивания линий" (применение более общего термина "режим" вместо "со- стояние" может привести к некоторой путанице, поскольку этот термин уже применялся ранее для трех "режимов черчения" P,N,A). В состоянии "альфа" все вводимые символы будут появ-
170 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ ляться на экране в позиции курсора. Поэтому куски текста могут быть внесены очень легко, так же, как при работе с обычным эк- ранным редактором (но теперь есть возможность задавать между строками любое расстояние, которое нравится!). Для коррекции ошибок при печати можно использовать клавишу возврата. При этом курсор переместится влево и неправильный символ будет удален. Клавиша пробела дает аналогичный эффект, но нажатие на нее вызывает перемещение курсора вправо. И даже прекрас- но, что эти две клавиши могут быть использованы для удаления не только текстовой информации, но и частей картинки! Поэто- му, если в состоянии черчения линий было что-то нарисовано, что затем не понравилось, то у нас есть средство для удаления этой части — задавая команду WA и комбинируя перемещение курсора с нажатием клавиш возврата и пробела. Поскольку любые символы, вводимые с клавиатуры в состоя- нии "альфа", будет только копироваться на экране, нам нужно какое-то какое-то средство для выхода из этого состояния. Здесь можно воспользоваться особенностью клавиши Ctrl. Для выхода из состояния "альфа" следует нажать на клавишу Ctrl и, удер- живая ее в нажатом состоянии, нажать на клавишу W, а затем на клавишу L (после нажатия на клавишу W клавишу Ctrl удержи- вать не обязательно). В верхней части экрана всегда сообщается, как перейти к текстовому состоянию, с нажатием или без нажа- тия на клавишу Ctrl. Так можно непосредственно перейти из со- стояния "альфа" в состояние "вычерчивания линий" и наоборот. 6.4.4. Наклонные линии и наборы маркированных точек Теперь рассмотрим прямые линии, которые не обязательно должны быть горизонтальными или вертикальными. Например, пусть нужно вычертить треугольник ABC, где А, В, С могут быть любыми тремя точками внутри экранного окна и не лежащими на одной прямой линии. Выполним следующие действия: 1. Переместим курсор в точку А и введем команду SA. 2. Переместим курсор в точку В и введем команду L. 3. Переместим курсор в точку С и введем команду L. 4. Переместим курсор в окрестность точки А и введем сначала команду F, затем L. 5. Введем одну из команд SI или SC.
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 171 Для понимания выполняемых действий, необходимо знать, что при этом используется набор (или коллекция) маркирован- ных точек. Первоначально этот набор пуст. При выполнении це- лого ряда команд текущая точка отмечается косым крестом (*) и одновременно добавляется в этот набор. Так команда SA делает только это и ничего больше, но есть команды, выполняющие од- новременно и другие действия. Например, команда L тоже отме- чает текущую точку (и добавляет ее к набору), но ее основная задача заключается в вычерчивании отрезка прямой линии меж- ду предыдущей маркированной точкой и текущей точкой. Команда F применяется для поиска ближайшей маркирован- ной точки и перемещения в нее курсора. Это позволяет очень бы- стро установить курсор в позицию, совпадающую с некоторой маркированной точкой. В примере с треугольником ABC вначале не предполагалось, что точки А, В, С задавались как маркиро- ванные точки. Но когда мы чертим сторону СА, то точка А счи- тается маркированной точкой и здесь мы воспользуемся полез- ными свойствами команды F. Без нее мы встретились бы с неко- торыми трудностями при возврате курсора точно в точку А. На последнем шаге мы можем выбрать между командами SI ('Set Invisible' — "Набор невидимый") и SC ('Set Clear' — "Очи- стить набор"). Обе команды стирают крестики с экрана, но их не следует путать. Хотя крестики исчезают в обоих случаях, коман- да SI не изменяет набор и команда F может быть использована по-прежнему. Поэтому после выполнения этой команды список маркированных точек оставался прежним, только маркеры сде- ланы невидимыми. Их можно опять сделать видимыми той же са- мой командой. Команда SI использует "переменный" режим и она просто "переключает" маркеры из видимого состояния в не- видимое и обратно. Команда SC — наоборот, стирает весь набор маркированных точек, поэтому после ее выполнения нет ни од- ной маркированной точки и команда F больше не сможет рабо- тать. Ради полноты упомянем, что есть еще команда SD ('Delete a single Set point' — "Удалить одну точку из набора"). Если сов- местить курсор с некоторой маркированной точкой (предпочти- тельно с помощью команды F!), а затем ввести команду SD, то эта точка будет удалена из набора и маркер этой точки исчезнет, если он был виден в данный момент. Набор может содержать до 4 000 маркированных точек.
172 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 6.4.5. Команды блокирования Иногда желательно иметь возможность манипуляций с опре- деленной частью картинки. Блоком будем называть прямоуголь- ную область с горизонтальными и вертикальными сторонами в пределах экранного окна. Блок полностью определяется коорди- натами верхнего левого и нижнего правого углов. Эти точки бу- дем называть началом и концом блока. Для задания любой из этих точек может быть использована текущая позиция курсора, если применить следующие команды: ВВ ('Block begin' — "Начало блока": текущая пози- ция находится в верхнем углу) ВК ('Block end' — "Конец блока": текущая позиция находится в нижнем правом углу). (Как побочный эффект эти две точки маркируются и, следова- тельно, добавляются к набору маркированных точек). Сам пря- моугольник может стать видимым после ввода команды ВК. Не обращайте внимания на протяженные вертикальные и горизон- тальные прямые линии, появляющиеся в результате выполнения команды ВВ — как только будет введена команда ВК они будут отсечены на нужном расстоянии. Если желательно удалить ли- нии прямоугольника (оставив определение блока), то можно просто ввести команду BE ('Block erase' — "Стирание блока"). Эта команда удаляет только прямоугольник, являющийся грани- цей блока, содержимое блока остается без изменения. Напротив, команда BD ('Block delete' — "Удаление блока") удаляет весь блок целиком, включая его содержимое. Посмотрим теперь, для чего нужен блок. Блок можно скопировать и переместить, ис- пользуя команды ВС и ВМ соответственно. В этих командах те- кущая позиция курсора применяется для обозначения позиции нового блока (то есть левого верхнего угла), который будет соз- дан. По команде ВС блок копируется, а исходный блок остается без изменения. Этого не происходит при задании команды ВМ, которая фактически переносит блок в новую позицию, удаляя старый блок. Заметим, что в любой момент времени может су- ществовать только один блок, поэтому как только блок был опре- делен, не возникает никаких вопросов относительно того, к ка- кому блоку относится команда.
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 173 Есть еще две очень важные команды: BW CBlock write' — "Запись блока") и BR CBlock read' — "Чтение блока"). Эти ко- манды выводят в верхней части экрана сообщение, запрашивая ввод имени файла. Затем по команде BW блок записывается в указанный файл, а по команде BR блок считывается из файла и размещается на экране так, чтобы текущая позиция курсора на- ходилась в точке начала блока. Как и в параграфе 6.4.1, не меша- ет снова предупредить в отношении имени файла, поскольку при задании имени существующего файла его содержимое командой BW будет уничтожено. Здесь также рекомендуется применять особое расширение для имен файлов, например, .SYM. В таких файлах удобно хранить некоторые особенно часто используемые символы. Вместо рисования символа заново каждый раз, когда он необходим, его можно просто "загрузить" из файла и разместить на нужном месте. Здесь не без причины предложено иное расши- рение (.SYM), отличающееся от расширения файла (.PIC), при- меняемого для записи всей картинки (см. параграф 6.4.1). Эти два файла несовместимы в отношении их формата данных, поэ- тому их нельзя смешивать. 6.4.6. Векторы, окружности и дуги В математике векторы обычно изображаются в виде стрелок. Перед обсуждением использования векторов, рассмотрим снача- ла, как можно определить вектор. При вводе команды I позиция курсора определит начальную точку вектора, аналогичным об- разом команда Е определит его конечную точку. Таким образом вектор будет направлен из точки I в точку Е. Точка I маркирует- ся прямым крестом (+), отличающимся от косого креста (*), ко- торым маркируется набор точек. После ввода команды Е на экра- не появляется стрелка, кончик которой совпадает с точкой Е. Полная окружность с центром в точке I и радиусом IE вычер- чивается по команде CF при условии, что вся окружность попа- дает в пределы границ экрана. Если нет, то появляется сообще- ние об ошибке. Если все-таки в этом случае нам нужно, чтобы окружность была начерчена в той ее части, которая находится в границах экрана, то вместо команды CF следует задавать описы- ваемые ниже команды С+ или С- указывая в качестве параметра угол 360°. Если заранее известно, что окружность не выйдет за пределы экрана, то лучше применять команду CF, поскольку по
174 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ этой команде окружность формируется гораздо быстрее, чем по командам С+ и С-. Как правило желательно, чтобы стрелка IE исчезла, когда она больше не нужна. Это произойдет при опреде- лении новой точки в качестве начальной точки I. Команды I и Е имеют побочный эффект, заключающийся в том, что точки I и Е также заносятся в набор точек и, следовательно, отмечаются косым крестом (*). Как мы видели в параграфе 6.4.4, от этих маркеров можно избавиться с помощью команд SI, SD, SC. Если нужна не полная окружность, а только некоторая ее часть (дуга), то применяются команды С+ и О. Центр окружно- сти опять будет располагаться в начальной точке вектора I, кото- рый был перед этим определен. Дуга будет вычерчиваться, начи- ная с конечной точки вектора Е, против часовой стрелки при за- дании команды С+ или по часовой стрелке, если была задана команда С-. Однако нужно еще сообщить дополнительные дан- ные о положении конечной точки на дуге. Если курсор находится всеещев точке Е вектора, когда вводится команда С+ или С-, то программа запросит ввести значение угла в градусах между пря- мыми линиями IE и IP, где буквой Р обозначена конечная точка дуги. Дуга на рис. 6.5а начерчена именно таким образом при за- дании команды С+ и угла 90°. (а) (б) Рис. 6.5. Дуга, (а) заданоого угла, и (б) заканчивающаяся на заданной линии Вначале отображается стрелка вектора IE, аналогичная все еще видимому вектору на рис. 6.56, и затем дуга вычерчиевается в направлении вверх. Если команды С+ или С- будут заданы ког- да курсор уже не совпадает с точкой Е, то запроса на ввод угла не последует, а конечная точка дуги будет лежать на прямой линии, проходящей через точку I и текущую позицию курсора. Этот способ был применен при генерации изображения на рис. 6.56 и он полезен в тех случаях, когда известно положение прямой ли-
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 175 нии, на которой должна лежать конечная точка дуги, а не угол дуги, выраженный в градусах. Обратите внимание, на рис, 6.5а стрелка и специальный маркер (+) в точке I исчезли. Это прои- зошло в тот момент, когда была определена новая точка I, как центр дуги на рис. 6.56. Последняя точка I все еще имеет этот маркер вместе с маркером (х), используемым в наборе точек. Оба эти маркера (а также и все остальные) можно стереть, как было описано выше. Имеются еще дополнительные команды для работы с векто- рами. Они образованы из двух букв, первая из которых всегда V: VS Замена вектора на обычный (вычерчиваемый) отрезок прямой; VD Замена вектора на штриховой отрезок; VL Замена вектора на длинную прямую линию, про- ходящую через точки I и Е. Длина линии ограни- чена границами экрана; VK Сохранение стрелки. После этой команды стрел- ка не исчезает после ввода команды I для опреде- ления новой начальной точки I. Однако после ввода этой команды старый вектор недоступен, если, например, должна быть вычерчена окруж- ность; VA Кончик стрелки вектора делается невидимым, ес- ли он был видим, и наоборот. На экране эффект выполнения этой команды кажется таким же, как для команды VS. Но после команды VS вектор больше недоступен, тогда как после команды VA вектор остается. Можно сказать, что команда VA переключает видимость головки стрелки. Следующие команды также делают видимые объекты невидимыми, и наоборот: + Переключает видимость маркера (+), используе- мого для обозначения начальной точки вектора; * Переключает видимость самого курсора; Переключает видимость точки, обозначаемой курсором.
176 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 6.4.7. Задание новой точки Мы можем определить направление, а затем использовать его для задания новой точки. В последующих трех командах направ- ление играет существенную роль, поэтому их имена начинаются с буквы D: DD Задание направления; DU Установка единицы длины, которая будет приме- няться в связи с предыдущей командой; DN Задание новой точки. После ввода команды DD программа запросит ввести либо угол (в градусах), либо звездочку (*). Если введем угол, то он оп- ределяет конечное направление воображаемого вектора, внача- ле направленного горизонтально вправо, а затем повернутого на заданный угол в положительном направлении против часовой стрелки. Если вместо угла будет введен символ звездочки, то пе- ред этим должен быть определен вектор (см. параграф 6.4.6), на- правление этого вектора и будет использовано. Не забывайте, что после ввода звездочки или значения некоторого угла необхо- димо нажать клавишу ввода ('Enter' или 'Return'). Если была указана звездочка, то будет запомнено направление существую- щего в данный момент вектора, если даже потом будет определен новый вектор. Это очень важно, особенно в связи с командами DU и DN, как увидим ниже. Команда DU позволяет определить новую единицу длины, которая по умолчанию была определена как один дюйм. Перед вводом команды DU обязательно должен быть определен вектор IE. После ввода команды DU на экране появится следующий вопрос: How many units? ("Сколько единиц?") Если введем число 1, то в качестве новой единицы будет ис- пользована длина текущего,вектора. Можно также ввести целое число больше 1, например, п. Тогда единица длины будет такой, что текущий вектор имеет длину п. Если теперь поместить кур- сор в некоторой точке, обозначив ее через Р, и ввести команду DN, то появится запрос на ввод либо значения длины, либо звез- дочки. Длина должна быть выражена во вновь определенных единицах, она может быть любым вещественным числом. После этого конструируется новая точка, скажем Q, такая, что отрезок
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ ill PQ имеет заданную длину, а его направление определено раньше по команде DD. Это правило будет действовать только в том слу- чае, если точка Q лежит в пределах окна экрана. Если точка на- ходится за пределами экрана, то будет выдано сообщение об ошибке. Как и в случае команды DD, после команды DN можно ввести звездочку (с последующим нажатием клавиши ввода). В этом случае вновь конструируемая точка Q будет лежать на воображаемой прямой линии, проходящей через начальную точ- ку I и конечную точку Е текущего вектора. Таким образом, при использовании этого средства должен существовать вектор, рас- положенный где-то на прямой линии, которой должна принадле- жать точка Q. Полезным применением этих команд может быть деление за- данного отрезка прямой линии АВ на несколько (допустим п) отрезков одинаковой длины. До настоящего момента вектор оп- ределялся как направленный отрезок, начало которого совпадает с точкой А отрезка, а конечная точка — с точкой В. Теперь вве- дем команду DU и после появления запроса "Сколько единиц?" зададим число п. Для определения точного направления введем команду DD со звездочкой. Затем переместим курсор в точку А (с помощью команды F) и введем команду DN. При появлении запроса "Длина или *" ответим 1 и тем самым будет сконструи- рована первая точка. Снова введем DN и 1 и так далее, пока не появятся все искомые точки в тех местах, где они должны нахо- диться. Поскольку все новые точки отмечены маркером (*) и до- бавлены к набору, к ним легко можно будет вернуться в любой момент, применяя команду F. Кроме деления отрезка на п частей его можно и расширить так, что новый отрезок прямой линии будет точно в п раз длин- нее исходного отрезка. Снова применим команду DU к заданно- му отрезку прямой линии АВ (после приравнивания его к векто- ру командами I и Е), но теперь присвоим ему длину 1. Поместив курсор в точку А, введем команду DN и затем число я, которое соответствует желаемой точке. 6.4.8. Применение матричного принтера; заполнение области Теперь обсудим четыре двухсимвольные команды, в которых первая буква Р. Если имеется матричный принтер, то на нем можно получить твердую копию сформированного к данному
178 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ моменту графического результата. Конечно, все маркеры в виде косого крестика (*) должны быть сначала сделаны невидимыми (применением команд SI или SC), поскольку они не нужны в окончательном изображении на твердой копии. Командой + нуж- но также удалить маркеры (+), обозначающие начальные точки векторов (см. параграф 6.4.6). Перед вводом команды печати нужно проверить, включен ли принтер. Размеры по горизонтали и вертикали в отпечатанной копии будут совпадать только в том случае, если был указан параметр -Р при запуске программы DIG (см. параграф 6.4.1). Если нет, то изображаемые на экране окружности при выводе на печать будут выглядеть как эллипсы. Команда печати состоит из буквы Р, за которой следует одна из трех цифр 0,1,2. Запомните, чем больше цифра в команде, тем больше чертеж. РО На печать выводится все, что находится внутри окна, сама рамка окна не выводится; Р1 Печатается все в пределах окна, включая рамку окна, но текст вокруг не печатается; Р2 Печатается содержимое всего экрана, включая текст по краям экрана. Закончим этот параграф описанием команды, ничего общего не имеющей с выводом на печать, за исключением первой буквы в имени команды. Команда PF служит для заполнения всей замкнутой области подсвеченными пикселами, начиная с точки текущей позиции курсора. Границы области могут быть составлены из любых комбинаций отрезков прямых линий и дуг окружностей. Если в области есть отверстия (определяемые более мелкими замкнутыми контурами, но кур- сор находится вне их), то они не будут заполнены. Здесь есть очень важный момент, заключающийся в том, что область дейст- вительно должна быть замкнутой. Например, если для полигона пропущен хотя бы один пиксел в границе, а это вполне можно не заметить, тогда даже маленькая замкнутая область, которую намеревались заполнить, может оказаться гораздо больше, чем предполагалось. В этом случае эффект может оказаться ужас- ным, поскольку окажутся подсвеченными значительно больше пикселов, чем ожидалось, вплоть до того, что вся картинка
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 179 может оказаться испорченной. Этот эффект, сравнимый с поло- водьем, назовем "заливкой". Поэтому может оказаться полез- ным сначала сохранить всю картинку, что обычно делается перед завершением работы программы. То есть можно остановить про- грамму и сделать копию файла картинки, например, командой DOS: COPY DRAWING.PIC DRAWING.BAK и запустить программу на выполнение вторично с исходным файлом. Если в этом случае произойдет заливка, то у нас ока- жется сохраненным файл DRAWING.BAK. 6.4.9. Кривая типа В-сплайна Не все чертежи образуются исключительно набором отрезков прямых линий, окружностей и их дуг. Хорошо известный способ проведения гладкой линии, примерно проходящей через не- сколько заданных точек называется сглаживанием кривой типа В-сплайна. Это очень гладкая кривая или, более точно, кривая с непрерывными вторыми производными. Подробно этот способ сглаживания описан в книге автора "Принципы программирова- ния в машинной графике". Здесь его будем просто использовать, не вдаваясь глубоко в математические основы. В общем, кривая, составленная из В-сплайнов, не будет проходить точно через заданные точки. Будем различать открытые и замкнутые кри- вые. Предположим, что определена последовательность из п + 1 точек Р0, Pj,..., Рп, где п не менее 3. Рассмотрим вкратце как это можно сделать. Имеются две команды для подбора кривой, имя каждой образуется из двух букв: SF п + 1 точек используются для вычерчивания (или стирания) кривой, проходящей вблизи л-1 точек Pi, P2,...Pn-i (Буква F означает Fitting - "под- бор") SR п + 1 точек используются для вычерчивания (или стирания) замкнутой кривой, проходящей вбли- зи этих точек (Буква R означает Round e "Круг- лый") Поскольку применяется режим "переключения", то кривые могут попеременно вычерчиваться и стираться.
180 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ В параграфе 6.4.4 обсуждался набор маркированных точек. Этот набор теперь будет рассматриваться как последователь- ность точек, то есть имеет значение порядок следования точек. Хотя здесь мы будем иметь дело с тем же объектом, что и в пара- графе 6.4.4, но теперь не будем его называть набором, поскольку в наборе, по определению, элементы не упорядочены. Однако в последовательности порядок элементов очень важен. Например, записи {Рр Р2, Р3} и {Р2, Р3, Pj} обозначают один и тот же на- бор, но это различные последовательности. Так что теперь будем применять термин "последовательность" для того же, что ранее называлось набором. Команда SA и некоторые другие команды, например, I, обыч- но расширяет последовательность в конце. Курсор можно пере- мещать от одной точки в последовательности к другой, исполь- зуя следующие четыре команды (имя каждой составлено из двух символов): S< Переместить курсор в начальную точку (Ро) последовательности; S> Переместить курсор в конечную точку (Рп) последовательности; S] Переместить курсор в следующую точку (если она есть); S [ Переместить курсор в предыдущую точку (если она есть). Если эти команды или команда F ("Найти") была использо- вана для перемещения курсора в некоторую точку последова- тельности, допустим в Р^., а затем переместим курсор в некото- рую другую точку, скажем, Q, тогда команда SA приведет к включению новой точки Q немедленно после Р^ Тогда все точки после Р. будут сдвинуты на одну позицию вправо. Аналогично команда SD, описанная в параграфе 6.4.4, удалит точку из по- следовательности и все последующие точки будут сдвинуты на одну позицию влево. Итак, теперь мы имеем некоторое средство для редактирования последовательности маркированных точек с учетом порядка следования элементов. Чэсто для подбора кривой бывает необходима не вся последо- вательность, а только часть ее. Она может быть задана путем
64. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 181 установки вектора IE между начальной точкой I и конечной точ- кой Е в последовательности. Если I это точка Р^ а Е — точка Р., то будем иметь *>0 / - i > 2 j<n Точки Р^, где к < i или к > у, будут проигнорированы. После конструирования кривой обычно желательно посмот- реть ее на экране без всяких маркеров (х). Для их стирания мож- но использовать команду SI (см. параграф 6.4.4). Нужно быть ос- торожным в применении команды SC слишком рано, поскольку после ее исполнения все маркированные точки будут потеряны, тогда как после команды SI они могут быть снова изображены на экране при вторичном вводе этой же команды. 6.4.10. Сводка команд Ниже приводится список всех команд из параграфа 6.4, кото- рый может быть полезным для быстрого получения необходимой справки. Главное меню команд, появляющееся в начале работы программы WL Переключение в состояние черчения линий WA Переключение в состояние "альфа" для вывода текста Q Выход: графический результат будет сохранен Ctrl-C Немедленный выход Н Помощь (справка) При нажатии клавиши Н появится вторичное меню: Нажмите: 1 Для начала программы и переключения состояний 2 Для команд курсора, положения пера и режимов черчения 3 Для состояния "альфе? — вывода текста 4 Для команд манипуляций с набором маркированных точек
182 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 5 Для команд определения блока 6 Для векторов, окружностей, дуг 7 Для направлений, единиц длины, новых точек 8 Для применения матричного принтера и заполнения области 9 Для подбора кривой типа В-сплайна М Возврат в главное меню. При нажатии одной из клавиш 0, 1,..., 9 появится соответст- вующий параграф пояснения, как будет показано ниже. После этого можно ввести любую другую цифру для отображения соот- ветствующей группы команд или нажать клавишу М или Н для отображения главного или справочного меню. (1) Начало программы и переключение состояний DIG Пуск программы; соотношение размеров сохра- няется на экране. DIG -Р Пуск программы; соотношение размеров сохра- няется на матричном принтере. При выводе на принтер углы будут сохранять свое значение, а окружности будут изображены в виде окружностей в том случае, если будет задан параметр -Р. Если точные соотно- шения более важны на экране, а не на принтере, то пара- метр -Р задавать нельзя. WL Переключение в состояние черчения линий. WA Переключение в состояние "альфа" для вывода текста в графическом формате. WT Переключение в текстовое состояние для выхода или для запроса справки Замечание: В графическом состоянии "альфа" для переключения состо- яния при вводе буквы W должна быть нажата управляющая клавиша Ctrl.
6.4. РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 183 (2) Команды курсора, положение пера и режимов черчения Курсор перемещается нажатием клавиш со стрелками. Размер шага увеличивается или уменьшается нажатием на клавиши > или <. PU Поднять перо. PD Опустить перо. РР Перо в положительном режиме черчения: в опу- щенном состоянии перо чертит. PN Перо в отрицательном режиме черчения: в опу- щенном состоянии перо стирает. РА Перо в режиме переключения: в опущенном со- стоянии перо вызывает "переключение" пиксе- лов. Следующие команды могут вызвать странный эффект, поэ- тому при их использовании необходимо соблюдать осторож- ность: * Переключает курсор. Переключает точку (внутри курсора). + Переключает О-курсор ("оболочку" курсора). (3) Рабочее состояние "Альфа" На чертеже появляется текст по мере ввода символов с клави- атуры. Для внесения корректив можно использовать клавишу возврата. При вводе команд переключения состояния WT и WL первую букву команды W следует вводить при нажатой клавише Ctrl. (4) Команды для работы с набором маркированных точек Все точки в наборе маркируются косым крестом (х) SA Добавление точки к набору. SD Удаление точки из набора. SC Стирание всего набора. SI Переключение видимости точек в наборе (видимые/невидимые).
184 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ F Нахождение ближайшей точки в наборе и пере- мещение курсора в эту точку. L Использование самой последней добавленной к набору точки в качестве "старой" точки. Затем вычерчивается отрезок прямой линии из старой точки в текущую позицию курсора. Кроме ко- манды SA команды L и I также добавляют точки к набору. См. также (9). (5) Команды определения блока вв вк BE BD ВС ВМ BW BR Начало блока, верхний левый угол. Конец блока, нижний правый угол. Удаление (или вычерчивание снова) границы блока. Удалить весь блок. Копирование блока. Перемещение блока. Запись блока. Считывание блока. [ (6) Векторы, окружности и дуги I Определение начальной точки I вектора. Е Определение конечной точки Е вектора. VA Переключение видимости стрелки вектора (не- видимая/видимая) . VS Замена вектора на штриховой отрезок. VL Замена вектора на длинную прямую линию. VK Сохранение вектора: стрелка должна остаться. Для команд CF, C+, С- точка I определяет положение цент- ра окружности, а длина отрезка IE — ее радиус. CF Вычерчивание полной окружности. С+ Вычерчивание дуги окружности по заданному уг- лу в направлении против часовой стрелки. С- То же самое, но по часовой стрелке.
6.4, РУКОВОДСТВО ДЛЯ ПОЛЬЗОВАТЕЛЯ 185 (7) Направления, единицы длины, новые точки Следующие три команды запрашивают ввод числа или звез- дочки. Не забывайте нажимать клавишу ввода после оконча- ния ввода числа или звездочки. DU Перед обращением к этой команде должен быть обязательно определен вектор. На экране поя- вится вопрос 'How many units?' — "Сколько еди- ниц?". Если будет введено некоторое целое число п, то единица длины будет определена такой, что длина отрезка IE будет равна п, выраженной в этих единицах. Эта единица может применяться в последующих командах DN. DD Запрашивается угол относительно положитель- ного направления полуоси х для определения на- правления. Это направление будет использовано при последующих вызовах команды DN. Звез- дочка здесь будет обозначать, что для этой цели следует использовать направление вектора IE. DN Определение новой точки по текущему направ- лению на заданном расстоянии от текущей пози- ции курсора. Звездочка означает: поместить точ- ку на прямой линии IE. (8) Вывод на матричный принтер: заполнение области РО Распечатать все, находящееся внутри границ ок- на (исключая сами границы). Р1 Распечатать все внутри границ окна, включая сами границы. Р2 Распечатать содержимое всего экрана. PF Заполнить замкнутую область, внутри которой располагается курсор. Остерегайтесь "заливки", которая может произойти, если область не будет полностью замкнута. Перед выдачей этой коман- ды предварительно желательно удалить все мар- керы с помощью команды SI.
186 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ (9) Аппроксимация кривой В-сплайна Набор маркированных точек здесь рассматривается как по- следовательность. S< Переместить курсор в начальную точку последо- вательности. S> Переместить курсор в конечную точку последо- вательности. S [ Переместить курсор в точку,, предшествующую текущей маркированной точке. S ] Переместить курсор в последующую точку для текущей маркированной точки. SF Вычертить (или удалить) кривую, которая про- ходит примерно через маркированные точки, за исключением начальной и конечной точек. SR Так же, как и в случае SF, но точки обходятся циклически, образуя замкнутую кривую. Если точки I и Е совпадают с некбторыми точками в последо- вательности, то вместо полной последовательности маркирован- ных точек используется подпоследовательность, начинающаяся в точке I и кончающаяся в точке Е (отрезок IE будет показан в виде стрелки). Последовательность можно редактировать командами SD и SA (см. п. 4). После совмещения курсора с маркированной точ- кой (используя команды F, S ], S [) новая точка вводится сразу же после данной маркированной точки установкой курсора в новую точку и вводом команды SA. 6.5. ИСХОДНЫЕ ТЕКСТЫ. Исходный текст программы DIG состоит из четырех модулей: DIG.C Главная программа (параграф 6.5.1.). DIGFUN.С Большой набор функций (параграф 6.5.2.). DIGH.C Функции для вывода справочных сообщений (параграф 6.5.3.). GRPACK.C Пакет общих графических функций (описан в параграфе 4.5).
6.5. ИСХОДНЫЕ ТЕКСТЫ. 187 6.5.1. Текст программы DIG.C (основная программа) /*DIG.C: */ /* Рисование в интерактивном режиме. В этой программе */ /* используются функции из модулей DIGFUN.C, DIGH.C, GRPACK.C. */ /* Drawing with Interactive Graphics (L. Ammeraal/John Wiley & Sons) */ /* This program uses functions defined in DIGFUN.C, DIGH.C, GRPACK.C.*/ extern Int X max, Y max, drawmode, colorgr, int Xminl, Xmaxl, Yminl, Ymaxl, Xcur, Ycur, stsize-8, pendown, Xold. Yold, keepvector, Xb-100, Yb-100, Xk-100. Yk-100; char fil[30], workstate-T'; /* T - Textmode */ /* T- текстовый режим */ main(argc, argv) int argc; char **argv; { charch, ch1,str[2]; int gr_entered4), first—1, dm, pd; If (argc > 1) { if(argv[1I0]--'- &&toupper(argv[1I1])--'P,) setprdim(); /* 'setprdim' is defined in GRPACK.C */ /* функция 'setprdim' определена в модуле GRPACK.C*/ else { printf /* "Invalid program argument" */ ("Неверный аргумент программы"); exit(1); } } printf ("Имя файла:"); scanfC'%s", fil); /* "File name:" */ while (1) { if (workstate - - T') /* WT: textmode (- Initial state) */ /* WT: текстовый режим (исходное состояние) */ { if (first) { ch - 'M'; first - 0; } else ch - getche(); if (ch - - 3) exit(0); /* 3 - Ctrl-C In Turbo С */ /* 3 — код комбинации Ctrl-C */ ch - toupper(ch); if(ch--'Cr) { if (gr_entered && Icolorgr) { /* The picture is now written to file wait a moment.please. */ printf ("\nКартинка записывается в файл %s;", fil); printf(" подождите немного, пожалуйстаЛп"); save(); } exit(0); }
188 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ М'){ menu(ch); continue; } /*W-Workstate г W — рабочее состояние } if(ch--'H' II ch' if (ch- -Ж) { ch 1-getchef); ch 1 - toupper(ch 1); /* if(ch1--'A' II ch1--'L') /* WA: graphics alpha, WL: line drawing and sketch /* WA — вывод текста в графическом режиме /* WL — режим черчения линий и ввода данных { wstate(chl); grentered - 1; continue; } } menu('M'); continue; /* We are now in one of the two graphics workstates WL and WA /* Теперь система в одном из графических режимов — WL или WA ch-getch(); if (ch - - 0) /* This happens if an arrow key is pressed /* Это происходит при нажатии клавиш со стрелками { ch - getch(); Xold - Xcur; Yold - Ycur; switch (ch) { case 75: cur(); Xcur —stsize; if (Xcur <Xmin1) Xcur-Xminl; /* Влево lseg(); cur(); prnum(192, Xcur); break; /* Left case 77: cur(): Xcur -H- stsize; if (Xcur > Xmaxl) Xcur-Xmax1; lseg(); cur(); prnum(192, Xcur); break; case 72: cur(); Ycur — stsize; if (Ycur <Ymin1) Ycur-Yminl; lseg(); cur(); pmum(272, Ycur); break; case 80: cur(); Ycur +- stsize; if (Ycur > Ymaxl) Ycur - Ymaxl; lseg(); cur(); pmum(272, Ycur); break; } continue; /* Вправо /* Right Вверх Up /* Вниз /* Down } message(" "); if (workstate- -'A') { if (ch - - 23) /* Clear the message area /* Очистка области сообщений /* State WA: Graphics Alpha /* Текст в графическом режиме /*23--CtrlW */ V V */ V V V { ch1-getch(); ch1-toupper(ch1);
6.5. ИСХОДНЫЕ ТЕКСТЫ. 189 } if(chK32)ch1-+-64; /* Convert Ctrl-L Into L etc. V /* Преобразование Ctrl-L в L и т.д. */ if(ch1--'L' II ch1--T' II ch1--'A') { wstate(cM); continue; } else messagedCtrl-W ???"); } if (ch - -13) /* Return (or Enter) V /* Клавиша Return (или Enter) */ { penpositlon(0); /* up — вверх */ cur(); /* Erase old cursor */ /* стирание курсора */ Xcur-10; Ycur-н-15; if (Ycur> Ymaxl - 11) Ycur-Yminl + 1; cur(); /* Draw new cursor */ /* Новый курсор */ continue; } cur(); /* delete old cursor */ /* удаление старого курсора */ strfOHch - - 8 ? " : ch); /* 8 - backspace */ /* 8 — код стирания символа */ str[lK\0'; Xcur &- 0xFFF8; /* truncated to a multiple of 8 */ /* округление до кратности 8 */ if(ch--8) { Xcur —8; /* space to be written on position of last character */ /* в позиции последнего символа должен быть пробел */ if (Xcur<Xmin 1) Xcur-Xmln 1; } textXY(Xcur, Ycur, str); Xcur4-(ch^—8?0:8); if (Xcur>Xmax1) Xcur-Xmax1; cur(); coord(); continue; /* We are now in state WL: Line drawing and sketching */ /* Теперь система в состоянии WL — вычерчивание линий */ ch - toupper(ch); if(ch--V) { stsize *- 2; if (stsize > 200) stsize - 200; /* Maximum step size - 200 */ /* Максимальный шаг курсора - 200 */ pmum(112, stsize); continue; >
190 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ If (ch--'<') { stsize/-2; if(stsize--0)stsize-1; prnum(112, stsize); continue; } if(ch--'P') { ch1-getch(); ch1-toupper(ch1); switch (ch1) { case 'D': penpositlon(l); break; case 'IT: penposltlon(0); break; case 'P': dmode(1); break; case 'N': dmode(-1); break; case 'A': dmode(O); break; case '0': case T: case'2': hardcopy(ch1); break; /* output on matrix printer */ /* вывод на матричный принтер */ case 'F': cur(); pixflll(Xcur, Ycur); cur(); break; continue; } if (ch - -' W) /* workstate switching *7 /* переключение рабочего состояния */ { ch1-getch();ch1-toupper(ch1); switch (ch1) { case 'A': case 'L*: case HP: wstate(chl); break; default: messageCW ???и); } continue; > dm-drawmode; pd-pendown; dmode(0); penposltion(0); iffch-^B') { ch 1-getch(); ch 1-toupper(ch 1); switch(chl) { case $B': /* начало блока */ /* block Begin */ Xb-Xcur; Yb-Ycur; If (Yb>Ymin1) draw_llne(Xb, Yb, Xmaxl, Yb); if (Xb>Xmln1) drawJine(Xb, Yb, Xb, Ymaxl); addset(Xb, Yb); break; case 'К': /* конец блока */ /* ЫосК end */ Xk-Xcur; Yk-Ycur; N if(Xk<Xb II Yk<Yb) break; if (Yb>Ymln1) draw_line(Xk, Yb, Xmaxl, Yb); if (Xb>Xmin1)draw_llne(Xb. Yk. Xb. Ymaxl);
65. ИСХОДНЫЕ ТЕКСТЫ 191 if (Xk<Xmax1) draw_line(Xk, Yb, Xk, Yk); if (Yk<Ymax1) drawJine(Xb, Yk, Xk, Yk); dot(Xb, Yb); dot(Xk, Yk); addset(Xk, Yk); break; case 'E': /* Удаление прямоугольника */ /* Erase rectangle */ If (Yb>Ymin1) draw_line(Xb. Yb, Xk. Yb); if (Xk<Xmax1) drawJine{Xk, Yb, Xk, Yk); If (Yk<Ymax1) drawJine(Xk, Yk, Xb, Yk); if (Xb>Xmln1) drawJlne(Xb, Yk, Xb, Yb); break; case 'C: case 'M': blcopy(ch1); break; case 'D': bldelete(); break; case 'W: blwrite(); break; case 'R': blread(); break; default: messageCB ???"); } continue; /* Копирование блока /* Copy block /* Перемещение блока /* Move block /* Удаление блока /* Delete block /* Запись блока /* Write block /* Считывание блока /* Read block */ */ */ V */ V */ */ */ */ } if (ch- -'D') /* определение направления или единицы длины */ /* define direction (DD) or unit of length (DU) */ { ch 1-getch(); ch 1-toupper(ch 1); switch (ch1) { case 'D': defdirectlon(); break; /* направление */ /* direction */ case 'IT: unit(); break; /* единица длины */ /* unit length */ case 'N': newpointQ; break; default: message(MD ???"); } continue; } If (ch- -C) /* Full circle (CF) or arc (C+, C-) */ /* Полная окружность (CF) или дуга */ { ch 1-getch(); ch 1-toupper(ch 1); switch (ch1) { case 'F': arc(0); break; case'+': arc(1); break; case'-': arc(-1); break; default: message(nC ???"); } continue;
192 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ if (ch- -'S') /* Set and sequence manipulation */ /* Манипуляции с набором точек */ { ch 1-getch(); ch 1-toupper(ch 1); switch (ch1) { case 'A': addset(Xcur, Ycur); break; /* Add P(Xcur, Ycur) to set */ /* Добавление позиции курсора P(Xcur, Ycur) к набору */ case 'С: clearset(); break; /* Clear set */ /* Очистка набора V case 'D': delpset(Xcur, Ycur); break; /* Remove P from list */ /* Удаление точки Р из набора */ case 'Г: inviset(); break; /* Points of set Invisible */ /* Точки в наборе невидимы */ case '[': toseq(-1); break; /* To previous sequence pnt */ /* На предыдущую точку последовательности */ case %]%: toseq(1); break; /* To next sequence point */ /* На следующую точку последовательности */ case '<': toseq(-2); break; /* To initial sequence point */ /* На начальную точку последовательности */ case '>': toseq(2); break; /* To final sequence point */ /* На последнюю точку последовательности */ case 'F1: curvefit(O); break; /* B-spline curve (open) */ /* Кривая В-сплайна (незамкнутая) */ case 'R': curvefit(1); break; /* B-spline curve (closed) */ /* Кривая В-сплайна (замкнутая) */ default: message("S ???"); } continue; } if (ch--'F'){ findset(); continue; } /* find point in set */ /* поиск точки в наборе */ if (ch- -T) { ipoint(); continue; } /* initial point */ /* начальная точка */ if (ch- -'!_') { drawtoP(); continue; } /* draw segment */ /* вычерчивание отрезка */ if (ch- -'E') { endpoint(); continue; } if(ch--'V') { ch 1-getch(); ch 1-toupper(ch 1); switch (ch1) { case 'K': keepvector-1; break; case 'S': lsegment(0); break; /* segment instead of arrow */ /* отрезок вместо стрелки */ case 'D': lsegment(1); break; /* dashed segment */ /* штриховой отрезок */ case 'L': lsegment(2); break; /* long line */ /* длинная прямая линия */ case 'A': arrow(); break; /* erase/draw arrow */ /* стирание/вычерчивание стрелки */
65. ИСХОДНЫЕ ТЕКСТЫ. 193 default: message("V ???"); } continue; } If (ch- -'*') { cur(Xcur, Ycur); continue; } /* toggle cursor */ /* переключение курсора */ If (ch- -'.') { dot(Xcur, Ycur); continue; } /* toggle dot */ /* переключение точки */ If (ch- -'+') { lcursor(Xcur, Ycur); continue; } /* toggle l-cursor */ /* переключение 1-курсора */ dmode(dm); penposltlon(pd); } } 6.5.2. Текст программы DIGFUN.C (функции) Функции, используемые в программе DIG.С, приводятся в алфавитном порядке. См. также модули DIGH.C И GRPACK.C. /* DIGFUN.C: */ /* В модуле собраны в алфавитном порядке функции, используемые */ /* в программе DIG.С. См. также модули DIG.H и GRPACK.C */ ♦Include "stdlo.h" ♦Include "math.h" ♦define PI 3.14159265358979 ♦define PIDOUBLE 6.28318530717959 ♦define MAX 4000 extern int Xcur, Ycur, Xold, Yold, X max, Y max, colorgr, stslze, drawmode, Xmlnl, Xmaxl, Ymlnl, Ymaxl, Xb, Yb, Xk, Yk, keepvector, pendown; extern float horfact, vertfact; extern char workstate, filQ; static int Ybottext, dispiaymax, XI—1.YI.XE, YE, vdeflned, Xprev—1, Yprev, XXold— 1, YYold, Xendl, Yendl, Xend2, Yend2, n, marksvlslble-1; static char str[80]; static float xxl, yyl, xxe, yye, xx1, yy1, dx, dy, r, phIO, ph!1, /* These variables obtain their values in 'geometries'; */ /* they are used in 'arc', 'defdirection', 'teegment' */ /* Этим переменным значения присваиваются в функции */ /* 'geometries' и они используются в функциях */ /* 'arc', 'defdirection', 'Isegment' */ '/27—275
194 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ xmin, xmax, ymln, ymax, un-1.0, phl-1000.0; double fx(),fy(); struct SET { int XX. YY;} set[MAX+1]; /* MAX+1 to accommodate sentinel */ /* В элементе МАХ+1 размещается "страж" */ addset(X1, Y1) Int X1, Y1; /* Добавление новой точки к набору (SA) */ /* Add new point to set (SA) */ { struct SET *p; Int nn, nO, XO, YO, I; If (n - - MAX) /* messageCSet full") */ { message("Набор заполнен"); return; } nn-find(X1,Y1); If(nn--n) { XO-Xprev; YO-Yprev; If (XO - - X1 && YO - - Y1) { XO-XXold; YCbYYold; } nO-flnd(XO.YO); If (nO - - n) p - set+n; else { for (l-n-1; i>nO; I- -) set[l+1]-set[l]; p-set+nf>1; } p->XX - X1; p->YY - Y1; n++; If (marksvlslble) mark(X1, Y1); } XXold-Xprev; YYold-Yprev; Xprev-X1;Yprev-Y1; static double angle(xC, yC, x, y) double xC, yC, x, y; /* angle between {(xC, yC), (x, y)} and positive x-axls */ /* угол между отрезком {(xC, yC), (x, у)} и осью х */ { float alpha; If (fabs(x - xC) < .0005) return ((y > yC ? .5 : -.5) * PI); alpha - atan((y - yC)/(x - xC)); return (x > xC ? alpha : alpha + PI); } arc(sign) Int sign; /* Draw arc (C+, C-) */ /* Вычерчивание дуги по командам (С+, С-) */ { float delta, phi, phideg, phi_0, theta; int X, Y, n. i, Xold—1, Yold—1; /* Draw circle arc, centre l(XI, Yl), first point E(XE, YE), */ /* endpolnt on CP, where P(Xcur, Ycur), or specified */ /* by angle phi. sign (sense of rotation): */ /* 1 - counter clockwise, -1 - clockwise */ /* If sign - 0, a full circle Is drawn. */
6.5. ИСХОДНЫЕ ТЕКСТЫ. 195 /* Дуга окружности с центром в точке l(XI, YI), */ /* и с начальной точкой в Е(ХЕ, YE), */ /* конечная точка будет располагаться на прямой СР, */ /* где P(Xcur, Ycur), или на прямой под углом phi. */ /* Параметр sign определяет направление дуги: */ /* sign - 1 : против часовой стрелки, */ /* sign - -1 : по часовой стрелке, */ /* sign - 0 : полная окружность. */ if (Ivdeflned) { messaged Вектор?"); return; } /* message("Vector?") */ geometrics(); phiJbphIO; phi—phi 1—phiO; If (sign--0) { if (xxl-r < xmin 11 xxi+r > xmax 11 yyi-r < ymin 11 yyi+r > углах) /* message("Outside screen") */ message(nBHe экрана "); else circle(xxi, yyi, r); return; } if (XE- -Xcur && YE- -Ycur) { if (епдшге("Угол :", str)- -O) /* "Angle:" */ { invalid(); return;} sscanf(str, "%f", &phideg); phl-PI*phideg/180.0; } n - (int) (80.0 * г * phi); /* 80 points per inchl */ n - abs(n); /* 80 точек на дюйм ! */ delta-sign * phi/n; for(i-1;i<-n;i++) { theta-phi_0 + i * delta; X - IX(xxl+r*cos(theta)); Y - IY(yyi+r*sin(theta)); lf((X!-Xold II Y!-Yold)&&(X>Xmin1 && X < Xmaxl && Y > Yminl && Y < Ymaxl)) dot(X, Y); Xold-X; Yold-Y; } addset(Xold, Yold); arrow() /* Стирание/вычерчивание стрелки */ /* Erase/draw arrow (VA) */ { float a, b, xdif, ydif, tx, ty; a-dx/r; b-dy/r; xdlf-xxe-0.125*a; ydlf-yye-0.125*b; tx-O.05*b; ty4).05*a; clipdraw(xdif+tx, ydif—ty, xxe, yye); clipdraw(xdlMx, ydif+ty, xxe, yye); 727**
196 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ Ысору(сМ) char ch1; /* Копирование блока ■*/ /* Copy a block (ВС, ВМ) */ { int Blength, Bwidth, X. Y, XX1, YY1, invlcalled-O; cur(); if (marksvisible) { inviset(); invlcalled-1; } lf(ch1--'M')delpinblock(); /* Remove all points Inside old block from set */ /* Удаление из набора всех точек внутри старого блока */ Blength-Xk-Xb; Bwidth-Yk-Yb; for(X-Xb;X<-Xk;X++) for(Y-Yb;Y<-Yk;Y++) lf(plxllt(X,Y)) { XX1-Xcur+X-Xb;YY1-Ycur+Y-Yb; If (XXK-Xmax1 && YYK-Ymax1) dot(XX1. YY1); If(cM--'M') { drawmode—1; dot(X, Y); drawmode-O; . } } Xb-Xcur; Yb-Ycur; Xk-Xcur+Blength; Yk-Ycur+Bwldth; lf(Xk>Xmax1)Xk-Xmax1; if(Yk>Ymax1)Yk-Ymax1; cur(); addset(Xb, Yb); addset(Xk, Yk); if (invicalled) inviset(); bldelete() /* Удаление блока */ /* Delete a block (BD) */ { intX.Y; delpinblock(); drawmode—1; /* Удаление блока */ /* delete block */ cur(); for(X-Xb;X<-Xk;X-HK) for (Y-Yb; Y<-Yk; Y++) dot(X, Y); drawmode-Ю; cur(); blread() /* Считывание блока */ /* Read a block (BR) */ { FILE *fp; int Blength, Bwidth, J, X, Y, buflen; unsigned char bitbuff 100], *p; if (enqulreCOaufl : H, str>- -Ю) return; /* enquire -File" */ fp-fopen(str, "rb"); if (fp- -NULL) ^ /* message("Unknown file") */ { message("Неизвестный файл"); return;
6.5. ИСХОДНЫЕ ТЕКСТЫ. 197 cur(); fread((char *)&Blength, 1, slzeof(lnt), fp); freadftchar *)&Bwldth, 1,*sizeof(int), fp); Xb-Xcur; Yb-Ycur; Хк-ХЬ+Blength; Yk-Yb+Bwidth; buflen-(Blength»3)H; for(Y-Yb;Y<-Yk;Y++) { fread(bltbuf, 1, buflen, fp); j-0; p-bitbuf; lf(Y<-Ymax1) for(X-Xb;X<-Xk;X++) { if (*p & 0x80 && X<-Xmax1) dot(X, Y); *p«-1; lf(++j--8){p++;j-0;} } } if(Xk>Xmax1)Xk-Xmax1; lf(Yk>Ymax1)Yk-Ymax1; addsetfXb, Yb); addset(Xk, Yk); fclose(fp); cur(); blwrlte() /* Запись блока */ /* Write a block (BW) */ { FILE *fp; Int Blength, Bwldth, I, j, X, Y, buflen; unsigned char bltbuf[100], *p; If (enquire ("Файл:", str)- -O) return; /* enquire ("File: 77 delpinblock(); cur(); fp-fopen(str, "wb"); Blength-Xk-ХЬ; Bwldth-Yk-Yb; buflen-(Blength»3}H; fwrlte((char *)&Blength, 1, slzeof(lnt), fp); fwrlte((char *)&Bwidth, 1, slzeof(lnt), fp); for(Y-Yb;Y<-Yk;Y++) { for (l-O; Kbuflen; I++) bitbuf[i}-0; J-0; p-bltbuf; for(X-Xb;X<-Xk;X++) { *p«-1; if (plxlitpC. Y)) *p 1-1; lf(-H4--8){p++;j-0;} } *P«-(8-j); fwrlte(bltbuf, 1, buflen, fp); } fclose(fp); cur();
198 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ clearset() /* Стирание всех точек из набора */ /* Clear the set of points (SC) */ { Intl; struct SET *p; If (marksvlslble) for(l-0;Kn;i++) { p - set + I; mark(p->XX, p->YY); } n-0; } static int clipcode(x, y) float x, y; { return ((x<xmin)«3) I ((x>xmax)«2) I ((y<ymln)«1) I ((y>ymax)); } static cllpdraw(x1, y1, x2, y2) float x1. y1, x2, y2; { Intc1,c2; float dx, dy; c1-cllpcode(x1, y1); c2-cllpcode(x2, y2); while(c1lc2) { if (d&c2) return; dx-x2-x 1; dy-y2-y 1; Iff(c1) { If (d & 8) { y1 4- dy*(xmln-x1)/dx; x1-xmln; } else If (d & 4) { y1 -и- dy*(xmax-x1)/dx; x1-xmax; } else If (q1 & 2) { x1 -и- dx*(ymln-y1)/dy; y1-ymln; } else If (d & 1) { x1 +- dx*(ymax-y1)/dy; y1-ymax; } c1-cllpcode(x1, y1); } else { If (c2 & 8) { y2 -H- dy*(xmln-x2)/dx; x2-xmln; } else If (c2 & 4) { y2 -и- dy*(xmax-x2)/dx; x2-xmax; } else If (c2 & 2) { x2 -H- dx*(ymln-y2)/dy; y2-ymln; } else If (c2 & 1) { x2 -и- dx*(ymax-y2)/dy; y2-ymax; } c2-cllpcode(x2, y2); } } move(x1, y1); Xend1-Xcur; Yend1-Ycur; /* to be used In */ draw(x2, y2); Xend2-Xcur; Yend2-Ycur; /* Isegment! */ Xend1-fx(x1);Yend1-fy(y1); Л Used In'Isegment' */ Xend2 - fx(x2); Yend2 - fy(y2); /* Эти переменные используются в функции 'Isegment' */ } coord() /* Отображение значений координаУ /* Display coordinate values */ { prnum(192, Xcur); prnum(272, Ycur); }
6.5. ИСХОДНЫЕ ТЕКСТЫ. 199 cur() /* Отображение/стирание курсора */ /* Display/erase cursor */ inti, J, dm; dm-drawmode; drawmode-O; /* change color, using xor */ /* изменение цвета операцией 'xor'*/ for 0—2;j<-2;J+-4) for (i—4; i<-4; i-м-) dot(Xcur-4, Ycur-1); dot(Xcur+3, Ycur-1); dot(Xcur-4, Ycur); dot(Xcur+4, Ycur); dot(Xcur-4, Ycur+1); dot(Xcur+3, Ycur+1); drawmode-dm; dot(Xcur+i, Ycur+j); dot(Xcur-3, Ycur-1); dot(Xcur+4, Ycur-1); dot(Xcur-3, Ycur); dot(Xcur, Ycur); dot(Xcur-3, Ycur+1); dot(Xcur+4, Ycur+1); dot(Xcur+3, Ycur); } #define ARSIZE 1004 curveflt(closed) int closed; { Int nlnlt, nend, I, J, N-15, flrst-1, nifARSIZE], m; float xA, xB, xC, xD, yA, yB, yC, yD, x, y, a0,a1,a2,a3, bO, M,b2, b3,t; double fx(),fy(); struct SET *p; nlnlt - flnd(XI, Yl); /* If not found In 'set', 'find' returns n */ /* Если точка не найдена в наборе 'set', */ /* то функция 'find' возвращает значение п nend-find(XE.YE); if (nend-- n 11 nend- m - nend - ninit; nlnit < 3) { ninit - 0; nend - n - 1; } if (m < 3) /* messageCToo few points") */ { messagef*Слишком мало точек"); return; } if(m + 3> ARSIZE) /* messageCToo many points") */ { message("OiHUJKOM много точек"); return; } for (i-0; I <- m; i++) nrfi] - nlnit + i; if (closed) { nr[++m]-ninit; nr[++m]-ninit+1; nr[++m] - ninit+2; } for(i-1;i<m-1;i++) { p - set + nr[i-1]; xA - fx(p -> XX); yA - fy(p -> YY); p-set + nr[l]; xB-fx(p->XX);yB-fy(p->YY); p - set + nr[i+1]; xC - fx(p -> XX); yC - fy(p -> YY); p - set + nr[i+2]; xD - fx(p -> XX); yD - ftfp -> YY); a3-(-xA+3*(xB-xC)+-xD)/6.0; b3-(-yA+3*(yB-yC>fyD)/6.0; a2-(xA-2*xB+xC)/2.0; Ь2-(уА-2*уВ+уС)/2.0; a1-(xC-xA)/2.0; Ы-(уС-уА)/2.0; af>(xA+4*xB+xC)/6.0; Ь0-(уА+4*уВ+уС)/6.0;
200 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ for О-О. J<-N; J++) { t-(float)J/(float)N; x-{(a3*t+a2)*t+a1)*t+a0; y-((b3*t+b2)*t+b 1)*t+b0; If (x > xmin && x < xmax && у > ymin && у < ymax) { If (first) { flrst-0; move(x, y);} else draw(x, y); } } } } static dash(x1, y1, x2, y2) float x1, y1, x2, y2; { Intl. k; float xdlf-x2-x1, ydlf-y2-y1, pltchO-0.3, dx, dy; к - 2 * Ont)cell(sqrt(xdlf*xdlf+ydlf*ydlf)/pltch0) + 1; dx-xdlf/k; dy-ydlf/k; for(l-0;Kk;H-2) { move(x1+i*dx, y1+l*dy); draw(x1-Ki+1)*dx, y1-Kl+1)*dy); } } defdlrectlon() /* Определение направления */ /* Define direction (DD) */ { enquire ("Введите угол или *:", str);/* Enter angle or *: */ If (sscanf(str, "%f", &phl)>0)phl-phl*PI/180.0; else { If (Ivdeflned) { message(nВектор?"); return; } /* message ("Vector?") */ phl-phIO; } } static delpinblock() /* Удаление из набора всех точек внутри блока V /* Delete all points within block from set */ { Intl.J.X.Y; struct SET *p; for(M);Kn;l++) { p - set+l; X - p->XX; Y-p->YY; * If (X>-Xb && Xo-Xk && Y>-Yb && Y<-Yk) If (marksvislble) mark(X, Y); /* удаление */ /* delete */ for (j-i; j<n; J++) set[j]-set[j+1]; i--. } }
6.5. ИСХОДНЫЕ ТЕКСТЫ. 201 delpset(X1, Y1) /* Исключение точки из набора */ /* Remove a point from set (SD) V { Intl. J; i-find(X1,Y1); If (I- -n) { message("HeT в наборе"); return; } /* message("Not In set") */ If (marksvlslble) mark(X1, Y1); /* удаление */ /* delete */ n--; for O-l; J<n; J-H-) set[j]-set[j+1]; } dmode(m) int m; /* Изменение режима черчения */ /* Change drawing mode (PP, PN, PA) */ /* 1-Positive, -1-Negative, 0-Alternate */ /* 1-Черчение, -1-Стирание, 0-Переменный */ { textXY(0, 55, m - - 1 ? "P" : m 1 ? "N" : "A"); drawmode - m; drawtoP() /* Вычерчивание отрезка прямой линии */ /* Draw line segment (L) */ { IntX.Y; If (Xcur- -Xprev && Ycur- -Yprev) { X-XXold;Y-YYold; } else { X - Xprev; Y - Yprev; addset(Xcur, Ycur); } If (X<0) message("Предыдущая точка?"); /* message("Prevlous point?") */ else draw_llne(X, Y, Xcur, Ycur); } endpolnt() /* Define vector endpolnt (E) */ /* Определение конечной точки вектора */ { If (XI - - -1) {message ("Точка I?"); return; } /* message ("Point I?") */ If (Xcur- -XI && Ycur- -Yl) /* message("Zero vector!") */ { message("Нулевой вектор!"); return; } If (vdefined && !keepvector) levector(); /* delete vector IE */ /* удаление вектора */ XE-Xcur; YE-Ycur; geometrics(); addset(Xcur, Ycur); levector(); vdeflned-1; keepvector-Ю; } static int enquire(txt, str) char *txt, *str; { char ch, s[2]; int Ю, len; len-strlen(txt); message(txt); s[1J-'\0';
202 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ while (ch-getch(), ch !- Лп' && ch !- V && ch !- '') { if(ch--8) { if (- -КО) К); /* dealing with backspace */ continue; /* нажатие клавиши стирания */ } str[i}-s[0}-ch; textXY((len+i+1)«3, 0, s); I++; } strtihAO'; return i;/ } static Int find(X, Y) int X, Y; /* Find position of (X, Y) In set */ /* Нахождение позиции точки (X, Y) в наборе */ { struct SET *p; p-set+n; p->XX - X; p->YY - Y; /* 'страж' */ /* sentinel */ for (p-set; p->XX !- X 11 p->YY !- Y; p++); /* пустой оператор V /* empty statement */ return p-set; /* If found, position (<n), otherwise n */ /* если точка найдена, ее позиция <п, иначе возвращается значение п*/ } findset() /* Перенос текущей точки в ближайшую точку из набора */ /* Move current point to nearest point in set (F) */ { IntXP.YP; struct SET *p; float minim2-1.0e10, x1, y1. dx, dy, sq; if (n- -Ю) { messaged Набор пуст"); return; } /* message(" Empty set") *y xl-fx(Xcur); y1-fy(Ycur); for (p-set; p<set+n; p++) { dx-fx(p->XX)-x1; dy-fy(p->YY)-y1; sq - dx*dx + dy*dy; if (sq<minim2) { minim2-sq; XP - p->XX; YP - p->YY; } } cur(); Xcur-XP; Ycur-YP; cur(); coord(); XXold-Xprev; YYold-Yprev; Xprev-Xcur; Yprev-Ycur; } static double fx(X) Int X; { return X/horfact; } static double fy(Y) int Y; { return (Y_max - Y)/vertfact; } static geometrlcs() /* Computes the global variables */ /* Вычисление глобальных переменных: */ /* xxi, yyl, xxe, yye, xx1, yy1, dx, dy, r, phiO, phil */ { xxl-fx(XI);yyi-fy(YI); xxe-fx(XE);yye-fy(YE); xx 1 - fx(Xcur); yy1 - fy(Ycur); dx - xxe - xxi; dy - yye - yyi;
65. ИСХОДНЫЕ ТЕКСТЫ. 203 г - sqrt(dx * dx + dy * dy); phiO - angle(xxi, yyi. xxe, yye); phM -angle(xxi, yyi. xx1, yy1); } hardcopy(cod.e) char code; /* Вывод изображения на принтер */ /* Picture to printer (PO, P1, P2) */ { int Xlo, Xhi, Ylo, Yhi; If (code - - '2') { Xlo-Ylo-O; Xhi-X_max; Yhi-Y_max; } else { Xlo-Xmin1; Xhi-Xmax1; Ylo-Ymln1; Yhi-Ymax1; } if (code - - '0') { Xlo++; Xhi- -, Ylo++; Yhi- -; } printgr(Xlo( Xhi, Ylo, Yhi); } IcursorfXI. Yl) int XI, Yl; /* Переключение курсора */ /* Toggle l-cursor */ { float xi, yi; xi-fx(XI); yi-fy(YI); clipdraw(xi-0.1, yi, xKO.1, yi); cllpdraw(xi, yi-0.1. xi, yi-Ю.!); } static ievector() {draw_line(XI, Yl, XE, YE); arrow(); } (nvalidO /* Отображение сообщения об ошибке */ /* Display error message "Invalid character" */ {message ("Неверный символ"); } lnviset() /* Установка режима видимости для точек в наборе */ /* Make points in set invisible/visible (SI) V { struct SET *p; for(p-set; p<set+n; p++) { mark(p->XX, p->YY); } marksvisible Л- 1; } ipoint() /* Начальная точка вектора */ /* Initial point of vector (I) */ { if (XI!- -1) icursor(XI, Yl); /* удаление курсора в точке I */ /* delete cursor at I s */ if (vdefined && Ikeepvector) /* delete vector IE */ levector(); /* удаление вектора IE */ vdeflned-O; Xl-Xcur; Yl-Ycur; icursor(XI, Yl); addset(Xcur, Ycur); }
204 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ static int load() { Int 1—1. J; short bufar[13]; unsigned char buf, ncopies; FILE *fp; fp-fopen(fil, Mrb"); if (fp--NULL) return 0; if (fread((char *)bufar, 2, 13, fp) < 13) { fclose(fp); return 0; } Xl-bufar[0]; YI-bufar[1]; XE-bufar[2]; YE-bufar[3]; Xcur-bufar[4]; Ycur-bufar[5]; Xb-bufar[6]; Yb-bufar[7]; Xk-bufar[8]; Yk-bufar[9]; marksvisible-bufar[10]; n-bufar[11]; vdeflned-bufar[12]; if (vdefined) geometrics(); /* Bug In first version corrected*/ /* Здесь скорректирована ошибка в первой версии */ If (fread((char *)set, sizeof (struct SET), n, fp) < n) { fclose(fp); return 0; } do { if (fread(&buf, 1, 1, fp) - - 0) { fclose(fp); return 0; } poke1(0xB800,++i, &buf, 1); if (buf - - 0 11 buf--0xFF) { fread(&ncoples, 1, 1,fp); If (buf) { for (j-0; J<ncoples; J++) роке1(0хВ800,-Ж, &buf, 1); } else i+-ncopies; } } while (i < dlsplaymax); fclose(fp); return 1; } lseg( )/* Sketch horizontal or vertical line segment (PD etc.): */ /* If pendown, draw line segment from (Xold, Yold) to (Xcur, Ycur) */ /* Задание горизонтального или вертикального отрезков */ /* прямой линии. Если перо опущено, то вычерчивание */ /* отрезка из точки (Xold, Yold) в точку (Xcur, Ycur) */ { static Int prevdown, /* Was previously a line segment drawn to old point? */ /* Был ли вычерчен отрезок прямой в старую точку ? */ prevdirection, direction; if (pendown) { draw_llne(Xold, Yold, Xcur, Ycur); direction - Xcur < Xold ? -1: Xcur > Xold? 1: Ycur < Yold?-2: Ycur>Yold? 2:0;
65. ИСХОДНЫЕ ТЕКСТЫ. 205 /* If prevdirection + direction - - 0, and drawmode - - 0, a line /*segment just drawn Is erased; beware of an extra call of dot /* In the point of reversal, performed In drawjlne. /* При условии prevdirection + direction - - 0 и drawmode - - 0 /* только что вычерченный отрезок прямой линии удаляется. /* Необходимо соблюдать осторожность с дополнительным /* обращением к функции 'dot' в точке возврата, */ /* выполняемом в функции 'drawjlne*. */ If (prevdown && drawmode- -0 && prevdirection + direction - - 0) dot(Xold, Yold); /* fourth call of dot In (Xold, Yold) */ /* четвертое обращение к функции 'dot' в точке (Xold, Yold)*/ } prevdown - pendown; prevdirection - direction; } lsegment(code) int code; /* Реализация команд: /* (VS (0), VD(1). VL(2) /* code 0: drawn segment, 1: dashed segment, 2: long line /* код О : чертится отрезок, /* код 1 : отрезок штриховой линии, /* код 2 : сплошная длинная прямая линия { lntXX1,YY1,XX2,YY2; If(vdeflned) {. arrow()> /* удаление стрелки */ /* delete arro*/ icursor(XI, Yl); /* удаление курсора в точке I */ /* delete cursor at I */ If (code) { draw_llne(XI, Yl, XE, YE); /* удаление отрезка прямой */ /* delete line segment */ If (code- -1) dash(xxl, yyl, xxe, yye); else { float c, s; c-10.0*cos(ph!0);s-10.0*sln(phl0); cllpdraw(xxl-c, yyl-s, xxl+c, yyl+s); /* 'cllpdraw'computes Xendl, Yendl, Xend2, Yend2 */ /* В функции 'clipdraw' вычисляются значения */ /* переменных Xendl, Yendl, Xend2, Yend2 */ XX1-Xend1;YY1-Yend1; XX2-Xend2; YY2-Yend2; /* Local copies (XX2, YY2) are needed V /* for reasons of security V /* Локальные копии (ХХ2, YY2) необходимы */ /* с точки зрения надежности */ addset(XX1,YY1); addset(XX2, YY2); } } vdeflned-0; XI—1; } else message("He вектор"); /* message("No vector") */
206 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ static mark(Xr Y) int X, Y; { float x, y, dx-0.07, dy-0.07; x-fx(X);y-fy(Y); cllpdraw(x-dx, y-dy, x+dx, y+dy); cllpdraw(x-dx, y+dy, x+dx, y-dy); } message(str) char *str; /* Стирание старого сообщения и отображение нового */ /* Erase old message and display new one */ { textXY(8.0. " "); textXY(8, 0, str); } newpoint() /* Выбор новой точки в заданном направлении */ /* Find a new point In a given direction (DN) */ { float dO, d, cphl, sphl, xold-^1000.0, yold, xnew—1000, ynew; Int XX, YY; If (phl>100.0) /* message("Angle undefined") */ { message("Yrofl не определен"); return; } xold-fx(Xcur); yold-fy(Ycur); cphl-cos(phl); sphl-sln(phl); If (flnd(Xcur, Ycur)- -n) addset(Xcur, Ycur); /* add to set If not yet In It */ /* добавление к набору, если этой точки в нем еще нет */ enquire ("Длина или *:", str); /* "Length, or *:" */ If (sscanf(str,"%f", &d0) > 0) d-d0*un; else { If (!vdefined){message("Вектор?"); return; } /* Vector? */ d - -(xold*yyl+yold*xxe+xxl*yye-xxe*yyl-xxl*yold-xold*yye)/ (cphi*yyl+sphi*xxe-xxi*sphl-yye*cphl); /* Now (xold+d*cos(phl), yold+d*sln(phi)) is */ /* the point of intersection we are Interested In */' /* Теперь точка (xold+d*cos(phi)< yold+d*sin(phl)) */ /* является интересующей нас точкой пересечения */ } xnew-xold+d*cphi; ynew-yold+d*sphi; XX-IX(xnew); YY-IY(ynew); if (XX<Xmin1 \ I XX>Xmax1 11 YY<Ymin1 11 YY>Ymax1) message("BHe экрана"); else /* message("Outslde screen") */ { cur(); Xcur-XX; Ycur-YY; cur(); coord(); addset(Xcur, Ycur); } } penposition(p) int p; /* Display pen position: 1 - down, 0- up */ /* Отображение положения пера: 1 — опущено */ /* 0 — поднято */
6.5. ИСХОДНЫЕ ТЕКСТЫ. 207 { intdm; dm-drawmode; drawmode-1; textXY(0, p ? 27 : 40," "); textXY(0, p ? 32 : 27, "H"); textXY(0, p ? 40 : 35, "V"); draw_line(0, 48, 6, 48); pendown-p; drawmode-dm; /* clear old pen portion /* стирание части старого пера /* a picture of a pen /* изображение пера /* бумага, на которой пишет перо /* the paper on which the pen writes V V */ V V */ } prnum(X, num) int X, num; /* Отображение числа на нижней строке */ /* Display a number in bottom margin */ { char str[4]; sprlntf(str, H%3d", num); textXY(X, Ybottext, str); } save() /* Запись всей картинки в файл */ /* Write entire picture to file */ { Inti—1; short bufar[13]; unsigned char ncopies, buf, newbuf; FILE *fp; fp-fopen(fil, "wb"); if (fp- -NULL) { invalid(); return; } bufar[0]-XI; bufar[1}-YI; bufar[2]-XE; bufar[3}-YE; bufar[4}-Xcur; bufar[5]-Ycur; bufar[6}-Xb; bufar[7]-Yb; bufar[8]-Xk; bufar[9]-Yk; bufar[10]-marksvisible; bufar[11^n; bufar[12^vdefined; fwrite((char *)bufar, 2, 13, fp); fwrite((char *)set, sizeof(struct SET), n, fp); do { peek1(0xB800, ++i, &buf, 1); fwrite(&buf, 1, 1,fp); if(buf--0 II buf--0xFF) { ncopies-O; /* ncopies -number of extra copies */ /* число дополнительных копий */ if (i < displaymax) { while (peek1(0xB800, ++i. &newbuf, 1). newbuf- -buf) { ncopies-н-; if (i-- displaymax 11 ncopies--OxFF) break; } if (newbuf !- buf) i- } /* число копий может быть равно 0 */ fwrite(&ncopies, 1,1, fp); /* ncopies may be 0 */ } } while (i < displaymax); fclose(fp);
208 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ static togrmodeO /* Switch to graphics mode, called in 'wstate' */ /* Переключение в графический режим, */ /* вызывается из функции 'wstate' */ { static Int load_deslred-1; initgrO; dlsplaymax - (colorgr ? 16383 : 32767); Xmlnl - 12; Xmaxl - X__max - 4; Ymln 1 - 22; Ymaxl - Y__max - 14; xmin-fx(Xminl); xmax-fx(Xmaxl); ymin-fy(Ymax1); ymax-fy(Ymin1); drawmode-1; if (load_desired) { if(!load()) { /* Совершенно новый чертеж*/ /* Completely new drawing: */ drawJine(Xmln1, Yminl, Xmaxl, Yminl); drawJine(Xmax1, Yminl, Xmaxl, Ymaxl); drawJine(Xmax1, Ymaxl, Xmlnl, Ymaxl); drawJlne(Xmin1, Ymaxl, Xmlnl, Yminl); Xcur-Ycur-30;cur(); } } /* "Stepsize:" */ Ybottext - Ymaxl +3; textXY(8, Ybottext, "Шаг курсора:"); textXY(160. Ybottext, "X -"); textXY(240, Ybottext, "Y -"); coord(); prnum(112, stslze); loaddesired - colorgr; } toseq(code) int code; { Inti; if (n - - 0) { messaged Набор пуст"); return; } /* message(" Empty set") */ if (abs(code) - - 1 && (I - flnd(Xcur, Ycur)) - - n) { message(" В ведите сначала F "); return; /* message("Use F first")*/ } cur(); /* Стирание старого курсора */ /* Delete old cursor*/ switch (code) { case -2:1 - 0; break; case 2: i-n-1; break; case -1: If (i > 0) I- -; break; case 1: if (I < n — 1) l-н-; break; } Xcur - set[i].XX; Ycur - set[i].YY; XXold-Xprev; YYold-Yprev; Xprev-Xcur; Yprev-Ycur; cur(); return;
6.5. ИСХОДНЫЕ ТЕКСТЫ. 209 static totextmode() /* Вызывается из функции 'wstate' */ { to_text(); menu('M'); /* Called in wstate */ } unlt() /* Определение единицы длины */ { Intn; /* Define unit of length (DU) */ float dx, dy; if (Ivdeflned) /* message("No initial point") */ { message("HeT начальной точки"); return; } If (XE- -XI && YE- -Yl) /* message("Zero vector") */ { message("Нулевой вектор"); return; } enquire ("Сколько единиц?", str); /* "How many units?" */ sscanf(str, "%d", &n); if (n<-0) n-1; dx4x(XE)-fx(XI); dy-fy(YE)-fy(YI); un-sqrt(dx*dx+dy*dy)/n; } wstate(ch) char ch; /* New workstate (WA. WL, WT), */ /* 'ch' is new state, 'workstate' is old state */ /* Переход в новое рабочее «стояние : */ /* в переменной 'ch' новое состояние */ /* в переменной 'workstate' — старое */ { If(ch--T') { if (workstate- -T') /* "Already in textmode" */ printf("y>Ke в текстовом режиме\п"); else { if (colorgr)save(); /* So that load() can be used when reverting to graphics */ /* mode later (the screen then having been blanked by the */ /* BIOS routine for the color graphics card). */ /* При последующем возврате в графический режим может */ /* быть использована функция load(), поскольку экран */ /* был очищен программой базового ввода/вывода BIOS. */ totextmode(); } } else { if (workstate- -T') togrmode(); /* Switch to graphics mode */ /* Переключение в графический режим */ textXY(8, 11, ch - -'L' ? "Режим черчения линий. " : "Графический текстовый режим."); /* Line-drawing state or Graphics alpha state - V textXY(260, 0, (ch- -A'? /* "Use Ctrl WT for text mode" */ "Нажмите Ctrl WT для перехода в текстовый режим": /* "Use WT for text mode " */ "Нажмите WT для перехода в текстовый режим ")); penposition(0); /* up */ /* перо поднять */ dmode(0); /* toggle */ /* переключить режим*/ } workstate-ch;
210 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 6.5.3. Текст программы DIGH.C (сообщения помощи) /*DIGH.C: ■*/ /* Functions for help, used in DIG.С */ /* Функции помощи, используемые в программе DIG.С */ static p(s) char *s; { printf(s); } menu(ch)charch; { int again-1; while(again) { switch (ch) { case 'M': p("\n\n\n\n\n\n\n\n\n\n\n\n"); p("Drawing with Interactive Graphics, by L. AmmeraalAn"); p("Published by John Wiley & Sons, Chichester/New york\n\n\nn); p("Maln MenuAnW); p("Enter one of the following commandsAnXn"); p("WL : Switch to line-drawing workstateAn"); p("WA : Switch to graphics alpha workstateAn"); p("Q : Quit; graphics results will be savedAn"); p("Ctrl С : Quit immedlatelyAn"); p(MH : HelpAn"); V p(" ЧЕРЧЕНИЕ В ИНТЕРАКТИВНОМ РЕЖИМЕ \n\n\n"); р(п по книге Л.Аммерала\п\п\пи); р(" LAmmeraal, Computer Graphics for the IBM PCAn"); p(" John Wiley & Sons, Chichester/New york.\n\n\n"); р(ТЛАВНОЕ МЕНЮ:\п\пм); p("Введите одну из следующих командАп\пи); p("WL : Переключение в режим черчения линийАп"); p("WA : Переключение в графический текстовый режимАп"); p("Q : Выход; графический результат будет сохраненАп"); p("Ctrl С : Немедленный выходАп"); р("Н : Помощь.\п\п\п\п"); break; case'Н': р("\п\п\п\пМеню помощиАп\п"); /* Help Menu */ /* p("Press:\n\n"); р(" 1 for program start and state switching\n"); p("2 for cursor commands, pen position and drawing modes\n"); p("3 for the alpha workstate\n"); p("4 for commands to manipulate a set of marked polnts\n"); p("5 for block commands\n"); p("6 for vectors, circles and arcs\n"); p("7 for directions, units of length, new points\nn);
6.5. ИСХОДНЫЕ ТЕКСТЫ 211 р("8 for matrix printer usage and for area fllllng\n"); p("9 for B-spline curve flttingV); p("M to return to the main menu\n\n"); p(" After that, you may enter another digit to"); p(" display a group of commands,\n"); p("or press M or H to display the main or help menu once again."); */ р(пНажмите клавишуАп\п"); p("1 начало работы программы и переключение режимов\п"); р("2 команды курсора, положения пера и режимов черчения\п"); р("3 команды в алфавитно-цифровом режиме\пп); р("4 команды манипуляций с набором маркированных точек\п"); р("5 команды работы с блоками\п"); р("6 векторы, окружности и дуги\п"); р("7 определение направления, единиц длины, новых точек\пл); р("8 вывод на матричный принтер и заполнение областей\пп); р("9 подбор кривой типа В-сплайна\п"); р("М возврат к главному меню\п\п\п"); р("После просмотра вызванного текста можно снова"); р(" ввести нужную цифру для\п"); р("отображения описания следующей группы команд,"); р(" или нажать М или Н для\п"); р(" вы вода главного меню или общего меню помощиАп\п\п"); break; case T: p("\n\n"); /* р("1. Program start and state switching\nn); PC \n\n"); p("DIG : Start the program; dimensions are"); p(w based on the screen An"); p("DIG -P : Start the program;"); p(" dimensions are based on the matrix printerAn\n\n"); p("Angles will have their proper sizes and"); p(" circles will appear as clrclesW); p("on the printer if the option -P is used."); p(" If proper dimensions on the\n"); p("screen are more important than those on the printer,"); p(" the option -P shouid\n"); p("not be usedAn\n"); p("WL : Switch to line-drawing stateAn"); p("WA ' : Switch to graphics alpha stateAn"); p("WT : Switch to text state, to quit,"); p(" or to ask for helpAn\n"); p("Note: In graphics alpha state the Control key must\n"); p(" be kept down when the letter W is entered for\n"); p(" state switching An"); V 8**
212 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ э("1. Начало программы и переключение состояния\п"); р(и \л\п"); o("DIG : Запуск программы; размеры установлены для вывода"); р(" изображения на экранАп"); j>("DIG -Р : Запуск программы; размеры установлены для вывода"); р(" на матричный принтерАп\п\п"); э(" Величины углов будут выдержаны, а окружности при"); р(" выводе на принтер\п"); о(" будут изображены верно, если используется"); р(" опция -Р. Если точные\п"); э(" соотношения размеров более важны на экране,"); р(" чем на принтере, то\п"); р(" опцию -Р задавать не следует.\п\п\п"); p("WL : Переключение в режим черчения линийАп"); p("WA : Переключение в алфавитно-цифровой режимДп"); p("WT : Переход в текстовый режим, на выход или запрос"); р(" помощиДпХп"); р(" Замечание: В графическом алфавитно-цифровом"); р(" режиме обязательно^"); р(" удерживать в нажатом положении клавишу"); р(" Ctrl при нажатии\п"); р(" клавиши W для смены режима.\п\п\п\п"); break; case '2': p("\n\n"); /* р("2. Cursor commands, pen position and drawing modes\n"); p(" \n"); p("\nThe cursor is moved by pressing the arrow keysAn"); p("The step size is increased or decreased"); p(" by pressing > or <.\n\n"); p("PU: PenUpV); p("PD : Pen Down\n"); p("PP : Pen in Positive drawing mode:"); p(" use pen, when down, to draw.Xn"); p("PN : Pen in Negative drawing mode:"); p(" use pen, when down, to rub outAn"); p("PA : Pen in Alternate drawing mode:"); p(" use pen, when down, to toggleAn\n"); p("The following commands may have confusing effects,"); p(" so be cautious when\n"); p("using them:\n\n"); p("* : Toggle cursor\n"); p(". : Toggle dot\n"); p("+: Toggle l-cursor\n"); */
6.5. ИСХОДНЫЕ ТЕКСТЫ. 213 р("2. Команды курсора, положения пера и режимов работыХп"); р(" ! \п"); р("\пКурсор перемещается нажатием на клавиши со стрелкамиАп"); р("Размер шага увеличивается или уменьшается нажатием "); р("на клавиши > или <Ап\п"); p("PU : Перо поднять\п"); p("PD : Перо опуститьХп"); р("РР : Перо в режиме черчения:"); р(" в опущенном состоянии перо чертитАп"); p("PN : Перо в режиме стирания:"); р(" в опущенном состоянии перо стираетЛп"); р("РА : Перо в переменном режиме:"); р(" при опущенном пере пикселы переключаются.\п\п"); р("Следующие команды могут привести к неожиданным эффектамДп"); р("поэтому нужно соблюдать осторожность при их применении:\п\п"); р("* : Переключение курсора\п"); р(".: Переключение точки\п"); р("+ : Переключение контура курсора\п\п\п\п\п\п"); break; case '3': p("\n\n\n"); /* р("3. The alpha workstate\n"); p(" \n\n"); p("Text appears in the drawing as it is entered."); p(" The backspace key\n"); p("may be used for corrections. Keep the Ctrl key"); p(" down when pressing\n"); p("the first letter W of the state switching commands"); p(" WT, WLAn"); */ p("3. Графическое алфавитно-цифровое рабочее состояние\п"); р(м \n\n"); р("Текстовые надписи на чертеже отображаются по мере в вода An"); р("Для коррекции может быть использована клавиша стирания.\п"); р("При вводе первой буквы W в командах переключения режимов\п"); p("WT и WL необходимо удерживать нажатой клавишу Ctrl."); р(" \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); break; case '4': p("\n\n"); /* р("4. Commands to manipulate a set of marked polnts\n"); p(" -\n\n"); p("AII points in the set are marked X.\n\n"); p("SA : Add point to set.\n");
214 Глава 6. DIG — СИСТЕМА'ИНТЕРАКТИВНОЙ ГРАФИКИ p("SD : Delete point from setAn"); p("SC : Clear the setAn"); p("SI : Toggle points In set(lnvislble/vlslble).\n"); p("F : Find the nearest point In the set,"); p(" and move cursor to ItAn"); p("L : Use the point most recently added to the set"); p(" as the 'old' pointAn"); p(" Then a line segment is drawn from the old point"); p(" to the currentW); p(" cursor position. Besides SA, the commands L,"); p(" I also add pointsW); p(" to the setAn"); p(" (See also 9.)\n"); V p('*4. Команды манипуляций с набором маркированных точек\п"); р(" ■ \n\nn); р("Все точки в наборе маркируются символом Х.\п\п"); p("SA : Добавление точки к наборуАп\п"); p("SD : Удаление точки из набораАп\пм); p("SC : Очистка набора.\п\п"); p("SI: Переключение точек в наборе (видимы/невидимы)Ап\пм); p("F : Поиск ближайшей точки в наборе и перенос"); р(" курсора в эту точкуАп\п"); р("1_ : Использование последней добавленной к набору"); р(" точки в качестве\п"); р(" 'старой' точки. Тогда отрезок вычерчивается"); р(" из старой точки\п"); р(" в точку текущей позиции курсора. Как и команда"); р(" SA, команды LAn"); р(" и I добавляют точки к набору."); р(" (См. также9)\п\п\п\п\п"); break; case '5': p("\n\n"); /* р("5. Block commandsXn"); p(" \n\n"); p("BB : Begin of block, top-left cornerAn"); pCBK : End of block, bottom-right cornerAn"); p("BE : Erase (or draw again) block boundaryAn"); p("BD : Delete the entire blockAn"); p("BC ; Block CopyAn"); p("BM : Block MoveAn"); p("BW: Block WriteAn"); p("BR: Block ReadAn"); */
6.5. ИСХОДНЫЕ ТЕКСТЫ 215 р("5. Команды работы с блоками\п"); р(п \п\п"); р("ВВ : Начало блока, левый верхний угол An"); р("ВК : Конец блока, нижний правый у го л An"); р("ВЕ : Стирание (или повторное вычерчивание) границ блокаAn"); p("BD : Стирание всего блокаAn"); р("ВС : Копия блока An"); р("ВМ : Перенос блока An"); p("BW : Запись блока An'*); p("BR : Считывание блокаАп\п\п\п\п\п\п\п\п\п\п\п\п"); break; case '6': p("\n\n"); /* р("6. Vectors, circles and arcs\n"); РГ - \n\n"); p("l: Define initial point I of vectorAn"); p("E : Define vector endpoint E of vectorAn\n"); p("VA : Toggle arrow of vector (invislble/visibleJAn"); p(nVS : Replace vector with line segmentAn"); p("VD : Replace vector with dashed line segmentAn"); p(MVL: Replace vector with long lineAn"); p(MVK : Keep vector: the arrow is to be permanentAn\n"); p(MWith CF, C+, C-, point I is the center and IE the radlusAn"); p(MCF : Draw a full clrcleAn"); р(иС+: Draw arc with a given^ngle, counter-clockwiseAn"); p("C-: Similarly, clockwiseAn"); p(" With C+ and C-, If the cursor Is not In E,M); p(" its position will be\n"); p(M used to determine the angle of the arcAn"); */ p("6. Векторы, окружности и дуги\п"); р(и^ ^\n\nn); p("l : Определение начальной точки I вектора An"); р("Е : Определение конечной точки Е вектораАп\п"); p("VA : Переключение стрелки вектора (видима/невидима)An"); p("VS : Замена вектора на отрезок прямой линииАп"); p("VD : Замена вектора на отрезок штриховой линииАп"); р( VL : Замена вектора на длинную прямую линиюАп"); p("VK : Сохранение вектора: стрелка должна остаться А п\п"); р(" Для команд CF, C+, С-, точка I является позицией"); р(" центра,\п"); р(" а отрезок IE определяет радиусАп\п"); p("CF : Вычерчивается полная окружностьАп"); р("С+ : Вычерчивается дуга с заданным углом в направлении"); р(" против \п"); р(" часовой стрелки An");
216 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ р("С- : То же самое, но в направлении по часовой стрелкеАп"); р("\п При задании команд C+and C-, если курсор не"); р(" находится в\п"); р(" точке Е, то его позиция будет использована для"); р(" определения\п"); р(" угла дугиАп"); break; case '7': p("\n\n"); /* р("7. Directions, units of length, new points\n"); p(H : \n\n"); p("The following three commands ask for a number"); p(" or an asterisk. Don't\n"); p("forget to press the Enter key after"); p(" typing that number or asterisk.\n\n"); p("DU : When this command is used, a vector IE must"); p(" have been definedAn"); p(" The question 'How many units?' is displayed."); p(" If you now enter\n"); p(" some integer value n, the unit of length is"); p(" defined such that\n"); p(" the length of IE is n, expressed in this unit."); p(" The unit may be\n"); p(" used by subsequent DN commands.\n\n"); p("DD : An angle measured from the positive X-axis"); p(" Is requested to define\n"); p(" a direction. This direction will be used"); p(" by subsequent DN commandsAn"); p(" An asterisk means: use the direction of"); p(" vector IE for this purposeAn\n"); p("DN : Find a new point in the current direction, at a given\n"); p(" distance from the current cursor positlonAn"); p(" An asterisk means: place the point on the line lEAn"); */ p("7. Направления, единицы длины и новые точки\п"); р(" \п\п"); р("Следующие три команды запрашивают ввод числа или"); р(" звездочки. ПослеХп"); р("набора числа или звездочки обязательно нажать"); р(" на клавишу 'Ввод'.\п\п"); p("DU : Перед вводом этой команды должен быть"); р(" определен вектор IE An"); р(" На экране появится запрос: 'Сколько единиц?'."); р(" Если теперь \п"); р(" ввести некоторое целое число п, то единица"); р(" длины будетХп"); р(" такой, что длина вектора IE в этих");
6-5. ИСХОДНЫЕ ТЕКСТЫ 217 р(" единицах равна п.\гГ); р(" Эта единица будет использована в"); р(" последующих командах DN.\n\n"); p("DD : Для определения направления запрашивается"); р(" значение угла\п"); р(" относительно положительной полуоси х."); р(" Это направление будет\п"); р(" использовано в последующих командах DN."); р(" Звездочка означаетДп"); р(" что с этой целью будет использовано"); р(" направление вектора 1Е.\п\п"); p("DN : Нахождение новой точки по текущему направлению"); р(" на задан ном\гГ); р(" расстоянии от текущей позиции курсора."); р(" Звездочка означает,\п"); р(" что точка располагается на прямой линии IE."); p("\n\n\n\n"); break; case '8': p("\n\n"); /* p("8. Output on a matrix printer; area fillingW); p(" \n\n"); p("P0 : Print everything inside the window"); p(" boundaries (not the boundaries\n"); p(" themselves).\n"); p("P1 : Print everything inside the boundaries, including the\n"); p(" boundaries themselvesAn"); p("P2 : Print the complete screen.\n\n"); p("PF : Fill the closed region in which the cursor is lying."); p(" Beware of\n"); p(" 'bleeding', which will happen if the region is"); p(" not completely\n"); p(" closed. Before giving the command PF,"); p(" temporarily erase any marks,\n"); p(" using SlAn"); */ p("8. Вывод на матричный принтер, заполнение областей\пп); р(" \п\п"); р("Р0 : Выводится на печать все в пределах границ"); р(" окна(исключая\п"); р(" сами границыАп\п"); р("Р1 : Выводится на печать все в пределах границ"); р(" окна, включаяХп"); р(" сами границы.\п\п"); р("Р2 : Выводится на печать все содержимое экрана.\п\п"); p("PF : Заполняется замкнутая область, в пределах"); р(" которой находится\п"); р(" курсор.\п\п");
218 Глава 6. DIG - СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ р(" Замечание: Остерегайтесь сплошной 'заливки',"); р(" что появляется\п"); р(" при неполном замыкании контура,"); р(" ограничивающего область.\п"); р(" Перед вводом команды PF временно удалите"); р(" из изображения все\п"); р(" метки с помощью команды Sl.\n\n\n\n\n\n"); break; case '9': /* p("\n\n9. B-spline curve fltting\n"); p(" \n"); p("The set of marked points is now regarded as a sequence.\n\n"); p("S< : Move the cursor to the initial point of the sequence.\n"); p("S> : Move the cursor to the final point of the sequenceAn"); p("S[: Move the cursor to the predecessor of the"); p(" current marked pointAn"); p("S]: Move the cursor to the successor of the current"); p(" marked pointAnW); p("SF : Draw (or erase) a curve that approximately"); p(" passes through the\n"); p(" marked points, with the exception of the initial"); p(" and the final\n"); p(" pointAn"); p("SR : As SF, but now going round, drawing a closed curve \n\n"); p("lf I and E coincide with appropriate points of"); p(" the sequence, the\n"); p("subsequence beginning in I and ending in E is used"); p(" instead of the\n"); p("entire sequence of marked points."); p(" (IE will be shown as an arrow.)\n\n"); p("The sequence can be updated using SD and SA (see 4.)."); p(" After movingW); p("the cursor to a marked point (using F, ], or [)."); p(" you can insert a new\n"); p("point immediately after that marked point by moving"); p(" the cursor to the\n"); p(Hnew point and using SAAn"); V p("\n\n9. Подбор кривой типа В-сплайна\п"); ;} р(" \п"); р("Набор маркированных точек принимается за"); р(" последовательность.\п\пп); p("S< : Переместить курсор в начальную точку"); р(" последовательностиАп"); p("S> : Переместить курсор в последнюю точку"); р(" последовательностиАп");
6.5. ИСХОДНЫЕ ТЕКСТЫ 219 p("S[: Переместить курсор в точку, предшествующую"); р(" текущей маркироЛп ванной точке An"); p("S]: Переместить курсор в точку, следующую за"); р(" текущей маркированЛп ной точкойЛп"); p("SF : Вычерчивание (или стирание) кривой, которая"); р(" проходит вблизи\п"); р(" маркированных точек (за исключением начальной"); р(" и конечной\п точек).\п"); p("SR : Как SF, но формируется замкнутая криваяАп"); р("Если точки I и Е совпадают с соответствующими"); р(" точками последовалп"); р("тельности, то вместо всей последовательности"); р(" маркированных точек\п"); р("используется только ее часть от точки Г); р(" до точки Е. (Отрезок 1Е\п"); р("будет отмечен стрелкой.)\п\п"); р("Последовательность точек может быть"); р(" отредактирована с помощью\п"); р("команд SD and SA (см. 4.). После совмещения"); р(" курсора с маркированЛп"); р("ной точкой (используя команды F, ] или [),"); р(" можно ввести новую\п"); р("точку сразу же после маркированной точки путем"); р(" установки курсорахп"); р("в позицию новой точки и использования команды SAAn"); break; case *': case An': break; default: agaln-O; ungetch(ch); } if (again) { ch - getche(); ch - toupper(ch); } } }
Приложение А ОБЗОР ПАКЕТА GRPACK Программисты, пользующиеся компилятором Lattice С вер- сии 3.0, могут связать свои программы с файлом GRPACK.OBJ и воспользоваться всеми графическими возможностями, перечис- ленными ниже. Сформированные таким образом программы ра- ботают на любом персональном компьютере фирмы IBM (или со- вместимом с ними) под управлением операционных систем PC DOS или MS DOS либо с цветным графическим адаптером, либо с монохромным графическим адаптером (совместимым с "платой Геркулес"). Введено различие между вещественными экранными коорди- натами (в дюймах), обозначаемыми строчными буквами дс, у и целочисленными пикселными координатами, обозначаемыми прописными буквами Ху У. Начало Ос=0.0, у=0.0) вещественной координатной системы лежит в нижнем левом углу экрана 0.0 < х < х_тах 0.0 < у < yjnax Значения xjnax и yjnax равны 10.0 и 7.0 соответственно, ес- ли не была вызвана функция setprdimi), см. ниже. Начало пик- селной системы координат (X = 0, У = 0) располагается в верхнем левом углу экрана 0.0 < X < Х_тах 0.0 < У < Y_max Значения X max и У max равны: X max У max Для цветного графического адаптера 639 199 Для монохромного графического адаптера 719 347
Приложение А. ОБЗОР ПАКЕТА GRPACK 221 Значения переменных xjnax, yjnax, X max, У max опре- деляются внутри пакета GRPACK. Их значения могут быть использованы после объявления extern float xmax, ymax; extern int X max, Y max; Ниже приводится список всех доступных графических функ- ций с кратким описанием. Также показывается смысл аргумен- тов, если они имеются. Более подробная информация содержит- ся в книге. initrgi) Инициализация графического пакета: видеодис- плей переключается в графический режим. За исключением функции setprdimi ) ко всем остальным функциям можно обращаться только после вызова данной функции. endgri) Ожидается нажатие какой либо клавиши; затем видеодисплей переходит в текстовый режим. Обращение к этой функции должно стоять обяза- тельно перед завершением выполнения програм- мы (может быть заменено вызовом функции tojext). tojtexti) Немедленный возврат в текстовый режим. moveix, у) Перенос фиктивного пера в точку Ос, у) (вещест- венные координаты). imoveix, у) Перенос пера в точку (X, У) (пикселные коорди- наты) . drawix, у) Вычерчивание отрезка прямой линии из старой позиции пера в новую позицию Ос, у). idraw(X, У) Вычерчивание отрезка прямой из старой позиции пера в новую позицию (X, У). drawJine(X\, У1,Х2, У2) Вычерчивание отрезка прямой линии из точки (XI, У1) в точку (Х2, У2) (без изменения текущей позиции пера). circle (хС, уС, г) Вычерчивание окружности с центром в точке (хС, уС) и радиусом г. dot(X, У) Включение пиксела в точке (X, У).
222 Приложение А. ОБЗОР ПАКЕТА GRPACK Отрезки прямых, окружности и пикселы могут быть стерты, если будет включено объявление extern int drawmode; и переменной drawmode будет присвоено значение -1. В этом случае функции draw, idraw, drawjine, circle и dot Ъуяут вызы- вать выключение пикселов. По умолчанию значение перемен- ной drawmode равно 1, что вызывает включение пикселов. В ка- честве третьей возможности состояние пикселов можно "пере- ключать" путем присвоения переменной drawmode значения 0. В этом случае эти функции будут вызывать включение соответ- ствующих пикселов, если они темные, или делать их темными, если они были подсвечены. Это правило не относится к последу- ющим функциям. clearpagei) Стирание всего экрана. filKx, у) Заполнение подсвеченными пикселами замкну- той области, внутри которой расположена точка Ос, у). Учитывайте возможность "заливки" всего изображения, если область окажется не полно- стью замкнутой! pixfilUX, У) Заполнение подсвеченными пикселами замкну- той области, внутри которой находится точка (X, У). Будьте осторожны! text(str) Отображение строки символов str в графическом режиме, начиная с текущей позиции пера (воз- можно полученной в результате обращения к функции imoveили move). В параграфе 5.6. было показано, как эту функцию использовать для отображения вновь разработанных символов. textXY(X, Y, str) Аналогична функции text (str), за исклю- чением начальной точки, которая в данной фун- кции задается явно. printgr(Xlo, Xhi, Ylo, Yhi) Распечатка содержимого пря- моугольника Xlo< X < Xhi, Ylo < У < Yhi. Мат- ричный принтер должен быть подключен к па- раллельному порту принтера и включен. См. также описание функции setprdimi).
Приложение А. ОБЗОР ПАКЕТА GRPACK 223 Для монохромного графического адаптера функ- ция printgr может применяться не только в графи- ческом режиме, но также и после выхода в тек- стовый режим! setprdim () "Установка размеров для печати". Если обраще- ние к этой функции было до вызова функции initgri), то впоследствии горизонтальные и вер- тикальные размеры будут отпечатаны в точном соотношении. Эту функцию не следует приме- нять, если графические результаты с правиль- ными пропорциями должны быть получены на экране дисплея. Следующие две функции возвращают целочисленные значения: pixlitiX, У) Возвращается значение 1, если пиксел (Х> Y) под- свечен, или 0, если пиксел темный. iscolori) Возвращаемое значение равно коду используемо- го графического адаптера: 1 : цветная графика, О : монохромная графика, -1 : нет графического адаптера.
Приложение Б МЫШЬ В КАЧЕСТВЕ УСТРОЙСТВА ГРАФИЧЕСКОГО ВВОДА Если IBM совместимый персональный компьютер оснащен мышью, то возможно окажется интересным, как получить вход- ные данные от этого устройства для наших графических про- грамм. В следующей программе показан очень простой путь для этого. Предполагается, что в компьютере установлен драйвер мыши фирмы Microsoft. Это программа с именем msmouse, кото- рая обычно приобретается вместе с мышью. Функция mouse в нижеследующей программе показывает, как для этой цели мо- жет быть использовано программное прерывание 51 (ЗЗН) /*MOUSEDEM.C: Demonstration of a mouse. */ /* After compilation, this program should be linked */ /* together with GRPACK.OBJ. */ /* Демонстрация работы с мышью. */ /* После компиляции эта программа должна быть */ /* отредактирована совместно с пакетом GRPACK.OBJ. */ #include "dos.h" extern Int drawmode, X_max, Y_max; /* Defined in GRPACK.C */ /* Переменные определены в пакете GRPACK.C */ main() { int buttons, Xm, Ym, XO, YO, X, Y, Xdev, Ydev, Xmin, Xmax, Ymin, Ymax; printf /* "This program assumes that the Microsoft mouse driver has" */ ("Эта программа предполагает, что драйвер мыши фирмы MicrosoftAn"); printf /* "been loaded (program 'msmouse' should have been executed)"*/ ("уже был загружен (то есть программа 'msmouse'6bMa выполнена).\п"); printf /* "If not, use Ctrl Break." */ ("Если нет, то нажмите клавиши Ctrl+Break.\n\n"); printf /* "The program demonstrates how x- and y-coordinates can" */ ("Программа демонстрирует процесс получения координат х и у\п"); printf /* "be derived from the position of the mouse. You can draw" */ ("при перемещении мыши. С помощью этой программы можно \п");
Приложение Б. МЫШЬ 215 printf /* "curves with this program." */ ("нарисовать различные кривыеЛгЛп"); printf /* "Press any key on the keyboard to start the demonstration." */ ("Нажмите любую клавишу на клавиатуре для начала работы.\п"); printf /* "The demonstration ends in the same way. */ ("Демонстрация заканчивается таким же образомЛп"); /* For each button on the mouse there is a bit in the variable" */ /* 'button', which is set as long as the button is pressed." */ /* The buttons are not used in this program." */ /* Для каждой кнопки на корпусе мыши в переменной 'button' */ /* отведен один бит, устанавливаемый в течение всего времени */ /* нажатия клавиши. Здесь кнопки не используются. */ getch(); lnltgr(); Xmin - 4; Xmax - X max - 4; Ymln - 2; Ymax - Y max - 2; drawmode-4); mouse(0, &buttons, &Xm, &Ym); /* Инициализация мыши */ mouse(3, &buttons, &Xm, &Ym); /* Ввод начальной позиции */ X - (X_max + 1)/2; Y - (Y_max + 1)/2; Xdev - X - Xm; Ydev - Y - Ym; cursor(X, Y); do { mouse(3, &buttons, &Xm, &Ym); X0-X;Y0-Y; X - Xm + Xdev; Y - Ym + Ydev; if (X < Xmin) X- Xmin; if (X > Xmax) X-Xmax; if(Y<Ymin)Y-Ymin; if (Y > Ymax) Y-Ymax; cursor(X0, YO); /* Erase old cursor */ /* Стирание старого курсора*/ cursor(X, Y); /* Draw new cursor */ /* Черчение нового курсора*/ draw_line(XO, YO, X, Y); /* Черчение отрезка */ } while (!kbhit()); to text(); } mouse(code, pbuttons, px, py) int code, *pbuttons, *px, *py; { union REGS regs; regs.x.ax-code; regs.x.bx-*pbuttons; regs.x.cx-*px; regs.x.dx-*py; №86(51, &regs, &regs); *pbuttons-regs.x.bx; *px-regs.x.cx; *py-regs.x.dx; } cursor(X, Y) int X, Y; { imove(X-4, Y-2); idraw(X+4, Y-2); idraw(X+4, Y+2); idraw(X-4, Y+2); idraw(X-4, Y-2); }
Литература Ammeraal, L. (1986) Programming Principles in Computer Graphics, Chichester: John Wiley & Sons. [Имеется перевод: Аммерал Л. Принципы программирования в машинной графике. М.] Ammeraal, L. (1986) С for Programmers, Chichester: John Wiley & Sons. Foley, J. D., and A. van Dam (1982) Fundamentals of Interactive Computer Graphics, Reading, Mass.: Addison-Wesley. [Име- ется перевод: Фоли Дж., вэн Дэм А. Основы интерактивной машинной графики: В 2-х книгах. — М.: Мир, 1985. ] IBM Corporation (1983) Technical Reference. Boca Raton, FL: IBM Corporation. Newman, M. N., and R. F. Sproull (1979) Principles of Interactive Computer Graphics, New York: McGraw-Hill. [Имеется перевод первого издания (1973): Ньюмен У., Спрулл Р. Основы интерактивной машинной графики. — М.: Мир, 1976.] Norton, P. (1983) Inside the IBM PC, Bowie, MD.: Brady. Rogers, D. F. (1985) Procedural Elements for Computer Graphics, New York: McGraw-Hill. Sargent, M., and R. L. Shoemaker (1984) The IBM Personal Com- ' puterfrom the Inside Out, Reading, Mass.: Addison-Wesley.
предметный указатель Аварийное завершение программы 52 адаптер 21 графический 21 тип 40 иаскиии коды 135 ассемблера язык 19, 24 ASCII 135 Базовая система ввода/вывода процедуры 17,24,35,45,95,98,105 беззнаковый символ 10 битовое отображение 23, 44 блок команды над блоками 172, 184 конец 172 копирование 172 начало 172 перенос 172 стирание 172 удаление 172 Брезенхама алгоритм 29,112 В-сплайн 179,186 Ввода устройство 224 ввода/вывода порт 16, 95 вектор 173 команды над векторами 184 вещественные координаты 107 видеодисплей 63 внешние (переменные) 78 выход 155 выход из графического режима 49 выходной порт 46 Гладкая кривая 179 графический адаптер 21 ввод 153,224 режим 44 Данных регистры 17, 46 деление отрезка 176 дерево 81 диагональ 52 динамическое распределение памяти 73 дисплейный контроллер 44, 47 диффузная составляющая 88 дуга 174, 184 Единица длины 176,185 Заливка 178 замкнутая граница 76 ,105 заполнение области 76,178,185 запрос на обслуживание 14 запуск программы DIG 182 звездочка 176 зеркальная компонента 86 зеркальная поверхность 88 Инвертирование 64 индексный регистр 45 интеграла знак 150 интенсивность 84 интерактивная графика 152 Клавиатура 10,49 клавиши со стрелками 154 Ctrl-клавиша 170 Ctrl-Break 13 Ctrl-C 13, 181 Ctrl-Z 97 команды системы DIG ВВ (начало блока) 172 49 ВС (копирование блока) 172 BD (удаление блока) 172 BE (стирание блока) 172 ВК (конец блока) 172 ВМ (перенос блока) 172 С+ (положительная дуга) 174 С- (отрицательная дуга) 174 CF (полная окружность) 173 DD (задание направления) 176 DN (новая точка) 176 DU (задание единиц) 176 F (найти) 171 PD (перо опустить) 168
228 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ PF (заполнить замкнутую область) 178 PU (перо поднять) 168 S< (перемещение в начальную точку) 180 5> (перемещение в конечную точку) 180 S [ (перемещение в предыдущую точку) 180 S] (перемещение в следующую точку) 180 т SC (очистка набора) 171 SF (подбор кривой) 179 SI (набор невидимый) 171 SR (подбор замкнутой кривой) 179 У А (вектор в стрелку) 175 VD (вектор в штрих) 175 VK (сохранение стрелки) 175 VL (вектор в линию) 175 VS (вектор в отрезок) 175 WA (текстовый режим) 167,169 WL (режим черчения) 167 WT (возврат в текстовый режим) 167 конец ввода 97 консольное прерывание 14, 54 конфигурации переключатель 46 координатная система 26 кривой сглаживание 179, 186 курсор 153 курсора команды 186 Ламберта закон косинусов 86 Латтис Си, компилятор 9, 13 Маркированные точки 171 матричный принтер 93,177,185 меню 167, 181 монохромный графический адаптер 21,41 монохромный дисплейный адаптер 21 мультипликация 63 мышь 224 Набор маркированных точек 171, 183 направление 176,185 новая точка 176, 185 новый символ 150 Область 178 окно 166 оконтуривание 84 окружность 104, 166, 184, 221 оператор редактирования 37 оператор static 28, 45, 78 освещенности модель 86 отраженный луч 86 ошибка 52 Памяти модель 12 параллельный порт 95 переключатель DIP 101 переключение, режим 64 переменная drawmode 64,222 horfact 27 lastchar 66,78 offset 13,66,78 stsize 155 vertfact 25 X__max 27, 104,108 xjnax 26, 108 Y_max 27,104,108 yjnax 26, 108 перенос кода 84 пересечения точка 72 перо опустить 159 поднять 159 печати команда 178 пиксел 21 пикселные координаты 27, 220 плата адаптера 21 побитовые команды И 65 Исключающее ИЛИ 65 ИЛИ 37,65 полутонов обработка 83 помощь 210 пороговый алгоритм 84 порт 46 порт управления режимом дисплея 46 прерывание 13, 18 клавиша 13 программное 18,36,98 флаг проверки 14 принтер 93, 177 программный генератор 142 продолжение отрезка 177 пропуски в отрезках 161 пространственная функция распределения 86 пустой символ 154 Размеров соотношение 166 разрешающая способность 21, 24 рамка 178
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 229 рассеянная составляющая 86 растровая графика 9, 83, 93 расширение имени файла 166, 173 регистр 12, 18 редактор связей 45 рекурсии глубина 81 ромашковый принтер 93 Сверху-вниз 165 связанный список 73 сегмент 12 сегментные регистры 12 символов генератор 22, 130 система автоматизированного проектирования 152 случайное число 73 снизу-вверх 165 состояния переключение 183 стек 20 максимальный размер 54 переполнение 53, 78,81 размер 20,81 стирание 63,70 страница 23,45 стрелка 174 структура 73 сфера 85 Текстовый режим 44, 52 текущая позиция пера 28 точечная фигура 94 Управляющий порт 46 Файл расширение имени 166, 173 CHARS.C 142 CHARS.TXT 136, 142 CHARSGEN.C 142, 143 DIG.C 187 DIGFUN.C 193 DIGH.C 210 dos.h 17 GRPACK.C 92,114,220 LINDRAW.C 56 LPT1 91 math.h 111 Фонга модель освещенности 88 функции brfun 55 check 28,53 checkbreack 14 clear page 63, 226 cos 112 cur 153 dmode 160 dot 29, 36, 39, 42, 64, 221 draw 8, 28, 221 drawjine 29,221 endcolgr 50 endgr 8, 50, 221 endmongr 51 fatal 53 fill 77,178,222 fopen 96, 103 getch 11,49 getche 11 idraw 133,221 imove 133,221 initcolgr 46 initgr 8,44,221 inp 17 inm 19 iscolor 44,223 IX 27 IY 27 kbhit 11,14,69 move 8, 27, 221 onbreak 15,55 outp 17,46 peek 13,37 penposition 162 pixfill 78,222 pixlit 76,223 poke 13,37 prchar 98 printgr 104 setgrcon 106 setprdim 109,223 sin 112 text 132,222 textXY 132,222 time 75 tojext 50,221 ungetch 11 Цветной графический адаптер 21, 23,45 Шрифт 130 Экрана границы 53 экрана дамп 104 экранная память 13, 22, 35 экранные координаты 26, 220 эллипс 104,166 эскизирование 153,169 эхо 11,50
Оглавление ПРЕДИСЛОВИЕ 5 Глава 1. ВВЕДЕНИЕ 7 1.1. История появления этой книги 7 1.2. Несколько замечаний для программистов на языке си 9 1.2.1. Тип данных unsigned char 10 1.2.2. Непосредственный ввод с клавиатуры 10 1.2.3. Модели памяти; считывание и запись 12 1.2.4. Консольное прерывание 13 1.2.5. Доступ к портам ввода/вывода микропроцессора 8088 16 1.2.6. Регистры и программные прерывания 17 1.2.7. Максимальный размер стека 20 1.3. Графические адаптеры 21 Глава 2. ВЫЧЕРЧИВАНИЕ ЛИНИЙ 26 2.1. Экранные и пикселные координаты 26 2.2. Вычерчивание линий в целочисленной арифметике 29 2.3. Использование прерывания 10h для подсветки пикселов 35 2.4. Непосредственный доступ к экранной памяти 36 2.5. Распознавание типа адаптера 40 2.6. Включение графического режима _ 44 2.7. Выход из графического режима 49 2.8. Аварийное завершение программы 52 2.9. Использование клавиши BREAK в графическом режиме 54 2.10. Пакет для вычерчивания линий 56 2.11. Пример 61
Глава 3. РЕДАКТИРОВАНИЕ НА ЭКРАНЕ 63 3.1. Битовые операции в экранной памяти 63 3.2. Вращающаяся звезда 68 3.3. Движущаяся кривая 72 3.4. Быстрая программа для заполнения области ^ 76 3.5. Обработка полутонов 83 Глава 4. ГРАФИКА И МАТРИЧНЫЕ ПРИНТЕРЫ 93 4.1. Принципы работы матричных принтеров 93 4.2. Программы, печатающие графический результат 95 4.3. Печать экранного дампа 104 4.4. Распечатка окружности в виде окружности 107 4.5. Тексты программ модуля GRPACK.C 114 Глава 5. ВЫВОД ТЕКСТА В ГРАФИЧЕСКОМ РЕЖИМЕ 130 5.1. Битовые комбинации для символов 130 5.2. Функция для вывода текста в графическом режиме 132 5.3. Формирование печатаемых символов 135 5.4. Программный генератор для шрифтов 142 5.5. Демонстрационная программа 148 5.6. Разработка новых символов 150 Глава 6. DIG — СИСТЕМА ИНТЕРАКТИВНОЙ ГРАФИКИ 152 6.1. Введение 152 6.2. Перемещение курсора 153 6.3. Операции по эскизированию 159 6.4. Руководство для пользователя 165 6.4.1. Начало и окончание программы. Рабочие состояния 166 6.4.2. Курсор, положение пера и режимы черчения 168 6.4.3. Рабочее состояние "альфа" (текст) 169 6.4.4. Наклонные линии и наборы маркированных точек 170 6.4.5. Команды блокирования 172 6.4.6. Векторы, окружности и дуги 173 6.4.7. Задание новой точки 176 6.4.8. Применение матричного принтера; заполнение области 177 6.4.9. Кривая типа В-сплайна 179 6.4.10. Сводка команд 181
6.5. Исходные тексты. 6.5.1. Текст программы DIG.С (основная программа) 6.5.2. Текст программы DIGFUN.C (функции) 6.5.3. Текст программы DIGH.C (сообщения помощи) Приложение А. ОБЗОР ПАКЕТА GRPACK Приложение Б. МЫШЬ В КАЧЕСТВЕ УСТРОЙСТВА ГРАФИЧЕСКОГО ВВОДА Литература Предметный указатель МАШИННАЯ ГРАФИКА НА ЯЗЫКЕ СИ В ЧЕТЫРЕХ ТОМАХ Перевод с английского языка. Четыре книги представляют единый комплекс и предназна- чены для широкой аудитории — от профессионалов до начинаю- щих программистов. Аммерал Л. ПРИНЦИПЫ ПРОГРАММИРОВАНИЯ В МАШИННОЙ ГРАФИКЕ Практическое введение в машинную графику. Рассматриваются вопросы аналитической и проек- тивной геометрии и программирования в машинной графике, задачи формирования В-сплайна, перспек- тивных проекций и удаления невидимых линий. Ал- горитмы доведены до "готовых к работе** графиче- ских программ на языке Си. В приложении дается краткий обзор элементов языка Си. 186 187 193 210 220 224 226 227
Аммерал Л. МАШИННАЯ ГРАФИКА НА ПЕРСОНАЛЬНЫХ КОМПЬЮТЕРАХ Излагается концепция построения программного обеспечения машинной графики нижнего уровня на языке Си для персональных компьютеров. Приво- дится пакет графических подпрограмм, реализую- щий ряд полезных функций, которые можно исполь- зовать в прикладных программах. В конце книги дается пример простой интерак- тивной программы для эскизирования и черчения. Аммерал Л. ИНТЕРАКТИВНАЯ ТРЕХМЕРНАЯ МАШИННАЯ ГРАФИКА Описывается интерактивная программная систе- ма для работы с пространственными объектами. Вы- полняются операции формирования, преобразова- ния, получения проекций с удалением невидимых линий, в том числе для криволинейных поверхно- стей. Приведены примеры программ для генерации описаний цилиндра, конуса, тора, пространственных гладких кривых и стержней. Вывод изображений на экран монитора, матричный принтер и плоттер. Аммерал Л. ПРОГРАММИРОВАНИЕ ГРАФИКИ НА ТУРБО СИ Расширение возможностей применения графиче- ского пакета на Турбо Си для системы координат поль- зователя. Показано, как на языке Турбо Си включать графику в текстовые документы, использовать сигна- лы прерывания, формировать меню в интерактивной программе, использовать "мышь" в качестве устройст- ва ввода и генерировать файл для вывода графических изображений на плоттер. С запросами и заказами книг и дискет обращайтесь по адресу: fi/M^UC^HC^H 103104 Москва К-104, а/я 38
ВНИМАНИЕ РАЗРАБОТЧИКОВ! Московское предприятие БИНОМ предлагает подробные технические описания микросхем и документацию на кросс-системы программирования. Приводится полная текстовая и графическая (временные диаграммы, типовые схемы включения, типовые зависимости параметров микросхем и т. п.) информация, необходимая для освоения и использования микросхем. Авторами материалов являются непосредственные разработчики описываемых микросхем. Заказ документации — гарантийным письмом. При наличии требуемого Вам тома мы вышлем счет на оплату. Цена одного тома документации с учетом НДС — по состоянию на 1 июля 1992 г. — 630 рублей. Точная цена указывается в счете, без оплаты которого документация не высылается. Убедительная просьба в гарантийном письме указать Ваш точный почтовый адрес, на чье имя высылать счет и документацию, а также телефон адресата. Заказчики с Украины производят оплату на расчетный счет нашего предприятия в Киеве. Заказчики из других стран СНГ — на расчетный счет МП БИНОМ в Москве. Гарантийные письма высылайте на адрес МП БИНОМ: 103473, Москва-473, а/я 133 БИНОМ. СПИСОК ТОМОВ ДОКУМЕНТАЦИИ Ниже приводится содержание томов технической документации, поставляемых МП БИНОМ. ТОМ 1. ОДНОКРИСТАЛЬНЫЕ МИКРОЭВМ СЕМЕЙСТВ МК48 И МК51:
KP1816BE35,39,49; KM1816BE48; KP1816BE31.51; KM1816BE751; KP1830BE35.48; KP1830BE31.51; KM1830BE751J53; KP1835BE49; KP1835BE31,51;KP1850BE35,39,40,48,50; KP1850BEC35,39,40,48,50; KP1850BE31. Серии 1816, 1850BE — пМОП микросхемы Серии 1830, 1835.1850ВЕС — КМОП микросхемы ТОМ 2. КРОСС-СИСТЕМЫ ПРОГРАММИРОВА- НИЯ ОМЭВМ СЕМЕЙСТВ МК48 И МК51: АССЕМБЛЕРЫ ASM48 И ASM51; ИНТЕРПРЕТАТОРЫ INT48 И INT51; РЕДАКТОР СВЯЗЕЙ RL51; БИБЛИОТЕКАРЬ LIB51, ПРОГРАММНО- ЛОГИЧЕСКИЕ МОДЕЛИ. Для IBM-совместимых компьютеров. ТОМ 5. ПЕРИФЕРИЙНЫЕ БИС ПОДДЕРЖКИ МИКРОПРОЦЕССОРОВ И ОМЭВМ: КР1810ВК56. ВТ37, ВН59А, ВИ54. КР1810ВК56 — универсальный контроллер поддержки микропроцессоров: два 8-разрядных параллельных порта, последовательный порт, пять программируемых таймер/счетчиков, контроллер прерываний на 8 уровней с возможностью каскадирования. Сопрягается с сериями- 1816, 1830, 1810. 1821. ТОМ 6. ИНТЕГРАЛЬНЫЕ МИКРОСХЕМЫ СЕРИИ КР1533. Серия логических микросхем, ТТЛШ, аналог 74ALSXXX, задержка на вентиль 5нс, потребляемая мощность 1.0 мВт/вентиль. ТОМ 7. ИНТЕГРАЛЬНЫЕ МИКРОСХЕМЫ СЕРИИ КР1554. Серия логических микросхем, КМОП, аналог 74АСХХХ, задержка на вентиль 3.2нс, потребляемая мощность 0.0025мВт/вентиль, выходной ток 24мА.
вниманию системных программистов ремонтного персонала разработчиков аппаратуры Общество "КОНСУЛ" предлагает книгу "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT". Авторами книги являются ведущие специалисты Минского НИИ ЭВМ. Вся приведенная информация есть результат обобщения многолетнего опыта коллектива авторов в исследованиях и разработке PC АТ-совместимых компьютеров. Полнота объема, последовательность изложения, подробность описания выгодно отличают книгу от других изданий и технических описаний, в которых бессистемно изложены сведения и случайно выбраны таблицы, рисунки, чертежи из "User's guide", чем грешит сегодня практически вся справочная литература по PC. При подготовке книги были использованы современные зарубежные издания по архитектуре, системному программированию и разработке аппаратных средств PC. В книге подробно изложена информация об архитектуре и подсистемах PC AT, приведены законченные примеры реализации программных и аппаратных средств на базе PC AT. Текстовый объем книги - 950 страниц энциклопедического формата. Заказ книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" — гарантийным письмом. По получении гарантийного письма Вам, в порядке очередности, будет выслан счет на оплату. Цена книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" с учетом НДС по состоянию на 1 июня 1992г.- 896 рублей. Точная цена указывается в счете, без оплаты которого книга не высылается.
Убедительная просьба в гарантийном письме указать Ваш точный почтовый адрес, на чье имя высылать счет и книгу, а также телефон адресата. Гарантийные письма высылайте на адрес 220012, г.Минск, ул.Толбухина, 21, Общество "Консул" содержание и объем книги "РУКОВОДСТВО ПО АРХИТЕКТУРЕ IBM PC AT" по разделам объем в стр. 1. Принципы построения процессора PC AT 150 2. Микропроцессоры и сопроцессоры 33 3. Архитектура и системное программирование микропроцессоров 80286/80386, 80287/803878 118 4. Системная память PC AT 40 5. Системная шина PC AT 30 6. Программируемые системные устройства 97 7. Система прерываний 25 8. Базовая система ввода-вывода (BIOS) 159 9. Клавиатура 25 10. Средства машинной графики 52 11. Интерфейсы PC AT 19 12. Дисковая подсистема 29 Приложения: 168 Функциональная схема процессора Общий список прерываний DOS-BIOS Прерывания и системные функции DOS Структура файлов типа ЕХЕ, загрузка и выполнение программ Пример программы для работы в защищенном режиме МП 80286/80386 - Порты ввода-вывода процессорного ядра Порты программируемых системных устройств Порты адаптера MDA Порты адаптера CGA Порты адаптера EGA Порты адаптера VGA Система питания PC AT ( Полное оглавление занимает 5 страниц.)
Средства для профессиональной работы с изображениями на PC Набор утилит DOS Imagic 1.0 * подбор палитры 16 - 256 цветов, * сжатие изображений методом JPEG, * улучшение качества и создание худо- жественных эффектов * подготовка изобра- жений для печати (10 растров), * цена 5760 руб. "Карандашный набросок", полученный из фотографии Пакеты программ в средах GEM и WINDOWS * GreyView - улучшение качества,редактирование и подготовка к печати полутоновых изображений 12800 руб. * Imagic 2.0 - обработка цветных изображений и преобразование графических форматов 8960 руб. * 3D View - сборка-трехмерных объектов и подготовка их цветных изображений 12800 руб. * MDBase - создание и управление базами цветных изображений 9600 руб. Платы ввода - вывода цветных изображений Покг STOIK0 Тел.: (095) 205 23 00,(095) 535 29 40 Москва, Новый Арбат, 34