Достижения современных мультимедийных технологий активно
Машинная графика
Достижения современных мультимедийных технологий активно проявляются в компьютерных играх, в рекламе на многочисленных Internet-сайтах, в научно-фантастических и приключенческих фильмах, в телевизионных роликах и т. п. И наиболее важной программной компонентой во всем этом разнообразии является компьютерная графика. Рассматриваемые в нашем пособии системы программирования появились 12—15 лет тому назад и их изобразительные средства весьма ограничены. Но мы не можем не познакомить читателя с базовыми графическими процедурами, которые оказываются столь полезными при оформлении результатов работы наших программ.
Инициализация графического режима
Переход в графический режим очень напоминает подготовку к работе с файлами. В жаргоне программистов бытует термин "открыть графику", и это надо сделать прежде, чем вы обратитесь к любой графической процедуре. Что реально скрывается за этим действием, знать не обязательно.
В QBasic графический режим работы монитора устанавливается после выполнения оператора SCREEN, содержащего от одного до четырех параметров:
SCREEN n [, [cs] [,ap] [,vp]]
0бязательным является только первый параметр, определяющий номер графического режима. Рекомендуемые значения:
n= 9 (640x350 точек, 16 цветов из 64 возможных)
n=12 (640x480 точек, 16 цветов из 64 возможных)
n=13 (320x200 точек, 256 цветов из 256К возможных)
Параметр cs в приведенных режимах смысла не имеет. Два последних параметра задают номера активной (ар = о или ар = i) и видимой (vp = 0 или vp = i) страниц в режиме n = 9.
Возврат в текстовый режим осуществляется по оператору SCREEN 0.
Программы на Си или Паскале, использующие графический вывод, должны подключить соответствующие системные средства:
ТС: #include <graphics.h>
ТР: uses Graph;
В среде BGI графика "открывается" обращением к процедуре initgraph:
ТС : initgraph(&gd,&gm, "path");
ТР : InitGraph(gd,gm,'path');
Первые два параметра представлены именами целочисленных (int, integer) переменных, в которых графическая система запоминает условные номера графического драйвера (gd) и графического режима (gm). Программист больше этими переменными не пользуется, но и не должен затирать их значения. 0бычно в переменную gd перед обращением к initgraph заносят нулевое значение (gd = о или gd = DETECT), что заставляет графическую систему определить тип видеосистемы и выбрать подходящий режим ее работы без. участия программиста. Как правило, будет установлен режим с названием VGAHI, соответствующий разрешению 640x480 с 16 цветами из 64 возможных.
Третий параметр задает путь к каталогу, в котором находится драйвер видеосистемы — программа, реализующая связь библиотечных процедур с видеокартой. В 99% случаев такой программой является файл egavga.bgi, который программисты копируют в свой текущий каталог или прокладывают к нему дорожку с помощью команды PATH в файле autoexec.bat. В любой из этих двух ситуаций третий параметр может быть задан пустой строкой. Но если графическая система не обнаружит нужный драйвер в доступных каталогах, то работа программы будет прекращена из-за того, что "открытие" графики не состоялось.
Возврат в текстовый режим обычно происходит при обращении к
closegraph:
ТС: closegraph();
ТР: CloseGraph;
Не забывайте "закрывать" графику перед окончанием работы вашей программы, иначе после возвращения в интегрированную среду русские буквы в комментариях и текстовых константах превратятся в нечто странное.
Как формируется RGB-цвет пикселов
Сокращение RGB происходит от английских обозначений базовых цветов -Red (красный), Green (зеленый) и Blue (синий). Именно эти цвета, смешанные в определенной пропорции, и управляют окраско'й пикселов на экране. В видеопамяти каждому пикселу отводится 2, 4, 8 или 24 двоичных разряда, запоминающих код цвета. В стандарте VGA используются только 4 бита, по которым определяется один из 16 регистров палитры, участвующий в формировании цвета. В каждом 6-разрядном регистре палитры по 2 разряда используются для задания интенсивности соответствующей RGB-компоненты. Это дает возможность манипулировать 64 цветами, но выбрать из них в каждый конкретный момент можно только 16. На самом деле механизм формирования сигнала управления цветом гораздо сложнее и в нем задействованы еще 256 18-разрядных регистров DAC (Digital Analog Converter — цифро-аналоговый преобразователь, русское сокращение ЦАП). С их помощью гамма цветовых оттенков увеличивается до 256 К.
В каждый момент графическая система имеет дело с двумя ранее установленными цветами — цветом переднего плана (цветом рисования —foreground color) и цветом заднего плана (цветом фона — background color). Цвету фона всегда соответствует нулевой программный код цвета. Физический цвет фона определяется содержимым регистра палитры с номером 0. По умолчанию в нем находится нулевой шестибитовый код, задающий нулевую интенсивность по каждой из базовых компонент цвета, что соответствует черному цвету. Изменение содержимого этого регистра изменяет фон экрана только в тот момент времени, когда программа обращается к процедуре очистки экрана.
В отличие от этого, изменение цвета пиксела в процедурах рисования происходит мгновенно, в момент записи нового кода цвета в соответствующую область видеопамяти. Видеокарта позволяет сформировать новый цвет как результат взаимодействия старого и нового кодов по выбранному правилу. Среди возможных правил — затирание прежнего кода новым кодом цвета или его инверсией, взаимодействие старого и нового кодов по одной из логических операций — AND, 0R, X0R. 0собо хотим обратить внимание на последнюю логическую операцию, которая используется для стирания или "проявления" изображений. Если на код цвета пиксела накладывается такой же двоичный код по операции X0R, то результат будет нулевым, что соответствует цвету фона или стиранию прежней точки. Если на нулевой код цвета по операции X0R наложить прежний цвет пиксела, то точка "проявится". Этой возможностью часто пользуются при анимации изображений.
Краткий обзор графических возможностей систем программирования
В каждой из трех рассматриваемых систем программирования предусмотрены базовые графические средства, с помощью которых можно устанавливать нужный режим работы видеосистемы, управлять цветовой палитрой, рисовать Простейшие геометрические фигуры и раскрашивать их, снабжать рисунки пояснительными подписями.
В системе QBasic эти средства встроены в язык — наряду с обычными операторами в программе можно использовать графические: LINE, CIRCLE, DRAW и др. С одной стороны, набор базовых графических операций QBasic, несколько беднее, чем состав аналогичных процедур и функций в системах фирмы Borland. В частности, QBasic значительно уступает своим конкурентам по возможностям отображения текстовых сообщений и управления шрифтами в графическом режиме.
С другой стороны, QBasic имеет в составе своих изобразительных средств элементы "черепашьей" графики, позволяющей строить графические процедуры любой сложности и использовать сформированные таким образом графические объекты как строительные кирпичики, включая их в состав более сложных объектов. "Черепашья" графика появилась как обобщение системы команд управления пишущим узлом перьевого плоттера. Такие процедуры, как смена, подъем и опускание пера, перемещение пишущего узла на заданное число шагов по одному из восьми направлений и некоторые другие непосредственно заимствованы из этой системы команд. 0днако возможность создания графических подпрограмм и построение фигур с учетом различных преобразований (смещение, поворот, масштабирование, непропорциональное изменение размеров фигур вдоль осей координат) придают "черепашьей" графике необычайную гибкость. В нашем пособии оператор DRAW не затрагивается, однако для любителей экзотики мы рекомендуем увлекательную книгу [7], переводчик которой, на наш взгляд, вложил в нее гораздо больше, чем ее предполагаемый автор. Программирование в терминах микроскопических команд плоттера — занятие довольно утомительное, да и отладка таких программ доставляет немало забот. Не менее сложно запоминать односимвольные команды управления "черепашкой". До версии ТР 3.0 фирма Borland использовала "черепашью" графику, но в последующих реализациях системы отказалась от нее.
Графические средства систем Turbo С и Turbo Pascal построены на базе общего подхода, сокращенно именуемого BGI — Borland Graphics Interface (графический интерфейс фирмы Borland). 0ни вынесены в системные библиотеки, содержащие почти одинаковый набор процедур и функций с полностью совпадающими именами и аналогичным набором аргументов. Состав этих библиотек довольно внушителен — 83 графические программы и более 60 системных констант. Наиболее содержательное описание BGI-пакета можно найти в [16].
Небольшая разница между Си и Паскалем наблюдается в записи имен процедур и системных констант, составленных из нескольких ключевых слов. В обозначениях процедур Паскаль предпочитает выделять начало каждого ключевого слова прописной буквой, например — SetUserCharSize. В системе Turbo С, в отличие от Паскаля, возможен режим работы, при котором прописные и строчные буквы считаются разными. Поэтому в Си составные имена графических функций записываются только строчными буквами — setusercharsize, хотя это и менее наглядно. Паскаль распространяет свою технику выделения ключевых слов и на составные обозначения системных констант (например — wideDotFiii), тогда как в Си для этой цели обычно используют символ " ." (подчеркивание) и обозначают системные константы только большими буквами (например — WIDE_D0T_FILL). Еще одно небольшое отличие связано с записью функций без параметров — в программах на Си их имена всегда сопровождаются пустыми скобками:
ТС: x=getmaxx();
ТР: x:=GetMaxX;
Различия эти не носят принципиального характера, но в программах необходимо придерживаться правил, принятых в той или иной системе. В связи с этим графические программы на Си и Паскале мы постараемся не дублировать и будем отдавать предпочтение текстам программ на Си, т. к. существует достаточно много книг с примерами графических программ на Паскале.
О мониторах и графических системах
Существует довольно много разных типов мониторов, объединяемых названиями VGA (Video Graphic Array — видеографический массив) и SVGA (Super VGA). Монитор в сочетании с управляющей схемой, оформленной в виде отдельной платы (видеокарты) или специализированной микросхемы на материнской плате, образует так называемую графическую систему. Наиболее важными характеристиками графической системы являются габариты экрана, измеряемые длиной диагонали в дюймах (14", 15", 17", 19" и более), разрешающая способность, измеряемая максимальным количеством точек, отображаемых по горизонтали и вертикали (640x480, 800x600, 1024x768, 1280x1024 и более), и цветовая гамма, характеризуемая максимальным числом различимых цветовых оттенков (16 цветов, 64 цвета, 256 цветов, 256 тысяч цветов, 16 миллионов цветов). Несмотря на такое разнообразие, общей для всех видеосистем является возможность работать в текстовом или графическом режимах. На самом деле, и текстовых, и графических режимов насчитывается довольно много (порядка 18—20), но в рамках рассматриваемых систем программирования приходится иметь дело с весьма ограниченным их подмножеством.
В текстовом режиме экран напоминает лист бумаги в крупную клетку, на котором в каждой клетке можно поместить только один символ (букву, цифру, знак препинания и прочие разделители). При этом сама клетка может быть окрашена в один из 8 цветов, а контур буквы — в один из 16 цветов.
В графическом режиме экран напоминает миллиметровку, т. е. разбит на довольно много мелких клеток (сторона клетки порядка 0,3—0,4 мм для мониторов с 14—15-дюймовыми экранами). Такие клетки называют пикселами (pixel — от picture's element, "элемент рисунка") и каждый из них может быть окрашен в тот или иной цвет. Самый высокий графический режим, поддерживаемый нашими системами программирования, соответствует стандарту VGA — 640 точек по горизонтали, 480 — по вертикали, 16 цветов из довольно большого числа (256 К) возможных цветовых оттенков.
О системах координат и текущей точке
Как в текстовом, так и в графическом режимах начало экранных координат располагается в левом верхнем углу экрана. 0днако в текстовом режиме самая левая верхняя клетка имеет координаты (1,1), тогда как самый первый пиксел в графическом режиме имеет координаты (0,0). 0сь х направлена вправо, а ось у — вниз.
В отличие от Turbo С и Turbo Pascal система QBasic предоставляет пользователю возможность назначить свою собственную систему координат, привязав к противоположным углам области вывода минимальные и максимальные значения координат конкретной задачи по осям х и у.
Ряд графических процедур наряду с абсолютными координатами использует и относительные координаты, задаваемые в приращениях (dx,dy) относительно положения так называемой текущей точки (СР — Current Point). При активизации графического режима текущая точка помещается в начало координат. Ее последующие перемещения зависят от выполняемых операций. Например, при построении видимого или невидимого отрезка[К102] текущая точка перемещается из начала отрезка в его конец. При построении окружности текущая точка находится в центре и никуда не смещается после выполнения операции.
Иногда текущую точку называют графическим курсором, который,.в отличие от текстового, не изображается на экране, чтобы не исказить выводимую картинку. 0днако программа в любой момент может узнать координаты текущей точки.
О видеопамяти
Все, что представлено на экране дисплея, является отображением содержимого видеопамяти — специального запоминающего устройства, расположенного на видеокарте. В большинстве ПК емкость видеопамяти варьируется сегодня в диапазоне от 2 до 32 Мбайт, и чем больше ее размер, тем большими возможностями по разрешению, цветовой гамме и специализированным графическим операциям располагает компьютер.
В тех графических режимах, с которыми мы будем знакомиться, видеопамять предоставляет пользователю одну или две страницы с номерами 0 и 1. Каждая из них может быть объявлена активной и/или видимой. Содержимое видимой страницы представлено на экране дисплея, а в активной странице происходит формирование нового изображения. 0дна и та же страница может быть и активной, и видимой. С целью ускорения процедуры смены кадров в мультимедийных приложениях желательно смотреть на одну страницу, а рисовать в другой и в нужный момент произвести переключение страниц.
Все системы программирования позволяют в два-три приема произвести быстрый обмен между содержимым ячеек видеопамяти и оперативной памяти, что бывает крайне необходимо при анимации изображений.
Окружности, эллипсы и дуги
В системе QBasic полные окружности, эллипсы, их дуги или соответствующие сектора воспроизводятся с помощью одного и того же оператора
CIRCLE:
CIRCLE (x,y),R [,c] : ' Полная окружность
CIRCLE (x,у),R,[с],al,a2 : ' Дуга окружности
CIRCLE (x,y),R,[с],-al,-a2 : ' Круговой сектор
CIRCLE (x,y),R,[с],,,k : ' Полный эллипс
CIRCLE (x,y),R,[с],al,a2,k : ' Дуга эллипса
CIRCLE (x,y),R,[с],-al,-a2,k : ' Эллиптический сектор
Координаты центра могут быть заданы как абсолютными, так и относительными (CIRCLE STEP(х,у)...) значениями. Для окружности R задает радиус, а для эллипса R и k участвуют в определении полуоси а (вдоль оси х) и полуоси b (вдоль оси у). При k<la = RHb = k*R, а При k > 1 а = k * R и
b = R. Проведите компьютерный эксперимент со следующей программой:
Программа 8_06.bas
REM Построение эллипсов SCREEN 12
VIEW (0,0)-(400,400),,1
F0R X=10 T0 390 STEP 10
F0R Y=10 T0 390 STEP 10
LINE (X,10)-(X,390),7: ' построение вертикальных линий сетки
LINE (10,Y)-(390,Y),7: ' построение горизонтальных линий сетки NEXT Y NEXT X
LINE (0,200)- (400,200),14 : ' построение "оси" х
LINE (200,0)-(200,400),14 : ' построение "оси" у
CIRCLE (200,200),120,15,,,4/3 : ' белый эллипс
CIRCLE (200,200),120,14,,,3/4 : ' желтый эллипс
END
В квадрате со стороной 400 пикселов с шагом в 10 пикселов строится серая сетка и по центру зоны вывода проводятся желтые координатные оси. Затем строятся белый и желтый эллипсы с центрами в середине области графики. На этом рисунке можно легко определить размеры полуосей каждого из эллипсов.
В построении дуг принимают участие два угла al и а2, задаваемые в радианах. Их абсолютные значения принадлежат интервалу [0,2хл]. Углы отсчитываются от положительного направления оси х против часовой стрелки. Угол al задает положение начального радиус-вектора, проведенного из центра в начало дуги, а угол а2 — положение конечного радиус-вектора. Вообще говоря, это справедливо для дуг окружности. Для дуг эллипсов углы приходится подбирать, т. к. параметры оператора используются сначала для вычисления точек на дуге окружности, при трансформации которой в дугу эллипса углы начального и конечного радиус-векторов искажаются.
Если углы задаются со знаком минус, то соответствующие концы дуг соединяются с центром, образуя круговой или эллиптический сектор. Такие фигуры могут использоваться для построения круговых диаграмм. Приведенный ниже пример демонстрирует построение дуги и сектора, вписывающихся в один и тот же центральный угол эллипсов разного размера. Углы начального и конечного радиус-векторов заданы в радианах и, соответственно, равны 30 и 60 градусам.
Программа 8_07.bas
RЕМ Построение дуги и сектора эллипса
SCREEN 12
VIEW (0,0)-(400,400)
LINE (0,200)-(400,200),7 :' построение "оси" .х
LINE (200,0)-(200,400),7 :' построение "оси" у
CIRCLE (200,200),120,4,,,.75 :' полный эллипс
CIRCLE (200,200),90,4,,,.75 :' полный эллипс
CIRCLE (200,200),120,15,.5236,1.0472,.75 :' белая дуга
CIRCLE (200,200),90,14,-.5236,-1.0472,.75 :' желтый сектор
END
Четвертый необязательный параметр в операторе CIRCLE задает явный цвет соответствующей фигуры, который может отличаться от цвета переднего плана, установленного по оператору C0L0R.
В BGI-пакете для построения каждой из описанных выше графических фигур используется отдельная процедура:
arc(x,у,a1,a2,r); //дуга окружности
circle(х,у,r); //окружность
ellipse(х,у,al,a2,rх,rу); //дуга эллипса или полный эллипс
В отличие от QBasic положение начального и конечного радиус-векторов здесь задается целочисленными значениями углов al и а.2 в градусах, которые могут быть как положительными, так и отрицательными. Второе отличие связано с явным заданием обеих полуосей эллипса гх и гу.
Определение области графического вывода и выбор системы координат
По умолчанию областью вывода графики является весь экран. 0днако каждая из систем программирования позволяет объявить зоной вывода некоторую прямоугольную область, выход за пределы которой обычно заблокирован, но может быть и разрешен, если вы отключите режим отсечения. Последняя возможность предусмотрена только в рамках BGI-пакета.
В системе QBasic область графического вывода переопределяется оператором VIEW, имеющим две модификации:
VIEW (xl,yl)-(x2,y2),cf,cb
VIEW SCREEN (xl,yl)-(х2,у2),cf,cb
Каждая из них устанавливает прямоугольную область, левая верхняя вершина которой задана экранными координатами (xi,yi), а противоположная вершина — координатами (х2,у2). Попытка задать область, выходящую за пределы экрана (о < х < 639, о < у < 479), системой пресекается. Целые числа cf и сь задают, соответственно, цвет фона и цвет рамки, которая окаймляет область вывода. 0бласть вывода и ее границы сразу же окрашиваются в указанные цвета.
В определенной таким образом области вывода действует одна из двух оконных систем координат. В первой из них начало координат находится в точке с экранными координатами (xi,yi), т. е. в левом верхнем углу окна, ось х направлена вправо, ось у — вниз, абсолютные координаты точек только целочисленные и положительные. Добавка SCREEN переносит начало отсчета координат с таким же взаимным расположением осей в левый верхний угол экрана, т. е. в точку (0,0). В любом случае все, что имеет координаты за пределами области вывода, отображаться не будет.
Приводимый ниже пример сначала объявляет квадратную область вывода, начинающуюся в точке с экранными координатами (10,10), ширина и высота которой равна 200 пикселам. Начало координат в этом окне физически находится в точке с экранными координатами (10,10). Поэтому два следующих оператора LINE строят горизонтальную и вертикальную линии, точно проходящие через середину области. Второй оператор (VIEW SCREEN) объявляет такую же по размерам квадратную область, привязанную своим левым верхним углом к точке с экранными координатами (310,10). 0днако в этой области действует экранная система координат с началом в точке (0,0). И для построения аналогичных прямых, проходящих через центр второй области, их концы приходится задавать в координатах экрана. Выполнив этот пример на компьютере, вы можете убедиться в том, что на одном экране могут одновременно сосуществовать изображения в не перекрывающихся зонах вывода, но в каждый конкретный момент выполнения программы действует только одно окно вывода.
Программа 8_01.bas
RЕМ Демонстрация графических окон
SCREEN 12 :' Переход в графический режим
VIEW (10,10)-(210,210),2,1 : ' 0бъявление графического окна
LINE (0,100)-(200,100)
LINE (100,0)-(100,200)
SLEEP : ' Задержка до нажатия любой клавиши
VIEW SCREEN (310,10)-(510,210),4,14: ' Новое окно
LINE (310,110)-(510,110)
LINE (410,10)-(410,510)
SLEEP
END
В момент создания зоны вывода первого типа (VIEW) текущая точка устанавливается в центр окна, тогда как для области вывода второго типа (VIEW SCREEN) текущая точка переводится в центр экрана и может оказаться за пределами окна. С учетом последнего замечания построения в окне можно вести не только в абсолютных координатах, определяемых описанными выше способами, но и в относительных координатах. Последние задаются в приращениях относительно положения текущей точки и сопровождаются добавкой STEP:
Программа 8_02.bas
RЕМ Построения в относительных координатах
SCREEN 12
VIEW (10,10)-(210,210),2,1
LINE STEP(0,0)-STEP.(-20,0) : ' 0трезок белого цвета
C0L0R 4 : ' Цвет рисования - красный
LINE STEP(0,0)-STEP(0,-20)
C0L0R 2 : ' Цвет рисования - зеленый
VIEW SCREEN (310,10)-(510, 210) , 4,14
LINE STEP(0,0)-STEP(-20,0)
END
В приведенном выше примере первый отрезок белого цвета начинается в центре области (нулевые смещения начальной точки отрезка означают совпадение с положением текущей точки). Конец отрезка смещен на 20 пикселов по горизонтали влево. В эту же точку перемещается СР после построения первого отрезка. Второй отрезок красного цвета начинается из конца первого отрезка
и продолжается вверх по вертикали на 20 пикселов (в направлении, противоположном оси у). Для второго окна текущая точка оказалась снаружи, поэтому горизонтальный отрезок зеленого цвета мы не увидим.
Программирование перемещений в целочисленных координатах пикселов далеко не всегда бывает удобным с точки зрения прикладной программы. Например, чтобы построить график синусоиды, ее амплитуду придется умножить на достаточно большое число, чтобы увеличить размах по оси у, принудительно увеличить значение функции, чтобы исключить отрицательные координаты точек, растянуть значение координаты х таким образом, чтобы на видимом участке оказались один-два более или менее красивых периода функции, и учесть специфику в направлении оси у (Y = у - уmах). Система QBasic берет все эти хлопоты на себя. Достаточно лишь после объявления области вывода указать пределы изменения координат (х,у) в отображаемых объектах с помощью оператора WIND0W:
WIND0W (XMIN,YMIN)-(XMAX,YMAX)
Построение одного периода синусоиды, например, может выглядеть следующим образом:
Программа 8_03.bas
RЕМ Построение синусоиды
SCREEN 12
VIEW (10,10)-(410,210),2,1
LINE (400,100)- (0,100) ' Построение оси х и возврат СР в ее начало
WIND0W (0,-1)-(б.З,1) ' 0бъявление пределов изменения функции
F0R X=.l Т0 6.3 STEP .1
Y=SIN(X) ' Вычисление значения функции
LINE -(X,Y) ' Проведение отрезка прямой из СР в (X,Y)
NEXT X
END
В пакете BGI область графического вывода устанавливается процедурой
setviewport:
ТС: setviewport(xmin,ymin,xmax,ymax,clip);
TP: SetViewPort(xmin,ymin,xmax,ymax,clip);
Экранные координаты точек (xmin,ymin) и (xmax,ymax) определяют, соответственно, положение левого верхнего и правого нижнего углов прямоугольной области вывода. Система координат в зоне вывода привязана своим началом к левому верхнему углу прямоугольника, и ее ось у, к сожалению, направлена вниз. Параметр clip, который может принимать только два значения (в Си — о или не о, в Паскале — true или false), управляет режи-
мом отсечения графических изображений, выходящих своими частями за пределы области вывода. При clip = о или clip = false отсечение не производится. 0бъявление окна графического вывода в ТС и ТР автоматически чистит окно и переводит текущую точку в начало координат. По умолчанию фон окна черный, а линии рисуются белым цветом.
Программа построения периода синусоиды на Паскаче, например, может выглядеть следующим образом:
Программа 8_03.pas
program sinl;
uses graph; var
gd,gm,xe,ye,k:integer;
x,y,dx:double;
begin
gd:=0;
InitGraph(gd,gm,'');
dx:=0.1;
SetViewPort(10,10,410,210, false) ;
LineTo(0,200); {0бводим границы установленного окна}
LineTo(400,200); LineTo(400,0);
LineTo(0,0);
{Проводим ось х и возвращаем СР в ее начало}
Line(400,100,0,100);
for k:=0 to 63 do
begin
x:=k*dx;
y:=sin(x);
xe:=round(x*400/6.28);
{Преобразуем программные координаты к экранным - хе, уе }
ye:=round(100-y*100);
LineTo(хе,уе) (Соединяем предыдущую точку с новой}
end;
readln;
closegraph;
end.
Для очистки окна вывода в системе QBasic используется оператор CLS, который заливает внутренность окна текущим цветом фона экрана (но не цветом фона, установленным при объявлении окна), зато сохраняет цветную окантовку окна. 0ператор CLS 2 чистит не только текущее окно, но и весь экран, переводя его в окно вывода по умолчанию.
Пакет BGI предлагает две процедуры без параметров — ciearviewport и cieardevice. Первая из них заливает цветом фона область текущего окна вывода и возвращает СР в начало координат, вторая — чистит экран и объявляет его текущей зоной вывода.
Для определения максимальных размеров текущего окна вывода по каждой координате можно использовать функции getmaxx и getmaxy, не требующие параметров. Значениями этих функций удобно пользоваться, когда необходимо привязать то или иное изображение к фиксированному месту в окне вывода. Например, построение окружности, расположенной в центре зоны вывода, выполняется следующим образом:
Circle(GetMaxX div 2,GetMaxY div 2, 50);
Отрезки прямых и прямоугольники
В системе QBasic для построения отрезков прямых и прямоугольников используется один и тот же оператор LINE по той причине, что обе эти фигуры могут быть определены двумя точкам — началом и концом отрезка или координатами противоположных вершин в прямоугольнике. Более того, на этот же оператор в Бейсике возложили функцию отображения залитого прямоугольника:
LINE (x1,y1l)-(х2,у2)[,[col][,mask] :' 0трезок прямой
LINE (x1,y1)-(x2,y2)[,[col] В [,mask] :' Контур прямоугольника
LINE (x1l,y1)-(x2,y2)[,[col] BF :' Залитый прямоугольник
В приведенных выше форматах оператора LINE координаты точек заданы как абсолютные оконные или экранные координаты. Если построению предшествовало задание границ изменения координат (WIND0W), то координаты точек задаются в терминах физических единиц задачи.
Если координатам точек предшествует добавка STEP, то в скобках задаются приращения относительно позиции текущей точки. Координаты первой точки могут отсутствовать (LINE - (х2,у2) ...), и тогда построение начнется с текущей точки.
Необязательный параметр col определяет цвет рисования, который может отличаться от цвета, установленного по оператору C0L0R.
Если отрезок прямой или контуры прямоугольника проводятся сплошной линией, то параметр mask не задается. Его функция заключается в том, чтобы в последовательности из очередных 16 пикселов линии определить, какие пикселы будут окрашены цветом линии, а какие будут окрашены цветом фона, т. е. останутся невидимыми. Значение mask представляет собой 16-битовую шкалу, в которой единичные разряды соответствуют видимым пикселам линии, а нулевые — невидимым. Например, для построения штриховой линии, у которой длина штриха равна длине пробела и состоит из 8 пикселов, достаточно указать mask = &HFF00.
В BGI-пакете функции построения отрезков прямых, границ прямоугольников и залитых прямоугольников четко разделены:
moveto (х, у); — перемещение СР в точку (х, у);
moverel (dx,dy) ; -- перемещение СР в точку (CP.x+dx,CP.y+dy) ;
lineto (х, у); — соединение СР отрезком с точкой (х, у);
linerel(dx,dy); — соединение СР отрезком с точкой (cp.x+dx,cp.y+dy);
line(x1,y1,x2,y2); — соединение отрезком точек (xi,yi) и (х2,у2);
rectangle (xl, yl,x2, у2); — построение контура прямоугольника;
bar (x1,y1,x2,y2); — построение залитого прямоугольника без обводки границ;
bar3d(x1,y1,x2,y2,d,b); — построение параллелепипеда.
Перемещение СР (moveto, moverel) не оставляет следов на экране, иными словами — проводится невидимый отрезок. Это необходимо для перехода к очередной несвязной фигуре. Может показаться странным, что в QBasic в явном виде нет аналогичных операций. На самом деле, это не совсем так. Перевод СР в точку с заданными координатами (х,у) или смещение ее по известным приращениям без построения видимого отрезка осуществляется с помощью оператора PRESET:
PRESET (x,y) ИЛИ PRESET STEP(dx,dy)
Однако, если до этого точка с координатами (х,у) была окрашена в цвет, отличный от цвета фона, то после оператора PRESET она будет перекрашена. Чтобы не затереть прежний цвет точки (х,у), можно предварительно опросить ее код цвета, а потом повторить его в одном из операторов PSET или
PRESET:
col=P0INT(x,y) : PSET (x,y),col
или
PSET (х, у), P0INT (х, у)
После построения видимых отрезков СР перемещается в конец отрезка. Построение прямоугольника также переводит СР в точку с координатами (х2,y2).
BGI-пакет располагает точно такой же возможностью по управлению видимостью пикселов линии с помощью 16-битовой маски. Но функция setiinestyle, определяющая стиль линии, несколько удобнее:
setlinestyle(style,mask,t);
Первый ее аргумент позволяет установить одну из нескольких заранее подготовленных системных масок, не перелагая на пользователя подбор соответствующего двоичного кода. Аргумент style — целое число из диапазона [0,4], которое удобно задавать в виде мнемонической константы:
ТС: S0LID_LINE TP: SolidLn 0 — сплошная линия
D0TTED__LINE DottedLn 1 — пунктирная линия
CENTER_LINE CenterLn 2 — штрих-пунктирная линия
DASHED_LINE DashedLn 3 — штриховая линия
USERBIT_LINE userBitLn 4 — маска пользователя
Если ни одна из четырех системных масок вас не устраивает, то при style = 4 в качестве маски используется значение параметра mask, подбираемое экспериментальным путем.
Последний параметр t может принимать только два значения 1 или 3, он задает толщину линии в пикселах. Две соответствующие" мнемонические константы дляСи — N0RM_WIDTH и THICK_WIDTH, для паскаля— NormWidth И ThickWidth.
В составе BGI-пакета присутствует еще одна функция, не имеющая аналога в QBasic. 0на позволяет обвести границы многоугольника, заданного координатами точек его вершин:
drawpoly(k,xy);
Здесь k — количество точек, координаты которых хранятся в целочисленном массиве ху — x1, y1, х2, у2, ... . Дополнительное удобство этой процедуры заключается в том, что, в случае необходимости, она сама осуществляет замыкание контура, соединяя последнюю точку массива с первой. В эту же точку перемещается и СР после построения многоугольника.
В системах программирования фирмы Borland некоторые трудности возникают при желании нарисовать черную линию на белом фоне. Дело в том, что черный цвет по умолчанию имеет нулевой программный код, и такой же нулевой код рассматривается в BGI-пакете как цвет фона. Поэтому попытка проделать нечто вроде:
setcolor(0);
setbkcolor(15);
line{0,0,100,100);
ни к какому результату не приводит. На белом фоне линия "рисуется" белым же цветом и поэтому не видна. В системе QBasic таких проблем просто не возникает. Попробуйте запустить программу:
Программа 8_05.bas
SCREEN 12
VIEW (0,0)-(100,100) ,15
LINE (0,0)-(100,100) ,0
END
В программах на Паскале или на Си приходится "обманывать" графическую систему следующим образом. Код нулевого цвета заносится в регистр палитры с номером, отличным от нуля. Затем содержимое этого регистра назначается в качестве цвета переднего плана.
Программа 8_05.pas
program black_white;
{ Построение черных линий на белом фоне }
uses graph;
var
gd,gm,x,у:integer; begin
gd:=0;
initgraph(gd,gm,'');
setcolor(0);
setbkcolor(15);
line(0,0,100,100);
{ ничего не видно, т. к. рисуем цветом фона}
readln;
setpalette(l,0);
setcolor(1);
setbkcolor(15);
line(0,0,100,100); {хорошая черная линия на белом фоне}
readln;
closegraph; end.
Стирание ранее нарисованных отрезков можно осуществить путем повторного построения такого же отрезка в режиме X0R_PUT. BGI-пакет позволяет на некоторое время установить один из двух режимов вывода — CОPY_PUT или X0R_PUT с помощью процедуры setwritemode:
setwritemode(C0PY_PUT);
setwritemode(X0R_PUT);
В первом режиме точки очередного отрезка или окружности будут затирать встречающиеся на их пути пикселы, а во втором — старый и новый цвета взаимодействующих точек будут поразрядно складываться по модулю 2.
Специфика построения залитых фигур рассматривается в одном из следующих разделов.
Работа с отдельными точками и растровыми изображениями
Вывод отдельных точек (пикселов) в программе имеет смысл, когда количество таких точек сравнительно невелико (от нескольких десятков до 2—3 тысяч). Если мы попытаемся заполнить весь экран путем вывода каждой точки, то эта процедура займет достаточно много времени. Для изменения кода цвета пиксела достаточно указать его координаты (оконные или экранные) и задать новый цвет.
В QBasic для этой цели используется один из двух операторов PSET или PRESET:
PSET х,у [,c] . PRESET x,y [,с]
Третий параметр является необязательным, но если он задан, то определяет новый код цвета. Если в этих операторах цвет не задавать, то оператор PSET использует цвет переднего плана, PRESET — цвет фона. Таким образом, один из них рисует точку, а второй — стирает ее на экране. Если очень надо, то программа может узнать цвет пиксела с заданными координатами. В QBasic для этой цели используется функция P0INT:
c=P0INT(x,y)
Аналогичные возможности присутствуют и в библиотеке BGI:
ТС: putpixel(х,у,с);
ТР: PutPixei(x,у,с);
c=getpixel (х, у) ;
c:=-GetPixel (x, у) ;
Гораздо больший интерес представляют процедуры группового копирования кодов цвета пикселов, заполняющих прямоугольную область экрана и образующих растровое изображение. Так как операция копирования между байтами видеопамяти и оперативной памяти выполняется достаточно быстро, то таким способом можно мгновенно обновить содержимое экрана. Правда, целиком скопировать или обновить содержимое экрана нельзя по совершенно тривиальной причине. В таком обмене должен участвовать массив, а в наших системах программирования размер массива не может превышать 64 Кбайт. Тогда как для запоминания кодов цвета всех пикселов экрана потребуется не менее чем 640x480x4/8 = 153 600 байт. Поэтому для полного обновления содержимого экрана необходимо не менее трех обращений к соответствующей процедуре.
В системе QBasic групповой обмен с видеопамятью осуществляют операторы GET И PUT:
GET (xl,yl)-(x2,y2), А% PUT (хЗ,уЗ), А%, ovr
Координаты двух точек, как и всегда, определяют положение левого верхнего (xl.yl) и правого нижнего (х2,у2) углов копируемой, прямоугольной области экрана. Коды цветности пикселов, попадающих в этот участок, переписываются в массив А%, который должен иметь достаточный объем. В самых первых элементах массива А% запоминаются габариты сохраняемой области экрана — число строк и число столбцов, вслед за которыми в каждый байт массива А% поступают коды цветности двух смежных пикселов. Если оказывается, что число пикселов в строке нечетно, то прихватывается код цветности еще одного лишнего пиксела, чтобы сохранить байтовую структуру данных.
Так как при копировании области экрана в оперативной памяти сохраняются не только цвета пикселов, но и размеры запоминаемой области, то в операторе PUT достаточно указать только одну точку на экране, начиная с которой будет размещаться восстанавливаемое изображение. Параметр ovr определяет способ взаимодействия кодов цветности налагаемых пикселов с их прежними цветовыми атрибутами. 0н может принимать одно из следующих мнемонических значений:
PSET — новый код цветности вытесняет предыдущий;
PRESET — инвертированное значение нового кода цветности заменяет предыдущий код;
AND — новый и старый коды цветности поразрядно логически умножаются; П 0R — новый и старый коды цветности поразрядно логически складываются;
X0R — над новым и старым кодами выполняется операция "исключающее ИЛИ" (другими словами — выполняется поразрядное сложение по модулю 2).
Абсолютно те же функции в BGI-пакете выполняют процедуры getimage и
putimage:
getimage(x1, y1,x2,y2,A);
putimage(x3,x4,A,ovr);
Единственное отличие заключается в мнемонике значений параметра ovr:
ТС: C0PY_PUT TP: CopyPut QBasic: PSET N0T_PUT NotPut PRESET
AND_PUT AndPut AND
0R_PUT 0rPut 0R
X0R_PUT XorPut X0R
В BGI-пакете предусмотрена еще одна функция, с помощью которой можно определить размер массива А в байтах до выполнения операции getimage:
size=imagesize(xl,yl,x2,y2);
Эта функция может оказаться полезной, если память для временного хранения растрового изображения динамически запрашивается и после использования возвращается.
Текстовые сообщения в графическом режиме
Возможностью вывода пояснительных подписей, сопровождающих графические изображения, располагает каждая из рассматриваемых систем. 0днако в системе QBasic отсутствуют средства по управлению шрифтами, масштабированию и точному (до пиксела) позиционированию текстов. В графическом режиме QBasic разрешает использование обычного оператора PRINT, который выводит тексты по правилам текстового режима.
Гораздо более богатыми возможностями располагает BGI-пакет, предоставляющий в распоряжение пользователя набор различных шрифтов и средства по управлению ими.
Собственно вывод текстовых сообщений осуществляется двумя процедурами (функциями):
outtext(сообщение); outtextxy(x,у,сообщение);
В первом случае место расположения текста на экране задается позицией СР, которая после вывода перемещается за последний символ сообщения. Во втором случае точка привязки текста явно задается координатами в окне вывода. После работы процедуры outtextxy позиция СР не изменяется.
Существует девять способов расположения точки привязки текста относительно габаритного прямоугольника, окаймляющего выводимое сообщение (рис. 8.1).
Рис. 8.1. Расположение возможных точек привязки текста
По каждой из. координат можно выбрать нижнюю, среднюю, верхнюю, левую или правую оси, пересечением которых и является точка привязки выводимого текста. По умолчанию в качестве точки привязки считается левый верхний угол габаритного прямоугольника. Выбор любой другой точки привязки осуществляется с помощью процедуры settextjustify:
SetTextJustify(horiz,vert) ;
Каждый из ее параметров может принимать одно из трех значений:
0 — снизу (воттом_ТЕХТ) или слева (LEFT_TEXT>;
1 — П0 центру (CENTER_TEXT) ;
2 — сверху (T0P_TEXT) или справа (RIGHTJTEXT) .
Учтите, что если какая-либо часть отображаемой буквы выходит за пределы экрана, то в растровом шрифте она вообще не рисуется, а в векторном рисуется либо ее часть, либо буква целиком, если режим отсечения заблокирован.
В состав базовой поставки Borland-систем программирования входит 11 шрифтов, среди которых шрифт по умолчанию (DEFAULT_F0NT) относится к растровым, а остальные — к векторным. Символы растрового шрифта формируются из точек, образованных единичными битами в мини-растре 8x8 пикселов. Для организации зазоров между символами и строками в этом мини-растре правый столбец и нижняя строка оставляются пустыми. По мере увеличения размеров букв составляющие их точки превращаются в заметные квадратики, что существенно ухудшает контуры символов. Векторные шрифты цредставлены набором отрезков, концы которых располагаются в узлах растровой сетки экрана. При увеличении размеров букв длины отрезков, образующих контур, увеличиваются, сохраняя пропорции и не ухудшая качество изображения.
Векторные шрифты обеспечивают довольно разнообразное представление алфавита. Самые примитивные из них (например, SMALL_F0NT) формируют контур в одну линию, и при увеличении таких букв их толщина не меняется. Более красивые (например, SANS_SERIF_F0NT) используют двухконтурные модели букв, при увеличении которых до определенного размера толщина контура заметно увеличивается. Но при большем увеличении между внутренним и внешним контурами начинают проглядывать фоновые пикселы. Наиболее качественные шрифты (например, TRIPLEX_F0NT или G0THIC_F0NT) "для создания разнотолщинных элементов контура используют до трех векторов на линию (рис. 8.2).
Рис. 8.2. Увеличенное изображение буквы в,разных шрифтах
Векторные шрифты, входящие в состав фирменной поставки ВС 3.1 и ТР 7.0, вместо контуров букв русского алфавита содержат экзотические символы европейских шрифтов (буквы с умляутами из немецкого, особые буквы чешского и польского алфавитов и т. п.). 0днако наши умельцы давным-давно модернизировали фирменные шрифты, пополнив их русскими буквами в соответствии с 866-й кодовой страницей. Найти русифицированные файлы с расширением chr особого труда не составляет.
Назначение шрифта, которым программа собирается выводить текстовые сообщения, производится с помощью процедуры settextstyle:
SetTextStyle(font,dir,size);
Параметр font определяет номер шрифта — число из диапазона от 0 до 10 — и может быть задан одной из мнемонических констант (табл. 8.3).
Таблица 8.3, Мнемонические константы шрифтов
Номер шрифта |
Константа ТС |
Константа ТР |
Пояснение |
||
0 |
DEFAULT F0NT |
DefaultFont |
Растровый, 8x8 |
||
1 |
TRIPLEX_F0NT |
TriplexFont |
Трехобводный с засечками |
||
2 |
SMALL F0NT |
SmallFont |
0днообводный, грубый |
||
3 |
SANS_SERIF_F0NT |
SansSerif Font |
Двухобводный, равнотолщинный |
||
4 |
G0THIC F0NT |
GothicFont |
Готический, трехобводный |
||
5 |
SCRIPT_F0NT |
Мнемоника отсутствует |
Рукописный, однообводный |
||
6 |
SIMPLEX F0NT |
Мнемоника отсутствует |
0днообводный |
||
7 |
TRIPLEX SCR F0NT |
Мнемоника отсутствует |
Курсив, трехобводный |
||
Номер шрифта |
Константа ТС |
Константа ТР |
Пояснение |
||
8 |
C0MPLEX_F0NT |
Мнемоника отсутствует |
Двухобводный, разнотолщинный |
||
9 |
EUR0PEAN F0NT |
Мнемоника отсутствует |
0днообводный |
||
10 |
B0LD_F0NT |
Мнемоника отсутствует |
Жирный, равнотолщинный |
||
Третий параметр size, значения которого должны принадлежать интервалу [1,10], выполняет роль масштабного коэффициента, с помощью которого можно незначительно увеличивать (size > 4) или уменьшать (size < 4) размеры букв.
Гораздо более гибкое управление размерами букв в векторных шрифтах обеспечивает процедура setusercharsize:
SetUserCharSize(mx,dx,my,dy);
Ее целочисленные аргументы выполняют роль множителей и делителей для координат (х,у) каждой точки контура букв:
X=x*mx/dx; Y=y*my/dy;
Выбирая те или иные значения этих параметров, можно осуществить раздельное масштабирование по каждой оси, сделав, например, узкие или широкие буквы. Коэффициент увеличения можно довести до такого значения, при котором единственная буква занимает весь экран с четко различимыми элементами ее контура.
С помощью функций textwidth и textheight, единственным параметром которых является текстовая строка, можно определить ширину и высоту габаритного прямоугольника в пикселах. Предварительное знание размеров подписи может оказаться полезным при выборе места ее расположения в последующей процедуре outtextxy.
В качестве примера ниже приводится программа, с помощью которой были получены изображения букв на рис. 8.2. Подбор некоторых параметров этой программы был сделан экспериментальным путем. Сначала была выбрана линия строки, на которой должны были располагаться буквы разных шрифтов (у = 470). Затем путем подбора коэффициентов увеличения были установлены приемлемые габариты буквы А для шрифта SMALL_F0NT. Получившиеся при этом "круглые" размеры габаритного прямоугольника (w = 180, h =270) были использованы для определения масштабного коэффициента шрифтовSANS_SERIF_F0NT И TRIPLEX_F0NT. После нескольки пристрелочных проб, дававших "перелеты" и "недолеты", были найдены подходящие коэффициенты для каждого из шрифтов. Подбирать их пришлось по каждой оси свой и с точностью до второго знака после запятой. 0братите внимание на то, что одним и тем же коэффициентом не всегда удается выровнять размеры букв в разных шрифтах. 0бъясняется это тем, что разработка контуров векторных шрифтов выполнялась на координатных сетках с разными шагами. Более того, трансляция этой программы в системах ТС 2.0 и ВС 3.1 дает разные результаты т. к. в каждой из систем используются свои наборы шрифтов, которые слегка отличаются по начертаниям букв.
Самые большие неприятности доставил шрифт DEFAULT_F0NT. Для него нельзя использовать функцию setusercharsize после выбора шрифта (settextstyie). Для того чтобы растровая буква оказалась на линии строки, ее пришлось опустить на межстрочный промежуток, равный 1 пикселу (с учетом масштабного множителя — на 10 пикселов).
Программа 8_13.с
/* Буква "А" в разных шрифтах */
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
int x,y=470;
void a_in_box(int dy) {
int w,h; /* 0пределение размеров габаритного прямоугольника */
w=textwidth("А");
h=textheight("А");
printf("w=%d h=%d ",w,h);
/* 0бводка границ прямоугольника со смещением на 2 пиксела */
rectangle(x-2,y-h,x+w-2,y+2); /* Вывод буквы А */
outtextxy(x,y+dy,"A"); }
main()
{
int gd=0,gm;
initgraph(&gd,&gm,""); /* Установки для рисования черным по белому */
setpalette(l,0);
setcolor(1);
setbkcolor(15);
settextjustify(0,0);
settextstyle(2,0,l); /* SmallFont */
setusercharsize(30,1,267,1);
x=4;
a_in_box(0);
settextstyle(3,0,1); /* SansSerifFont */
setusercharsize(86,10,75,10);
x=186;
a_in_box(0);
settextstyle (1,0,1); /* TriplexFont */
setusercharsize(95,10,78,10);
x=368;
a_iri_box(0);
settextstyle(0,0,10); /* DefaultFont, общее увеличение=10 */
x=550;
a_in_box(10);
getch ();
closegraph(); }
Управление цветом
Процедуры воспроизведения элементарных геометрических объектов -точек, отрезков прямых, дуг окружностей или эллипсов, прямоугольников, — используют тот или иной цвет при окрашивании соответствующих пикселов. В системе QBasic цвет объекта может быть задан с помощью необязательного параметра в соответствующем графическом операторе. Если он явно не указан, то объект рисуется цветом переднего плана, установленным ранее в операторе C0L0R:
C0L0R cf,cb
В режимах 12 и 13 оператор C0L0R допускает единственный параметр cf, определяющий программный цвет рисования. Его значением может быть любое число из интервала [0,15], по которому системные программы извлекут содержимое регистра палитры с номером cf и преобразуют его в физический цвет отображаемых на экране пикселов. Второй параметр сь допустим только в режиме 9 (EGA), он определяет цвет фона. В режимах 12 и 13 цвет фона устанавливается в момент объявления области вывода. По умолчанию им является черный цвет.
Приводимая ниже программа демонстрирует цветовую палитру режима 12, воспроизводя с задержкой в 1 с прямоугольники размером 100x75 пикселов, заливаемые последовательно цветами от 0 до 15.
Программа 8_04.bas
REM Демонстрация цветовой палитры
SCREEN 12
CLS
F0R Y=10 T0 310 STEP 100
F0R X=10 T0 460 STEP 150
LINE (X-l,Y-l)-(X+101,Y+76),15,В
LINE (X,Y)-(X+100,Y+75),C0L,BF
C0L=C0L+1
SLEEP 1 : ' Задержка на 1 сек NEXT X NEXT Y END
В BGI-пакете цвет переднего плана и цвет фона устанавливаются процедурами (функциями) setcoior(cf) и setbkcolor (cb). В процедурах отображения геометрических объектов в системах Turbo С и Turbo Pascal цвет рисования явно не задается. Используется только тот цвет, который был установлен с помощью setcoior. Для тех, кому английский язык не чужд, наряду с числовыми значениями кода цветности BGI-пакет предлагает набор мнемонических констант (табл. 8.1).
Таблица 8.1. Набор мнемонических констант BGI-пакета
Код | Цвет | Turbo С | Turbo Pascal | ||||||
0 | Черный | BLACK | Black | ||||||
1 | Синий | BLUE | Blue | ||||||
2 | Зеленый | GREEN | Green | ||||||
3 | Бирюзовый | CYAN | Cyan | ||||||
4 | Красный | RED | Red | ||||||
5 | Малиновый | MAGENTA | Magenta | ||||||
6 | Коричневый | BR0WN | Brown | ||||||
7 | Светло-серый | LIGHTGRAY | LightGray | ||||||
8 | Темно-серый | DARKGRAY | DarkGray | ||||||
9 | Светло-синий | LIGHTBLUE | LightBlue | ||||||
10 | Светло-зеленый | LIGHTGREEN | LightGreen | ||||||
11 | Светло-бирюзовый | LIGHTCYAN | LightCyan | ||||||
Код |
Цвет |
Turbo С |
Turbo Pascal |
||
12 |
Светло-красный |
LIGHTRED |
LightRed |
||
13 |
Светло-малиновый |
LIGHTMAGENTA |
LightMagenta |
||
14 |
Желтый |
YELL0W |
Yellow |
||
15 |
Белый |
WHITE |
White |
||
Программа 8_04.с
/* Демонстрация цветовой палитры */
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
main () {
int X,y,col=0;
int gd=0,gm;
initgraph(&gd,&gm, "");
for(y=10; y<=310; y+=100)
for(x=10; x<=460; x+=150) {
setfillstyle(1,col); /* Сплошная заливка цветом col */
bar3d(x-l,y-l,x+101,y+76,0,l);
col++;
sleep(l); /* Задержка на 1 сек*/ }
getch () ; closegraph(); }
Кроме управления двумя основными цветами переднего и заднего планов, имеется возможность изменить содержимое одного или всех регистров палитры. В системе QBasic для этой цели используются операторы PALETTE и PALETTE USING:
PALETTE reg,col PALETTE USING A%
Параметр reg задает номер модифицируемого регистра палитры, а значение col из диапазона [0,63] определяет новое содержимое указанного регистра. В случае групповой модификации всех регистров палитры их новые значения определяются байтами массива А%. Если в каком-то из этих байтов находится — 1 (0xFF), то значение соответствующего регистра палитры не изменяется.
Функции setpaiette и setallpaiette в пакете BGI ничем не отличаются от описанных выше операторов:
SetPalette(reg,col);
SetAllPalette(A);
Написать программу, которая рисует на
Задание 8.14. Построение шахматной доски
Написать программу, которая рисует на экране шахматную доску. Идея программы подсказана К. Э. Садыровым, однако мы предпочли другую реализацию с целью демонстрации идентичных программ на разных языках.
Совет 1 (общий)
Структура программы мало чем отличается от программ, демонстрировавших палитру (см. выше). Единственная тонкость заключается в организации правильного чередования цвета заливки смежных квадратов как в пределах строки, так и при переходе на следующую строку. Для этой цели использован прием "мерцающего бита". При каждом повторении внутреннего цикла величина col принимает чередующиеся значения 7 или 8 (в "мерцающем бите" аналогичная операция выглядела бы так: bit=l- bit или bit:=not bit). Если такое же действие не было бы предусмотрено и во внешнем цикле, то две смежные линейки начинались бы с квадратов одинакового цвета.
Совет 2 (QBasic)
Для подчеркивания границ каждого поля доски приходится сначала заливать очередную клетку, а потом обводить ее границу.
Совет 3 (Си, Паскаль)
Построение залитого поля с одновременной обводкой его границы можно выполнить процедурой bar3d с нулевой глубиной трехмерного столбика.
Программа 8_14.bas
RЕМ Построение шахматной доски DEFINT A-Z SCREEN 12
х0=10: у0=10: col=8: w=50
F0R y=y0 T0 y0+7*w STEP w
col=15-col F0R x=x0 T0 x0+7*w STEP w
LINE (x,y)-(x+w,y+w),col,BF :' Заливка клетки
LINE (x,y)-(x+w,y+w),15,В :' 0бводка границ col=15-col:
' Цвет для смежной клетки NEXT x NEXT у
Программа 8_14.с
/* Построение шахматной доски */
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
main() {
int x,y,x0=10,y0=10,col=8,w=50;
int gd=0,gm;
initgraph(Sgd,&gm,"");
for(y=y0; y<y0+8*w; y+=w)
{
col-15-col;
for(x=x0; x<x0+8*w; xt=w) {
setfillstyle(1,col);
bar3d(x,y,x+w,y+w,0,1); /* 0бводка и заливка клетки */
col=15-col; /* Цвет для смежной клетки */ } }
getch();
closegraph(); }
Программа 8_14.pas
program shach;
{ Построение шахматной доски } uses Graph;
const
x0=10;
y0=10;
w=50;
col:integer=8;
var
i,j,gd, gm,x,у:integer;
begin gd:=0;
initgraph(gd,gm,' ') ;
for i:=0 to 7 do begin
col:=15-col;
y:=y0+i*w;
for j:=0 to 7 do begin
x:=x0+j *w;
setfillstyle(1,col);
bar3d(x,y,x+w,y+w,0,true);
{ 0бводка и заливка клетки }
col:=15-col;
{ Цвет для смежной клетки }
end;
end;
readln;
closegraph;
end.
Задание 8.15. 0тображение семисегментныхцифр
В электронных часах с цифровым индикатором цифры формируются как комбинации из семи сегментов (рис 8.3). Составить функцию (процедуру) cifra, которая по заданным координатам (х,у) и числовому значению цифры k (о <= k <= 9) формирует на экране ее графическое изображение.
Совет 1 (общий)
Выберем в качестве точки привязки (х,у) вершину семисегментной комбинации, расположенную в левом верхнем углу. 0чевидно, что для плотного примыкания смежных горизонтальных и вертикальных сегментов их срезы должны быть направлены под углами 45 и 135 градусов.
Рис. 8.3. Горизонтальный и вертикальный сегменты цифр
Если пронумеровать точки контура одного сегмента от 0 до 6 по часовой стрелке от самой левой (для горизонтальных) или самой верхней (для вертикальных) и считать, что начальная точка имеет координаты (х0.у0), то координаты остальных точек можно рассчитать, используя относительные смещения, приведенные в табл. 8.4. Через а и ь здесь обозначены смещения по координатным осям для наклонных и горизонтальных (вертикальных) ребер контура. Задавая различные числовые значения этих параметров, можно строить сегменты нужного размера. Удобно выбирать b = 4*а, хотя не возбраняются и любые другие соотношения. В последующих расчетах нам понадобится и константа с = = 2*а. + b, представляющая максимальную длину (высоту) сегмента.
Таблица 8.4. Расчет координат точек с использованием
относительного смещения
Номер точки |
Смещения по координатам относительно предыдущей точки |
|||||
Для горизонтального сегмента |
Для вертикального сегмента |
|||||
По х |
По у |
Пох |
По у |
|||
0 | 0 | 0 | 0 | |||
а | -а | а | а | |||
0 1 2 |
b |
0 |
0 |
b |
||
Номер точки |
Смещения по координатам относительно предыдущей точки |
|||||
Для горизонтального сегмента |
Для вертикального сегмента |
|||||
По х |
По у |
Пох |
По у |
|||
3 |
а |
а |
-а |
а |
||
4 |
-а |
а |
-а |
-а |
||
5 |
-b |
0 |
0 |
-b |
||
Из табл. 8.4 видно, что при реализации программы достаточно иметь всего два массива смещений:
d1 = (0, а,b, а,-а,-а) и d2 = (0,-а, 0, а, а, 0)
Для вертикального сегмента они меняются местами и у одного из них меняются знаки.
Установим соотношения между точкой привязки цифры и координатами начальных точек каждого из семи сегментов. Пронумеруем сверху вниз числами 1, 2 и 3 горизонтальные сегменты, а числами 1, 2, 3 и 4 — вертикальные сегменты слева направо и сверху вниз. Несложно убедиться в том, что координаты их нулевых вершин вычисляются следующим образом.
Для горизонтальных сегментов:
xl = х х2 = х х3= х
yl = у у2 = у + с уЗ = у + 2*с
Для вертикальных сегментов:
xl=x х2=х+с х3=х х4 = х + с
yl = у у2 = у у3 = у + с у4=у + с
Совет 2 (Си)
Для того-чтобы установить соответствие между числовым значением отображаемой цифры и составляющим ее набором сегментов, предлагается использовать семиразрядные двоичные числа, единичные разряды в которых устанавливают присутствие того или иного сегмента. Например, контур цифры 0 образуется первым и третьим горизонтальными сегментами и всеми четырьмя вертикальными. Поэтому цифре 0 можно поставить в соответствие код 1011111. Цифра 9 описывается кодом 1111101 и т. д. В приведенном ниже тексте программы на Си эти коды представлены не самым рациональным способом — в виде двумерного массива. Более оптимальный вариант предложен в следующем задании:
Программа 8_15.с
/* Построение 7-сегментных цифр */
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
void cifra(int x,int y,int n) ;
void main() {
int gd=0,gm, k;
initgraph(&gd,&gm,"");
setcolor(4);
for(k=0;k<10;k++)
cifrat(k+1)*50,30,k) ; getch(}; return;
}
/*-----------------------------* /
void cifra(int x,int y,int n)
{
const a=4,b=20,c=a+a+b; //габариты сегмента
const dl[]={a,b,a,-a,-b}; //приращения по координатам
const d2 [] = {-a,_0,a,a, 0}; //приращения по координатам
const dx[]={0,0,0,0,с,0,с}; //смещения начальных1 вершин по х
const dy [] = {0, с, 2*с, 0, 0, с, с}; //смещения начальных вершин по у
int j, k; struct {int x;int у;} ху[6]; //координаты точек контура
char q[10][7]={ {1,0,1,1,1,1,1}, //перемычки цифры 0
{0,0,0,0,1,0,1},{1,1,1,0,1,1,0}, //перемычки цифр 1,2
{1,1,1,0,1,0,1},{0,1,0,1,1,0,1}, //перемычки цифр 3,4
{1,1,1,1,0,0,1},{!,1,1,1,0,1,1}, //перемычки цифр 5,6
{1,0,0,0,1,0,1},{1,1,1,1,1,1,1}, //перемычки цифр 7,8
{1,1,1,1,1,0,1}}; //перемычки цифры 9
for(j=0; j<7; j++) {
if(q[n][j]==0) continue;
xy[0].x=x+dx[j];
xy[0].y=y+dy[j];
for(k=l; k<6;k++)
{// цикл вычисления координат точек контура if(j<3)
{//пересчет для горизонтальных перемычек
xy[k].x=xy[k-l].x+dl[k-l];
xy[k].y=xy[k-l].y+d2[k-l]; }
else
{//пересчет для вертикальных перемычек
xy[k].x=xy[k-l].x-d2[k-l];
xy[k].y=xy[k-l].y+dl[k-l]; } }
fillpoly(6,(int *)xy); //заливка перемычки
} }
Задание 8.16. Цифровые часы
Используя составленную в предыдущем примере программу формирования семисегментных цифр, написать программу, отображающую на экране текущее показание компьютерных часов.
Попытка использовать предыдущую программу cifra сразу же выявляет ее недостаток. 0на хорошо "рисует" цифры на чистом экране, но в часах приходится строить новую цифру поверх уже нарисованной. Поэтому в самом начале процедуры придется стереть предыдущее изображение, например с помощью процедуры bar.
Совет 1 (Паскаль)
Показания системных часов в Паскале можно опросить с помощью процедуры gettime. Для того чтобы исключить излишнее мигание на экране, целесообразно перерисовывать цифры часов, минут или секунд только при их изменении. С этой целью в программе предусмотрен двойной набор переменных для хранения предыдущего (hl.ml.sl) и текущего (h2,m2,s2) показаний часов. В начале работы программы в первый набор заносятся невообразимые значения, поэтому первое же обращение к gettime вызовет перерисовку всех трех цифр и запоминание нормальных значений часов, минут и секунд. В последующих циклах будут перерисовываться только те цифры, значения которых изменились.
Совет 2 (Паскаль)
Позиции цифр на экране и двоеточия, разделяющего часы, минуты и секунды, подбирались экспериментальным путем. В программе не очень изящно реализовано отображение двоеточия. Было бы лучше, если бы оно мигало с частотой раз в секунду. Но усовершенствования такого рода мы вполне доверяем читателям.
Совет 3 (Паскаль)
В отличие от приводившейся выше функции cifra здесь использован одномерный массив двоичных чисел, что уменьшает объем используемых данных и несколько ускоряет работу программы (операции с элементами одномерных массивов выполняются быстрее). Прощупывание двоичных битов ведется в цикле путем сдвига соответствующей константы на нужное число разрядов влево и логического умножения на код соответствующих перемычек.
Совет 4 (Паскаль)
Программа оформлена в виде бесконечного цикла, работа которого прерывается нажатием любой клавиши.
Программа 8_16.pas
program time;
uses Crt, Dos, Graph;
var
gd,gm,k:integer;
hi,ml,si,h2,m2,s2,hs2:word;
procedure cifra(x,y,n:integer); type
a4=array [0..4] of integer;
a6=array [1..7] of byte; const
a=4; b=20; c=a+a+b;
dl:a4=(a,b,a,-a,-b);
d2:a4=(-a,0,a,a,0);
dx:a6=(0,0,0,0,c,0,c);
dy:a6=(0,c,2*c,0,0,c,c);
q:array[0..9]of byte=
($5F,$5,$76,$75,$2D,$79,$7B,$45,$7F,$7D); var
xy:array [0..5] of PointType;
j,k,d:byte; begin
setfillstyle(0,0);
bar(x-a,y-a,x+(c+a+a),y+2*(c+2*a));
d:=q[n];
for j:=1 to 7 do begin
if ((d) and ($80 shr j))=0 then continue;
ху[0]. х:=x+dx[j];
ху[0].y:=y+dy[j];
for k:=l to 5 do if j<4 then begin
xy[k].x:=xy[k-l].x+dl[k-l];
xytk].y:=xy[k-l].y+d2[k-l];
end
else
begin
xy[k].x:=xy[k-l].x-d2[k-l];
xy[k].y:=xy[k-l].y+dl[k-l];
end;
setfillstyle(l,14);
fillpoly(6,xy);
end;
end;
begin gd:=0;
initgraph(gd,gm,'');
settextstyle(0,0,4);
setcolor(14);
outtextxy(136,44,':');
{ Разделитель часов и минут }
outtextxy(256,44,':');
{ Разделитель минут и секунд }
setcolor(4);
hi:=100; { Начальные установки }
ml:=100;
sl:=100;
repeat
gettime(h2,m2,s2,hs2); ( 0прос текущего времени }
if hloh2 then begin { Если изменились часы }
k:=h2 div 10;
cifra(50,30,k);
{ Старшая цифра часов }
k:=h2 mod 10;
cifra(100,30,k); { Младшая цифра часов }
hi:=h2; end;
if ml<>m2 then begin { Если изменились минуты }
k:=m2 div 10; cifra(170,30,k);
k:=m2 mod 10; cifra(220,30,k);
ml:=m2;
end;
if sl<>s2 then begin { Если изменились секунды }
k:=s2 div 10;
cifra(290,30,k);
k:=s2 mod 10; cifra(340,30,k);
sl:=s2; end;
unit1 KeyPressed;
closegraph;
end.
Задание 8.17. Графические спрайты
Спрайты (от англ. Sprite — "дух, привидение") представляют собой небольшие графические изображения, перемещаемые по экрану для имитации движущихся фигур или предметов. Без спрайтов не обходится ни одна динамическая игра. Для некоторых изображений достаточно единственного спрайта, который просто перемещается по заданной траектории. Таким спрайтом, например, может быть изображение летящего самолета. В других случаях приходится манипулировать цепочкой спрайтов, на которых зафиксированы отдельные кадры, соответствующие разным фазам движения.
0сновная идея простейших программ анимации заключается в организации быстрой смены кадров на экране, при которой отображаются последовательные фазы изменения состояния фигуры и производится ее перемещение. При этом крайне нежелательно, чтобы спрайты "затирали" находящийся под ними интерьер. Последнее достигается за счет разумного подбора цветовых оттенков как спрайта, так и фона, и вывода спрайтов в режиме X0R.
Продемонстрируем технику перемещения единственного спрайта —"летающей тарелки" на фоне "звездного неба". Увеличенное изображение "тарелки" было нарисовано на миллиметровой бумаге в прямоугольной области размером 43x24 мм и представляло собой эллипс с полуосями 20 и 8 мм, внутри которого проходил эллиптический поясок (дуга эллипса со смещенным центром). Из "корпуса" тарелки под небольшими углами расходились отрезки прямых — стойки локаторов, на концах которых размещались две круговые "антенны" - кружочки небольшого радиуса (2 мм). Контуры тарелки рисуются белым цветом, а внутренность корпуса заливается красным цветом.
Совет 1 (общий)
Для создания образа спрайта в оперативной памяти поступим следующим образом. Сначала воспроизведем спрайт в любом месте экрана и запомним содержимое соответствующего участка видеопамяти в массиве соответствующего размера (в QBasice — оператор GET, на Си или Паскале — процедура getimage). Затем повторно выведем спрайт на то же место экрана в режиме X0R для стирания статичного изображения.
Совет 2 (общий)
Для имитации звездного неба построим 1000 случайных точек на экране, окрашенных в случайные цвета.
Совет 3 (общий)
0рганизуем бесконечный цикл, в котором изображение тарелки на небольшое время (порядка 0,6 с) появляется в некоторой точке с координатами (х,у), а затем стирается. После этого точка (х,у) смещается на случайные перемещения (dx.dy) и цикл повторяется до нажатия какой-либо клавиши пользователем. Для того чтобы перемещения из текущей точки в следующую выглядели случайными, можно менять знаки приращений dx и dy, когда они оказываются четными или нечетными. Кроме того, необходимо организовать проверку очередной точки на принадлежность экрану и запретить ее выход из допустимого интервала.
Совет 4 (QBasic)
В связи с тем, что задержка по оператору SLEEP может быть организована только с точностью до секунды, предлагается воспользоваться оператором S0UND f, dt. 0н организует выдачу звукового сигнала с частотой f Гц, длящегося dt тиков (в 1 с содержится 18,2 тика). А чтобы звук не доставлял неприятностей, можно выбрать частоту, не воспринимаемую человеческим ухом (например, f = 32767 Гц).
Программа 8_17.bas
REM Летающая тарелка
DEFINT A-Z
DIM TARELKA(300)
Х=320: Y=240
SCREEN 12
CIRCLE (100,50),20,15,,,.4
PAINT (100,50),4,15
CIRCLE (100, 46); 20,15, 3.3,0, .3
LINE (107,44)-(110,38) ,15
CIRCLE (110,38),2,15
LINE (93,44)-(90,38),15
CIRCLE (90,38),2,15
' Запомнили образ
GET (79,36)-(121,59),TARELKA
' Стерли
PUT (79,36),TARELKA,X0R
' Построение звездного неба
F0R I=0 T0 1000
PSET (INT(RND*639)+1,INT(RND*473)+1),INT(RND*15) +1
NEXT I
D0 WHILE INKEY$=""
PUT (X,Y),TАРЕЛКА, X0R :
S0UND 32767,4.92 ;' Задержка на 0.6 сек
PUT (A,Y),TAKELKA,X0R ;' Стирание тарелки
DX=INT(RND*60)+1: IF DX M0D 2=1 THEN DX=-DX
X=X+DX :' Смещение по горизонтали
IF X>590 THEN X=590 :' Контроль за правой границей экрана
IF X<0 THEN X=0 :' Контроль за левой границей экрана
DY=INT(RND*40)+1: IF DY M0D 2=1 THEN DY=-DY
Y=Y+DY :' Смещение по вертикали
IF Y>450 THEN Y=450 :' Контроль за нижней границей экрана
IF Y<0 THEN Y=0 :' Контроль за верхней границей экрана
L00P
END
Программа 8_17.с
/* Летающая тарелка */ #include <graphics.h> #include <stdlib.h>
main() {
int x=320, y=240, i, dx, dy, gd=0, gm;
char Tarelka[600];
initgraph(&gd,&gm,"");
randomize() ; /* Построение летающей тарелки */
setfillstyle( S0LID_FILL, 4);
fillellipse(100, 50, 20, 8) ;
ellipse(100, 46, 190, 357, 20, 6) ;
line(107, 44, 110, 38);
circle(110, '38, 2} ;
line(93, 44, 90, 38);
circle(90, 38, 2); /* Запомнили изображение и стерли его */
getimage(79, 36, 121, 59, Tarelka);
putimage(79, 36, Tarelka, X0R_PUT); /* Построение звездного неба */
for ( i=0 ; i<1000; ++i )
putpixel(random(639), random(479), random(15)+1) ; while ( !kbhit() ) { /* Бесконечный цикл до нажатия клавиши */
putimage(x, у, Tarelka, X0R_PUT);
/* вывод тарелки */
delay(6000);
/* задержка */
putimage(x, у, Tarelka, X0R_PUT);
/* стирание тарелки */
/* Перемещение тарелки */
dx = random(60);
if (dx % 2 == 0 ) dx = - dx;
x = x + dx;
if (x > 590) x = 590;
else if (x < 0) x = 0; dy = random(40);
if (dy % 2 == 0 ) dy = - dy; у = у + dy;
if (y > 450) у = 450;
else if (y < 0) у = 0; }
getch(); }
Программа 8_17.pas
{ Программа "Летающая тарелка" } program nlo; uses Crt,Graph; var
x, y, i, dx, dy, gd, gm: integer;
Tarelka:array [1..600] of byte; begin
x:=320;
y:=240;
gd:=0;
initgraph(gd,gm,'');
randomize; { Построение летающей тарелки }
setfillstyletSolidFill, 4);
fillellipse(100, 50, 20, 8) ;
ellipse(100, 46, 190, 357, 20, 6);
line(107, 44, 110, 38);
circle (110, 38, 2); line(93, 44, 90, 38); circle(90, 38, 2);
{ Запомнили изображение и стерли его }
getimage(79, 36, 121, 59, Tarelka);
putimage(79, 36, Tarelka, X0Rput);
{ Построение звездного неба }
for i:=0 to 1000 do
putpixel(random(639), random(479), random(15}+1);
repeat { Бесконечный цикл до нажатия клавиши }
putimage(х,у,Tarelka,X0Rput);
{ вывод тарелки }
delay(6000);
{ задержка }
putimage(х,у,Tarelka,X0Rput);
{ стирание тарелки }
( Перемещение тарелки }
dx:=random(60);
if odd(dx) then dx:=- dx;
x:=x+dx;
if x>590 then x:=590;
if x<0 then x:=0;
dy:=random(40);
if odd(dy) then dy:=- dy;
y:=y+dy;
if y>450 then y:=450;
if y<0 then y:=0;
until KeyPressed;
closegraph;
end.
Задание 8_18. Биоритмы
Некоторые исследователи утверждают, что каждому человеку присущи три периодических процесса, сопровождающие его жизнь с момента рождения. Первый из них с периодом 23 дня соответствует физическому состоянию, второй с периодом 28 дней — интеллектуальному, третий с периодом 33 дня — эмоциональному. По каждому процессу можно построить график, отложив по горизонтальной оси время Т в днях с момента рождения, а по вертикальной оси — амплитуду синусоиды со своим периодом:
PH=SIN(2*PI*T/23)
IN=SIN(2*PI*T/28)
EM=SIN(2*PI*T/33)
Совместив все графики на общем рисунке, можно определить моменты общего подъема или спада потенциала человека. 0собого единства в трактовке "плохих" интервалов жизни человека нет. 0дни считают, что к "черным" дням относятся моменты, когда все три кривые одновременно или в достаточно узком интервале пересекают ось времени, другие склонны рассматривать в качестве дней депрессии моменты, когда все кривые имеют общую или близкую точку минимума.
Составим программу построения графика биоритмов на текущий месяц по заданной дате рождения. Для упрощения анализа високосных лет будем считать, что эта дата относится к XX столетию. Тогда можно ограничиться проверкой остатка от деления последующих лет на 4.
Совет 1 (общий)
С целью структурирования программы, кроме ее главной части целесообразно выделить в отдельные функции следующие процедуры:
offset — определение количества дней, прошедших от 1.01.1900 г. до заданной даты (dd — день, mm — месяц, уу — год);
axis — построение осей для графика биоритмов с нанесением рисок по горизонтальной оси и числовых меток, упрощающих определение дня текущего месяца;
grafik — построение графика синусоиды с заданными периодом т и начальным смещением df 1, окрашенной в указанный цвет.
Совет 2 (общий)
Алгоритм процедуры offset может быть самым примитивным. Например, можно прибавлять к сумме по 365 дней за каждый полностью прошедший год и компенсировать добавочным днем каждый високосный. В начале работы в сумму заносим 365 — число дней в невисокосном 1900 году. В текущем году количество дней подсчитываем с помощью массива days, задающего количество дней в каждом месяце. Нужно только откорректировать число дней февраля (days [1] = 29), если текущий год является високосным.
Совет 3 (общий)
Для определения текущей даты можно воспользоваться одной из функций, предлагаемых соответствующей системой программирования — DATE$ (QBasic), getdate (Си), GetDate (Паскаль).
Совет 4 (QBasic)
Самым неудобным моментом в программе является подбор положения цифровых меток у графических штрихов на оси х. К сожалению, кроме метода проб и ошибок, мы не можем предложить ничего разумного. Дело в том, что оператор L0CATE позиционирует курсор в терминах строка-столбец, а графика оперирует с пикселами. Поэтому приходится слегка смещать либо положение рисунка, либо положение подписи.
Совет 5 (Си)
Для абсолютной идентичности результатов программ на Си и Паскале пришлось написать очень нехитрую функцию round, округляющую вещественный аргумент до ближайшего целого числа. К сожалению, воспользоваться напрямую одной из библиотечных функций floor или ceil нельзя.
Совет 6 (Паскаль)
0братите внимание на то, что в графическом режиме мы не пользуемся процедурами и функциями модуля Crt, ибо они конфликтуют с процедурами модуля Graph. В отличие от этого в Си, например, функция gotoxy прекрасно работает и в графическом режиме.
Программа 8_18.bas
DECLARE SUB AXIS()
DECLARE SUB GRAFIK(t!,dfi!,col!)
DECLARE FUNCTI0N 0FFSET!(d!,m!,у!)
REM Построение биоритмов на текущий месяц
DATA 30,28,31,30,31,30,31,31,30,31,30,31
DIM SHARED days(11),a
F0R k=0 T0 11: READ days(k): NEXT k
wwod:
PRINT "Биоритмы на текущий месяц"
PRINT "Введите день, месяц (числом) и год своего рождения"
INPUT d,m,у
d$=DATE$: ml=VAL(LEFT$(d$,2)) : dl=VAL(MID$(d$, 4, 2))
yl=VAL(RIGHT$(d$, 4))
IF (m<l)0R(m>12)0R(d<l)0R(d>days(m))0R(y<1900)0R(y>yl) THEN
PRINT "Вы ошиблись. Повторите ввод"
SLEEP 1: G0T0 wwod END IF
IF (y1 M0D 4)=0 THEN days(2)=29:' поправка на високосный год
a=days(ml):' число дней в текущем месяце
' Интервал от дня рождения до начала текущего месяца
dd=0FFSET(l,ml,yl)-0FFSET(d,m,у) SCREEN 12
PRINT "красный - физическое состояние"
PRINT "синий - эмоциональное состояние"
PRINT "зеленый - интеллектуальное состояние"
' Построение и разметка координатных осей
axis
GRAFIK 23,dd
M0D 23,4
GRAFIK 28,dd
M0D 28,2
GRAFIK 33,dd
M0D 33,1
SLEEP END
SUB AXIS
LINE (0,140)-(0,340)
LINE (0,240)-(a*20,240)
F0R j=l T0 a stroke=5
IF (j M0D 5)=0 THEN stroke=10
LINE (j*20,240+stroke)-(j*20,240-stroke)
IF stroke=10 THEN L0CATE 17,(j*20-4)\8: PRINT j
NEXT j
END SUB
SUB GRAFIK(t,dfi, col)
C0NST twopi=6.2831853*
x=0: y=240-100*SIN(twopi*dfi/t): C0L0R col PSET (x,y)
F0R k=l T0 a xl=20*k
yl=240-100*SIN(twopi*(k+dfi)/t)
LINE -(xl,yl)
NEXT k
END SUB
FUNCTI0N 0FFSET (d,m,y)
REM Вычисляет количество дней от 1.01.1900 до
d.m.y dd=365: 'Количество дней в 1900 г REM Цикл учета полных лет
F0R k%=1901 T0 у-1 dd=dd+365
REM Поправка на високосный год
IF (k% M0D 4)=0 THEN dd=dd+l NEXT k%
REM Учет дней в году у до месяца m
F0R k%=l T0 m-1: dd=dd+days(k%): NEXT k%
0FFSET=dd+d:' Добавление дней, прошедших в месяце m
END FUNCTI0N
Программа 8_18.с
#include <dos.h>
#include <math.h>
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#define twopi 2*M_PI
void axis(void);
void grafik( int t, int dfi, int color);
long offset(int d, int m,int y);
int round(double x);
char days[]={30,28,31,30,31,30,31,31,30,31,30,31}; int a ;
main () {
int gd=0,gm, k,d,m, y,ml,yl;
struct date q;
long dd; wwod:
clrscr () ;
gotoxy(1,1);
printf("Биоритмы на текущий месяц\n");
printf("Введите день, месяц (числом) и год своего рождения\n");
scanf("%d %d %d",&d,&m,&y);
getdate(&q);
ml=q.da_mon;
yl=q.da_year;
if(m<l || m>12 || d<l || d>days[m-l] || y<1900 || y>yl)
{
printf("\nBы ошиблись. Нажмите Enter и повторите ввод");
getch();
goto wwod;
}
if(yl % 4 == 0) days[l]=29;
a=days[ml-1];
dd=offset(l,ml,yl)-offset(d,m,y);
initgraph(&gd, &gm, "");
gotoxy(1,1);
printf ("красный - физическое состояние");
gotoxy(1,2);
printf ("синий - эмоциональное состояние");
gotoxy(l,3);
printf ("зеленый - интеллектуальное состояние");
axis();
grafik(23,dd % 23,RED);
grafik(28,dd % 28,GREEN);
grafik(33,dd % 33,BLUE);
getch();
closegraph ();
}
/*---------------------------------*/
void axis(void)
{ /* Построение и маркировка осей */
int j,str; char qq[5];
line(0,140,0,340); /* вертикальная ось */
line(0,240,a*20,240);
/* горизонтальная ось */
/* цикл построения малых и больших штрихов на оси х */
for(j=l;j <= a;j++) {
str=5;
if(j % 5 == 0) str=10;
line(j*20,240+str,j*20,240-str);
if (str == 10)
{ /* маркировка больших штрихов через 5 дней */
itoa(j,qq,10);
outtextxy(j*20-5,240+20,qq);
}
}
} /*---------------------------*/
void grafik(int t,int dfi,int color)
/* построение синусоиды */
/* t - период (в днях) */
/* dfi - смещение для х=0 */
/* color - цвет графика */
/***************************/
{
int x,y,x1,y1,k; х=0;
y=round(240-100*sin(twopi*dfi/t));
setcolor(color); moveto(x,у);
for(k=l; k<=a; k++)
{
xl=20*k;
yl=round(240-100*sin(twopi*(k+dfi)/t));
lineto(xl,yl); }
}
/*-------------------------------*/
long offset(int d,int m,int y)
{
/* Вычисляет количество дней от 1.01.1900 до d.m.y */ int k; long dd;
dd=365;
/* Количество дней в 1900 г */
/* Цикл учета полных лет */
for(k=1901; k<y; k++)
{
dd += 365;
if(k % 4 == 0) dd++;
/* Поправка на високосный год */
}
dd += d;
/* Учет дней в году у до месяца m */
for(k=0; k<m-l; k++)
dd += days[k];
/* Добавление дней, прошедших в месяце m */ return dd;
}
/*-----------------------------------*/
int round(double x)
{ /* округление вещественного числа до ближайшего целого */
if(x>=0) return (int)(x+0.5);
return (int)(x-0.5); }
Программа 8.18.pas
program bioritms; uses Graph,Dos; label wwod; const
twopi=2*Pi;
days:array [1..12] of byte = (30,28,31,30,31,30,31,31,30,31,30,31);
var
a, k, d,m, y,dl,ml,yl:word;
gd,gm:integer;
dd: longint;
procedure axis; var
j,stroke:integer; s:string[2];
begin
line(0,140,0,340);
line(0,240,a*20,240);
for j : =1 to a do begin
stroke:=5; str(j,s);
if j mod 5=0 then stroke:=10;
line(j *20,240+stroke,j*20,240-stroke);
if stroke=10 then outtextxy(j*20-5,240+20, s) ;
end;
end;
procedure grafik(t,dfi,color:integer) ;
var
x,y,x1,y1,k:integer;
begin
x:=0;
y:=round(240-100*sin(twopi*dfi/t));
setcolor(color); moveto(x,y) ;
for k:=l to a do begin
xl:=20*k;
yl:=round(240-100*sin(twopi*(k+dfi)/t));
lineto(xl,yl);
end;
end;
function offset(d,m,y:integer)rlongint;
{Вычисляет количество дней от 1.01.1900 до d.m.y}
var
k:integer;
dd:longint;
begin
dd:=365; {Количество дней в 1900 г} {Цикл учета полных лет}
for k:=1901 to y-1 do begin
dd:=dd+365;
{Поправка на високосный год}
if k mod 4=0 then inc(dd);
end;
{Учет дней в году у до месяца m}
for k:=l to m-1 do inc(dd,days[k]);
sdwig:=dd+d; {Добавление дней, прошедших в месяце m}
end;
begin
gd:=0; wwod:
writeln('Биоритмы на текущий месяц');
writeln('Введите день, месяц (числом) и год своего рождения');
readln(d,m,у);
GetDate(y1,m1,d1,k); {опрос текущей даты}
if (m<l)or(m>12)or(d<l)or(d>days[m])or(y<1900)or(y>yl) then begin
write('Вы ошиблись. Нажмите Enter и повторите ввод');
readln;
goto wwod;
end;
if yl mod 4=0 then days[2]:=29; {поправка на високосный год}
а:=days[ml];
{число дней в текущем месяце}
{ Интервал от дня рождения до начала текущего месяца}
dd:=offset(I,ml,yl)-offset(d,m,у);
initgraph(gd,gm,'');
outtextxy(0,0,'красный - физическое состояние');
outtextxy(0,20,'синий - эмоциональное состояние');
outtextxy(0, 40,'зеленый - интеллектуальное состояние');
axis;
{Построение и разметка координатных осей}
grafik(23,dd mod 23,RED);
grafik(28,dd mod 28,GREEN);
grafik(33,dd mod 33,BLUE);
readln;
closegraph;
end.
Закрашивание и заполнение замкнутых областей
Графическое изображение становится более красочным, когда на экране представлены не только контуры геометрических фигур, но и закрашенные или заштрихованные области. Выполнение такого рода операций осуществляется с помощью процедур заливки (англ, paint, fill). Подобно тому, как при построении линий задается точечный шаблон, управляющий воспроизведением пикселов, для площадных объектов существует точно такая же возможность задания двумерной маски, хранящейся в битовом массиве размером 8x8 (Си, Паскаль) или 8x8xk (QBasic, 1 < k < 8). Такая маска, перемещаясь по горизонтали и вертикали, управляет окраской попадающих под ее биты пикселов. К сожалению, идеи такого управления, заложенные в системе QBasic и BGI-пакете, сильно отличаются.
Более-естественными нам представляются алгоритмы заполнения площадных объектов, реализованные фирмой Borland. Для них характерно задание квадратной маски 8x8, управляющей окраской пикселов изображения, накрываемых этой маской. Если в соответствующем разряде маски находится единица, то расположенный под ним пиксел закрашивается в заранее установленный цвет. Пиксел экрана, попадающий под нулевой бит маски, окрашивается в цвет фона. После обработки очередной площадки изображения маска смещается на 8 пикселов вправо. Когда текущая полоска экрана исчерпана, маска перемещается в начало следующей полоски. Системная или пользовательская маска в сочетании с установленными цветами заливки и фона составляют то, что принято называть шаблоном (англ, pattern) заливки.
Для выбора шаблона заливки в BGI-пакете используются две функции (процедуры) — setfillstyle И setfillpattern: setfillstyle(numpat,col); setfiilpattern(pattern,col);
Параметр numpat задается целым числом из диапазона [0,12] или значением соответствующей мнемонической константы (табл. 8.2). 0н позволяет выбрать один из системных шаблонов (numpat < 12) или установить пользовательский шаблон (numpat = 12), описываемый с помощью функции setfillpattern. Параметр col задает код цвета, в который должны окрашиваться пикселы изображения, попадающие под единичные биты маски.
Таблица 8.2. Шаблоны заливки в BGI-пакете
Номер шаблона |
Константа ТС |
Константа ТР |
Способ заполнения области |
||
0 |
EMPTY FILL |
EmptyFiil |
Заливается цветом фона |
||
1 |
S0LIQ_FILL |
SolidFill |
Заливается цветом col |
||
2 |
LINE_FILL |
Line Fill |
Штриховка горизонтальными линиями |
||
3 |
LT3LASH FILL |
LtSlashFill |
Тонкая штриховка под 45° |
||
4 | SLASH FILL | SlashFill | Толстая штриховка под 45° | ||
5 | BK£T.ASH "ILL | EkSlashFill | Толстая штриховка под 135° | ||
6 | LTBKSLASH_?1LL | LtBkSlashFill | ; Тонкая штриховка под 135° | ||
7 |
HATCH_FILL |
HatchFill |
Двойная штриховка, 0° и 90° |
||
8 |
XHATCH FILL |
XHatchFill |
Двойная штриховка, 45° и 135° |
||
9 |
INTERLEAVE FILL |
InterleaveFill |
Короткие чередующиеся штрихи |
||
10 |
WIDE_D0T_FILL |
WideDotFill |
Редкий точечный растр |
||
11 |
CL0SE D0T FILL |
CloseDotFill |
Густой точечный растр |
||
12 |
USER_FILL |
UserFill |
По шаблону пользователя |
||
Программа 8_08.с
/* Системные шаблоны заполнения фигур */
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
main() {
int k,gd=0,gm;
int col=14; //желтый цвет для единичных пикселов шаблона
initgraph(sgd,&gm,"");
for(k=0; k<12;k++)
{
cleardevice();
printf("\n номер шаблона=%3",k);
setfillstyle(k,14);
bar(300,100,400,200);
getch();
} Closegraph(); }
Если при выборе шаблона вы остановились на numpat = 12, то перед построением залитых фигур необходимо определить структуру нестандартного узора. В качестве параметра pattern в функции filipattern может выступать любой 8-байтовый массив:
ТС: char patl[] = {0xCC,0x33,0хСС,0x33,0хСС,0x33, 0хСС, 0x33};
setfillpattern(patl,4);
ТР: const
patl:FillPatternType=($CC,$33,$СС,$33,$СС,$33,$СС, $33);
SetFillPattern(patl,4);
Тип данных FillPatternType, описанный в модуле Graph, представляет собой байтовый массив из 8 элементов — array [1. .8] of byte.
В BGI-пакете предусмотрены пять процедур (функций) для построения заполненных геометрических фигур:
bar (xl,y1,x2,y2) — залитый прямоугольник без контура;
bar3d(x1,y1,x2,y2,d,t) — трехмерный столбик;
fillellipse (x, у, rх, rу) — залитый эллипс или окружность;
filipoly (п,ху) — залитый многоугольник;
sector (х,у, a1,a2, rх, rу) — залитый эллиптический сектор;
pieslice (x,y,al,a2, r) — залитый круговой сектор.
В трехмерном столбике первые четыре параметра определяют положение передней грани. Параметр d задает "глубину" столбика, и иногда его рекомендуют выбирать равным четверти ширины (d = 0.25 * (х2 - x1)). Последний аргумент в Си может принимать нулевое или ненулевое значение, а в Паскале — true или false. Если он отличен от о или равен true, то верхняя грань столбика ("крыша") рисуется. В противном случае столбик воспроизводится без крыши, что дает возможность поставить над ним еще один столбик и не заботиться об удалении невидимых линий. В трехмерном столбике заливается только передняя грань.
Процедура bar3d, как правило, используется для воспроизведения объемных диаграмм. 0днако в ней можно задать нулевую глубину столбика (d = о) и тогда результат ее работы заменяет выполнение двух последовательных процедур:
bar111,11,99,99);
rectangle(10,10,100,100);
Первая из них отображает залитую внутренность прямоугольника, а вторая — обводит его границу.
Параметры остальных процедур довольно подробно рассматривались в предыдущих разделах.
Недавно одному из авторов пришлось разрабатывать программные средства выделения замкнутых областей в цифровых электронных картах. Кроме использования самых простых идей — выделение цветом и штриховками, -наиболее плодотворный результат при выводе на дисплей был достигнут регулярными заполнителями с помощью заливочных шаблонов. Самая богатая коллекция таких шаблонов, которая была известна нам по литературе, насчитывала 28 образцов [13]. 0днако по своему разнообразию этот набор показался нам недостаточно выразительным. Кроме того, в нем отсутствовала какая-либо классификация шаблонов. Пришлось потратить несколько дней на эксперименты с редактором образов в системе Builder C++, после чего на свет появилось более сотни шаблонов. Их мы и представляем нашим читателям в надежде, что наиболее любознательные сумеют построить еще не один десяток приятных узоров.
Программа 8_09.с
/* Пользовательские шаблоны заполнения фигур
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
#define Nmax 111
main()
{
int k,gd=0,gm;
unsigned char pattern[Nmax][8] ={
//Массив заливочных шаблонов 8x8
//==== Сплошная заливка
//шаблон 0 = очистка цветом фона
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
//шаблон 1 = сплошная заливка установленным цветом
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
//==== Штриховка сплошными линиями
//= по горизонтали
//шаблон 2 = горизонтали (линия — 1 пиксел, зазор — 7)
{0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
//шаблон 3 = горизонтали (2:6)
{0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},
//шаблон 4 = горизонтали (3:5)
{0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00},
//шаблон 5 = горизонтали (4:4)
{0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00, 0x00},
//шаблон б = горизонтали (5:3)
{0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00, 0x00},
//шаблон 7 = горизонтали (6:2)
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00},
//шаблон 8 = горизонтали (7:1)
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00},
//шаблон 9 = горизонтали (1:1)
{0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00},
//шаблон 10.= горизонтали (1:3)
{0xFF,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},
//шаблон 11 = горизонтали (3:1)
{0xFF, 0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0x00},
//шаблон 12 = горизонтали (2:2)
{0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00},
//= по вертикали //шаблон 13 = вертикали (1:7)
{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
//шаблон 14 = вертикаль (1:3)
{0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88},
//шаблон 15 = вертикаль (2:6)
{0хС0,0хС0, 0хС0,0хС0,0хС0,0хС0,0хС0, 0хС0.},
//шаблон 16 = вертикаль (3:5)
{0хЕ0,0хЕ0,0хЕ0,0хЕ0,0хЕ0,0хЕ0,0хЕ0,0хЕ0},
//шаблон 17 = вертикаль (4:4)
{0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0},
//шаблон 18 = вертикали (5:3)
{0xFS, 0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8},
//шаблон 19 = вертикали (6:2)
{0xFC,0xFC,0xFC,0xFC,0xFC,0xFC, 0xFC, 0xFC},
//шаблон 20 = вертикали (7:1)
(0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE},
//шаблон 21 = вертикали (1:1)
{0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA},
//шаблон 22 = вертикали (3:1)
{0xEE, 0xEE, 0xEE,0xEE,0xEE,0xEE,0xEE,0xEE},
//шаблон 23 = вертикали (2:2)
{0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC},
//==== прямоугольная двойная штриховка
//шаблон 24 = сетка (1:7,1:7)
{0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
//шаблон 25 = сетка (2:6,2:6)
{0xFF,0xFF,0хС0,0хС0,0хС0,0хС0,0хС0, 0хС0},
//шаблон 26 = сетка (3:5,3:5)
{0xFF,0xFF,0xFF,0хЕ0,0хЕ0,0хЕ0,0хЕ0, 0хЕ0},
//шаблон 27 = сетка (4:4,4:4)
{0xFF,0xFF,0xFF,0xFF,0xF0,0xF0,0xF0, 0xF0},
//шаблон 28 = сетка (5:3,5:3)
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFS,0xFS,0xF8},
//шаблон 29 = сетка (6:2,6:2)
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFC, 0xFC},
//шаблон 30 = сетка (7:1,7:1)
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE},
//шаблон 31 = сетка (2:2,2:2)
{0xFF,0xFF,0x99,0x99,0xFF,0xFF,0x99, 0x99},
//=== штриховка по диагонали //шаблон 32 = справа налево (1:10)
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80},
//шаблон 33 = справа налево (3:7)
{0x83,0x07,0х0Е,0xlC,0x38,0x70,0хЕ0, 0хС1},
//шаблон 34 = справа налево (6:3)
{0xC7,0x8F,0x1F,0хЗЕ,0х7С,0xF8,0xFl, 0хЕЗ},
//шаблон 35 = слева направо (1:10)
{0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01},
//шаблон 36 = слева направо (3:7)
(0хС1,0хЕ0,0x70,0x38,0xlC,0x0E,0x07,0x83},
//шаблон 37 = слева направо (6:3)
{0хЕЗ,0xFl,0xF8,0х7С,0хЗЕ,0xlF,0x8F, 0xC7},
//==== косоугольная двойная штриховка
//шаблон 38 = штриховка (1:6,1:6)
{0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81},
//=== Штриховка штриховыми линиями
//= по горизонтали
//шаблон 39 = штрихи (4*1:4,4) в шахматном порядке
{0xF0,0x00,0x00,0x00,0x0F,0x00,0x00,0x00},
//шаблон 40 = штрихи (5*1:3,4) в шахматном порядке
{0xF8,0x00,0x00,0x00,0xlF,0x00,0x00,0x00},
//шаблон 41 = штрихи (4*2:4,4) в шахматном порядке
{0xF0,0xF0,0x00,0x00,0x0F,0x0F,0x00,0x00},
//шаблон 42 = штрихи (5*2:3,4) в шахматном порядке
{0xF8,0xF8,0x00,0x00,0xlF,0xlF,0x00,0x00},
//шаблон 43 = редкие штрихи (4*1:4,7)
{0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
//шаблон 44 = сплошная и штриховая линии
{0xFF,0x00,0x00,0x00,0xF0,0x00,0x00,0x00},
//= по вертикали //шаблон 45 = редкие штрихи (4*1:4,7)
{0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x00},
//шаблон 46 = штрихи (4*1:4,4) в шахматном порядке
{0x80,0x80,0x80,0x80,0x08,0x08,0x08,0x08},
//==== Волнообразные линии //= по горизонтали
//шаблон 47 = тонкие с небольшой амплитудой
{0xF0,0x0F,0x00,0x00,0x00,0x00,0x00,0x00},
//шаблон 48 = толстые с небольшой амплитудой
{0xF0,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00},
//шаблон 49 = тонкие с большой амплитудой
(0x81,0x42,0x24,0x18,0x00,0x00,0x00,0x00},
//шаблон 50 = толстые с большой амплитудой
{0x81,0хСЗ,0x66,0хЗС,0x18,0x00,0x00,0x00},
//= по диагонали //шаблон 51 = слева направо
{0x10,0x10,0x10,0xF0,0x01,0x01,0x01, 0x0F},
//==== Растровые сетки //шаблон 52 = густые точки в шахматном порядке (1:1,1)
{0x00,0x55,0x00,0хАА,0x00,0x55,0x00,0хАА},
//шаблон 53 = густые точки в шахматном порядке (1:3,2)
{0x00,0x44,0x00,0x11,0x00,0x44,0x00,0x11},
//шаблон 54 = мелкие редкие точки в шахматном порядке
{0x22,0x00,0x00,0x00,0x88,0x00,0x00,0x00},
//шаблон 55 = средние точки в шахматном порядке
{0x00,0хбб,0x66,0x00,0x00,0x99,0x99,0x00},
//шаблон 56 = средние редкие точки в шахматном порядке
{0x00,0x00,0x30,0x30,0x00,0x00,0x03,0x03},
//шаблон 57 = средние очень редкие точки в шахматном порядке
{0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00},
//шаблон 58 = крупные точки
{0x00,0x00,0хЗС,0хЗС,0хЗС,0хЗС,0x00,0x00},
//шаблон 59 = густая шахматная доска
{0хСС,0x33,0хСС,0x33,0хСС,0x33,0хСС,0x33},
//шаблон 60 = очень густая шахматная доска
{0хАА,0x55,0хАА,0x55,0хАА,0x55,0хАА, 0x55},
//==== Фигуры в узлах прямоугольной сетки
//шаблон 61 = крестики
{0x10,0x10,0х7С,0x10,0x10,0x00,0x00,0x00},
//шаблон 62 = пунктирные крестики
(0x10,0x00,0x54,0x00,0x10,0x00,0x00,0x00},
//шаблон 63 = жирные кресты
{0x00,0x00,0x18,0хЗС,0хЗС,0x18,0x00,0x00},
//шаблон 64 = сетка из больших ромбиков
{0x00,0x18,0хЗС,0х7Е,0х7Е,0хЗС,0x18,0x00},
//шаблон 65 = сетка из больших квадратов
{0x00,0х7Е,0х7Е,0х7Е,0х7Е,0х7Е,0х7Е, 0x00},
//шаблон 66 = залитые квадраты 5x5
{0xF8,0xF8,0xF8,0xF8,0xF8,0x00, 0x00, 0x00}, //шаблон 67 = залитые квадраты 6x6
{0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0x00, 0x00},
//шаблон 68 = залитые квадраты 7x7
{0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x00},
//шаблон 69 = отдельные большие квадратики
{0x00,0х7Е,0x42,0x42,0x42,0x42,0х7Е,0x00},
//шаблон 70 = отдельные маленькие квадраты
{0x00,0x00,0хЗС,0x24,0x24,0хЗС,0x00,0x00},
//шаблон 71 - квадратики с точками в центре
(0x00,0х7Е,0x42,0х5А,0х5А,0x42,0х7Е, 0x00},
//шаблон 72 = символ "х"
(0x00,0x42,0x24,0x18,0x18,0x24,0x42, 0x00},
//шаблон 73 = символ "у"
{0x00,0x44,0x28,0x10,0x10,0x10,0x00,0x00},
//шаблон 74 = галочки
{0x00,0x42,0x24,0x18,0x00,0x00,0x00, 0x00},
//шаблон 75 = перевернутые галочки
{0x00,0x00,0x00,0x18,0x24,0x42,0x00,0x00},
//шаблон 76 = тонкие стрелки вверх
{0x10,0x38,0x54,0x10,0x10,0x10, 0x00, 0x00}, //шаблон 77 = тонкие стрелки вниз
{0x10,0x10,0x10,0x54,0x38,0x10,0x00,0x00},
//шаблон 78 = тонкие стрелки влево
{0x00,0x20,0x40,0xFC,0x40,0x20,0x00,0x00},
//шаблон 79 = тонкие стрелки вправо
{0x00,0x10,0x08,0xFC,0x08,0x10,0x00, 0x00},
//шаблон 80 = жирные стрелки влево
(0x00,0x08,0x18,0x3F,0x3F,0x18,0x08,0x00},
//шаблон 81 = двойные стрелки по вертикали
{0x20,0x70,0хА8,0x22,0x22,0х8А,0x07,0x02},
//шаблон 82 = двойные стрелки по горизонтали
{0x24,0x02,0xlF,0x02,0x24,0x40,0xFS, 0x40},
//шаблон 83 = контуры ромбиков по сетке
{0x00,0x10,0x28,0x44,0x28,0x10,0x00,0x00},
//шаблон 84 = залитые ромбики и точки
{0x81,0x18,0х3С,0х7Е,0х7Е,0хЗС,0x18,0x81},
//шаблон 85 = залитые ромбики по вертикали
{0x18,0хЗС,0хЗС,0х7Е,0х7Е,0хЗС,0хЗС,0x18},
//шаблон 86 = кружочки на сетке (d=6)
{0x00,0хЗС,0x42,0x42,0x42,0x42,0хЗС,0x00},
//шаблон 87 = кружочки на сетке (d=5)
{0x44,0x38,0x00,0x00,0x00,0x38,0x44,0x44},
//шаблон 88 = не залитые квадратики
(0x80,0x7F,0x41,0x41,0x41,0x41,0x41,0x7F},
//шаблон 89 = точечная сетка с точкам в узлах
{0x08,0x00,0x08,0x50,0x08,0x00,0x08, 0x00},
//==== Фигуры в шахматном порядке
//шаблон 90 = ромбики в шахматном порядке
{0x00,0x18,0хЗС,0х7Е,0х7Е,0хЗС,0x18,0x00},
//шаблон 91 = густые мелкие квадратики в шахматном порядке
{0xF0,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x0F),
//шаблон 92 = более крупные квадраты в шахматном порядке
{0хСС,0x33,0хСС,0x33,0хСС,0x33,0хСС,0x33},
//шаблон 93 = контуры квадратиков в шахматном порядке
{0x42,0хСЗ,0хЗС,0x24,0x24,0хЗС,0хСЗ,0x42},
//==== Узоры //шаблон 94 = залитые кирпичики по горизонтали
{0x00,0xFB,0xFB,0xFB,0x00,0xDF,0xDF,0xDF},
//шаблон 95 = не залитые кирпичики по горизонтали
{0x00,0xFB,0x0A,0xFB,0x00,0xDF,0x50,0xDF},
//шаблон 96 = залитые кирпичики по вертикали
{0хЕЕ,0хЕЕ,0х0Е,0хЕЕ,0хЕЕ,0хЕ0,0хЕЕ,0хЕЕ},
//шаблон 97 = не залитые кирпичи по диагонали
(0x01,0x82,0x44,0x28,0x10,0x20,0x40,0x80},
//шаблон 98 = не,залитые кирпичи по диагонали
{0x80,0x41,0x22,0x14,0x08,0x04,0x02,0x01},
//шаблон 99 = волнистый узор
{0x94,0x84,0x48,0x30,0x00,0хС1,0x22,0x14},
//шаблон 100 = спиральный узор
{0xFF,0x01,0xVD,0x45,0x5D,0x41,0x7F,0x00},
//шаблон 101 = узор 1
{0x00,0хЕС,0х2А,0х2А,0х2А,0х2А,0хЕС,0x00},
//шаблон 102 = узор 2
{0x00,0x00,0х7Е,0x42,0х7Е,0x42,0х7Е,0x42},
//шаблон 103 = узор 3
{0x00,0x50,0хЗЕ,0x6D,0x7F,0x63,0x36,0xF0},
//шаблон 104 = узор 4
{0x92,0x24,0x49,0x92,0x24,0x49,0x92,0x24},
//шаблон 105 = узор 5
{0хВ1,0x22,0x14,0x14,0x22,0x91,0x48,0x24},
//шаблон 106 = узор 6
{0x02,0x91,0x68,0x08,0x10,0x16,0x89,0x40},
//шаблон 107 = узор 7
{0хСЗ,0x42,0х5А,0х7Е,0х7Е,0х5А,0x42, 0хСЗ},
//шаблон 108 = узор 8
{0x03,0x18,0x60,0x80,0x80,0x40,0x20,0x18},
//шаблон 109 = узор 9
{0xBF,0xA0,0xA0,0xBF,0xFB,0x0A,0x0A,0xFB},
//шаблон 110 = узор 10
{0xFF,0x81,0xBD,0xA5,0xA5,0xBD,0x81,0xFF},
//шаблон 111 = узор 11
{0xFF,0x81,0x81,0x99,0x99,0x81,0x81,0xFF}};
initgraph(&gd,&gm,""); f0r(k=0;k<Nmax;k++) {
cleardevice();
g0t0xy(1,1);
printf("\n номер шаблона=%d", k) ;
setfillpattern(&pattern[k] [0],4);
setfillstyle(12, 14);
bar3d(240,40,440,240,16,l);
getch (); } }
0дной из самых интересных процедур заливки является функция fi00dfiii: fl00dfill(x,у,eg);
Работает эта процедура с замкнутой областью, внутренней или внешней части которой принадлежит точка с координатами (х,у). Параметр eg задает цвет пикселов, составляющих границу области. Заливке подвергается та часть области, которой принадлежит точка (х,у). При этом предполагается, что в заливаемой области не встречаются пикселы цвета eg. Если в границе области будет обнаружен микроскопический разрыв хотя бы в один пиксел, то узор-заполнитель обязательно просочится через него и заливка распространится как на внутреннюю, так и на внешнюю части области. При заливке цвет границы сохраняется. 0днако в случае совпадения цвета границы с цветом окраски единичных пикселов шаблона получится область без ярко выраженной границы. Повторная перезаливка такой области уже невозможна.
В системе QBasic мы уже демонстрировали возможность построения залитого прямоугольника с помощью оператора LINE .. . BF. Заполнение его
внутренности ограничено сплошной окраской пикселов в заданный цвет. Все остальные возможности по построению закрашенных или заштрихованных фигур сосредоточены в операторе PAINT, пользоваться которым не так уж и легко. 0ператор PAINT допускает несколько модификаций:
PAINT (x,y),[c0l],eg PAINT (x,y),Ml$,cg PAINT (x,y),Ml$,cg,M2$
Первый формат оператора очень напоминает функцию fi00dfill, заливающую внутреннюю или внешнюю часть области заданным цветом c0l или цветом переднего плана, ранее установленным по оператору C0L0R.
Второй формат отличается от первого только тем, что цвет и узор заливки задаются с точностью до бита в строке M1$, длина которой может достигать 64 байт. Первые четыре байта строки М1$ определяют цветовые атрибуты восьми смежных пикселов на экране. При этом в первом байте такой четверки сосредоточены биты управления синим цветом, во втором байте — биты управления зеленым цветом, в третьем байте — биты управления красным цветом, а в последнем байте — биты управления повышенной яркостью. Четыре следующих байта строки MI$ определяют аналогичные атрибуты восьми смежных пикселов следующей строки и т. д. В полном объеме строка-шаблон позволяет задать цветовые атрибуты пикселов, образующих на экране прямоугольник размером 8x16.
Попробуем подобрать узор заливки, при котором область покрывается красными штрихами длиной по четыре пиксела с шахматным расположением штрихов между строками. 0чевидно, что первые четыре байта строки-шаблона могут быть получены в виде суммы:
M1$=CHR$(&H0)+CHR$(&H0)+CHR$(&HF0)+CHR$(&HFF)
Первый байт подавляет биты синего цвета у всех восьми пикселов, второй -биты зеленого, третий байт формирует у первых четырех пикселов единичные биты красного цвета, сохраняя у оставшихся четырех нулевые разряды. Последний байт заносит по единице в бит яркости каждого пиксела, превращая черный цвет фона в темно-серый.
Три следующие четверки шаблона могут состоять из одинаковых наборов вида:
M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H00)+CHR$(&HFF) M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H00)+CHR$(&HFF) M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(SH00)+CHR$(&HFF)
Пятая строка шаблона должна изменять расположение красного штриха:
M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H0F)+CHR$(SHFF)
Наконец, три последних четверки должны повторять зазор между строками, описанный выше:
M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H00)+CHR$(&HFF) M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H00)+CHR$(&HFF) M1$=M1$+CHR$(&H0)+CHR$(&H0)+CHR$(&H00)+CHR$(&HFF)
0кончательный вариант программы, в которой можно поварьировать строку заливочного шаблона, приведен ниже.
Программа 8_10.bas
REM Красные штрихи в шахматном порядке SCREEN 12
I1$=CHR$(&HFF):' байт с битами повышенной интенсивности
B0$=CHR$(&H0):' байт с нулевыми битами синего цвета
G0$=CHR$(&H0):' байт с нулевыми битами зеленого цвета
R0$=CHR$(&H0):' байт с нулевыми битами красного цвета
R40$=CHR$(&HF0):' байт с битами красного-черного
R04$=CHR$(&HF):' байт с битами черного-красного
M1$=M1$+B0$+G0$+R40$+I1$: M1$=M1$+B0$+G0$+R0$+I1$ M1$=M1$+B0$+G0$+R0$+I1$ : M1$=M1$+B0$+G0$+R0$+I1$ M1$=M1$+B0$+G0$+R04$+I1$: M1$=M1$+B0$+G0$+R0$+I1$ M1$=M1$+B0$+G0$+R0$+I1$ : M1$=M1$+B0$+G0$+R0$+I1$
LINE (100,100)-(200,200),,B PAINT (150,150),M1$,15 END
Сравнительно несложная модификация этой программы позволяет изобразить фрагмент кирпичной стены:
Программа 8_11.bas
' Построение кирпичной стены SCREEN 12
I1$=CHR$(&HFF) ' байт с битами повышенной интенсивности
B0$=CHR$(&H0) ' байт с нулевыми битами синего цвета
G0$=CHR$(&H0) ' байт с нулевыми битами зеленого цвета
R8$=CHR$(&HFF) ' байт с единичными Ситами красного цвета
RL$=CHR$(&H40) ' красный бит слева
RR$=CHR$(£H2) ' красный бит справа
M1$=M1$+B0$+G0$+R8$+I1$: M1$=M1$+B0$+G0$+RL$+I1$
M1$=M1$+B0$+G0$+RL$+I1$: M1$=M1$+B0$+G0$+RL$+I1$
M1$=M1$+B0$+G0$+R8$+I1$: M1$=M1$+B0$+G0$+RR$+I1$
M1$=M1$+B0$+G0$+RR$+I1$: Ml$=Ml$+B0$+G0$+RR$+Il$
LINE (100,100)-(200,200),,B
PAINT (150,150),M1$,15 END
Заливка площадных фигур "прозрачными" шаблонами
При заполнении замкнутых областей тем или иным узором изменению цвета подвергаются все пикселы, попадающие под биты шаблона. Под "прозрачным" шаблоном мы будем понимать такой вариант заполнения, при котором единичные пикселы шаблона тем или иным способом взаимодействуют с пикселами изображения, а нулевые биты шаблона не изменяют цвет соответствующих элементов рисунка. Таким образом, в шаблоне могут быть созданы участки, сквозь которые просвечивает прежний рисунок или его фон.
Идея создания такого эффекта довольно проста. Из прямоугольной области черного экрана (цвет рисования по умолчанию равен 15, а цвет фона равен 0), на которую предстоит наложить прозрачный шаблон, логически вырезаются и запоминаются два битовых массива. 0дин из них формируется путем обычного наложения шаблона с его будущим цветом, а второй — также путем обычного наложения шаблона, получающегося в результате инвертирования битов исходного шаблона. Цвет такого "антишаблона" устанавливается равным 15 (в двоичном представлении такой цвет имеет код 1111). Второй массив предназначен для сохранения кодов цвета тех пикселов изображения, которые попадут под нулевые биты исходного шаблона, т. е. для прорезания в изображении траектории, образуемой единичными битами шаблона. Выполняется такое вырезание путем наложения "антимассива" на изображение по операции AND. На расчищенное таким образом место по операции X0R наносится изображение исходного шаблона.
В приведенной ниже программе область изображения представлена квадратом (0,0,300,300), закрашенным цветом fon, который выбирается случайным образом. В точках с координатами (60,60) и (240,240) проводятся красные окружности радиусом в 30 пикселов. "Прозрачный" и обычный шаблоны, покрывающие квадратные области размером 100x100, своими центрами совпадают с центрами окружностей. Их узор состоит из пилообразных линий цвета c1, а цвет фоновых пикселов — черный (по умолчанию).
Результат работы программы лучше увидеть своими глазами. После вывода очередного изображения программа ждет нажатия любой клавиши и прекращает работу, опознав код клавиши <Esc>.
Программа 8_12.с
/* "Прозрачный шаблон"*/
#include <graphics.h>
#include <stdlib.h>
#include <alloc.h>
main () {
int gd=0,gm;
int cl,fon;
char q,
patl[] = {0x81,0x42,0x24,0x18,0x00,0x00,0x00, 0x00},
pat2[] = {0x7E,0xBD,0xDB,0xE7,0xFF,0xFF,0xFF, 0xFF};
int xyl[]={0,0,300,0,300,300,0,300} ;
long k;
char far *pl,*p2;
initgraph(Sgd,&gm,"") ;
k=imagesize(10,10,110,110) ;
pl=farmalloc(k); /* Запрос памяти */
p2=farmalloc(k);
m:
fon=random(15)+1;
ml:
cl=random(15)+1;
if((cl==fon)) goto ml; /*3апоминание битового образа шаблона в заливаемой области*/
setfillstyle(12,0);
setfillpattern(patl,cl);
bar(10,10,110,110);
getimage(10,10,110,110,pi); /*3апоминание битового образа антишаблона V
setfillstyle(12,0);
setfillpattern(pat2,15);
bar(10,10,110,110);
getimage(10,10,110,110,p2); /*Формирование изображения */
setfillstyle(l,fon);
fillpoly(4,xyl);
setcolor (4);
circle(60,60,30) ; /* Вырезание профиля шаблона */
putimage(10,10,р2,AND_PUT); /*Вклеивание "прозрачного" шаблона*/
putimage(10, 10,pl,X0R_PUT) ; /*3аливка обьмным непрозрачным шаблоном*/
setcolor (4);
circle(240,240,30);
setfillstyle(12,0);
setfillpattern(patl,cl);
bar(190,190,290,290); /* 0жидание нажатия клавиши. Выход по нажатию Esc*/
q=getch();
if(q != 0xlb) goto m;
closegraph(); }