ЛАБОРАТОРНАЯ РАБОТА № 1
Растровые и векторные примитивы. Алгоритмы генерации векторных примитивов
 
1. Цель работы
 
1) Освоить программирование графических примитивов с использованием алгоритмов генерации векторов и окружностей.
2) Разработать программу в среде визуального проектирования C++Builder, позволяющую демонстрировать различные способы построения векторных объектов.
3) Приобрести практические навыки по программированию пользовательских интерфейсов с использованием пакета визуального программирования  C++Builder.
 
2. Постановка задачи и порядок выполнения работы
 
2.1. Создать проект в среде визуального проектирования C++Builder. Порядок создания проекта приведен в разделе 6 данной лабораторной работы.
2.2. В графическом окне проекта, используя команды компьютерной графики, изобразить структурную схему согласно варианту из таблицы 1.
                                                                                                                                                        Таблица 1
Вар.
Тип структурной схемы
Алгоритм генерации векторов
Цвет
фигур
Цвет
фона
1
Трехуровневое выровненное бинарное дерево
Простой алгоритм ЦДА
Синий
Голубой
2
Линейный однонаправленный список из 4-х элементов
Несимметричный алгоритм ЦДА
Зеленый
Белый
3
Линейный двунаправленный список из 3-х элементов
Алгоритм Брезенхема
Коричневый
Желтый
4
Блок-схема программы суммирования в цикле элементов массива
Простой алгоритм ЦДА
Черный
Серый
5
Электрическая схема: 2 клеммы, однополюсный выключатель, резистор
Несимметричный алгоритм ЦДА
Красный
Желтый
6
Четырехуровневое несимметричное бинарное дерево
Алгоритм Брезенхема
Синий
Серый
7
Линейный однонаправленный список из 5-ти элементов
Простой алгоритм ЦДА
Зеленый
Желтый
8
Линейный двунаправленный список из 4-х элементов
Несимметричный алгоритм ЦДА
Коричневый
Розовый
9
Блок-схема программы перемножения в цикле элементов массива
Алгоритм Брезенхема
Черный
Белый
10
Электрическая схема: 2 клеммы, конденсатор, резистор
Простой алгоритм ЦДА
Красный
Белый
 
2.3. Структурная схема должна быть прорисована двумя способами:
1) с использованием стандартных функций графической библиотеки пакета С++Builder;
2) с использованием алгоритмов генерации векторов, указанных в таблице 1.
 
Структурные схемы, построенные различными способами должны быть идентичны по форме и размерам.
 
2.4. Процесс создания программного кода проекта должен выполняться в два этапа.
Первый этап выполняется во время планового лабораторного занятия, после получения варианта задания у преподавателя. За это время студент должен выполнить следующее:
- создать проект (см. подраздел 6.1);
- выполнить компоновку главной формы Form1 согласно рисунку 8 (см. подраздел 6.2);
- добавить к проекту дополнительный модуль с формой Form2  (см. подраздел 6.3)
- установить на поле формы Form2 стандартные компоненты ввода, вывода и управления, согласно рисунку 9, в порядке, описанном в подразделе 6.4.
Результаты проделанной работы предъявить преподавателю.
Второй этап выполняется дома, с использованием проекта, созданного во время лабораторного занятия. В домашнюю работу входит окончательная доработка программы, при которой в программный код модуля  Form2  добавляются функции, позволяющие выполнять прорисовку структурной схемы первым и вторым способами, согласно пункту 2.3. Порядок формирования этих функций описан в разделе 7, "Разработка файла реализации Unit2.cpp".
2.5. Оформить отчет согласно правилам, принятым в учебном заведении.

3.  Краткие теоретические сведения
 
3.1. Растровые и векторные примитивы
 
Графические примитивы – это простейшие геометрические фигуры, такие как точка, линия, прямоугольник, полигон (многоугольник), окружность, дуга, битовый массив и др.
Объединяя различные примитивы можно создавать графические фигуры любой формы и размеров.
Основное назначение примитивов – обеспечение программистов удобным набором готовых функций для изображения графических объектов.
Примитивы разделяются на два класса: растровые и векторные.
К растровым примитивам относятся битовые карты, битовые матрицы и шрифты.
Растровое изображение строится из отдельных точек – пикселов. Растровые изображения хранятся в матрицах изображения. Каждый элемент матрицы содержит информацию о координатах данного пиксела (расположении на экране монитора), его цвете и яркости.
Векторные объекты, в отличие от растровых, задаются не набором пикселов, а набором отрезков (векторов). Например, отрезок прямой линии задается координатами её начала и её конца, окружность задается координатами центра окружности и радиусом. Сами же задаваемые фигуры прорисовывается параметрически, т.е. по формулам, с помощью специальных функций, так называемых генераторов векторов. Эти функции позволяют вычислять местоположение всех пикселов, из которых формируется изображение конкретного вектора.
Как правило, все функции генерации векторов, окружностей и других примитивов помещаются в специальных графических библиотеках. Каждый язык высокого уровня имеет в своем составе такую библиотеку. Например, в языках Pascal и C графическая библиотека находится в папке BGI. Графической библиотекой, независимой от типа операционной системы является библиотека OpenGL. Операционная система Windows имеет свою собственную графическую библиотеку - DirectX 3D, а компьютеры типа Mac снабжены библиотекой QuickDraw 3D, и т. д.
К вышесказанному необходимо добавить, что процедуры генерации векторов, окружностей и других фигур, а также различные алгоритмы отсечения, удаления, закрасок и т.п. на современных компьютерах реализуются с помощью микропроцессорных устройств аппаратной поддержки графики.
 
3.2.  Алгоритмы  генерации  векторных  примитивов
 
Экран растрового дисплея можно рассматривать как матрицу дискретных элементов (пикселов), поэтому на нем нельзя непосредственно провести отрезок линии из одной точки в другую. Здесь нужны специальные методы вычерчивания прямых и кривых линий, создающие впечатление непрерывных объектов. Процесс определения пикселов, наилучшим образом аппроксимирующих заданный отрезок, называется разложением в растр. Для горизонтальных, вертикальных и наклоненных под углом 45° отрезков выбор растровых элементов (пикселов) очевиден. При любой другой ориентации выбрать нужные пикселы труднее, что показано на рисунке 1.
Общие требования к алгоритмам рисования отрезков:
         1) отрезки должны выглядеть прямыми;
         2) отрезки должны начинаться и заканчиваться в заданных точках;
         3) яркость отрезка должна быть постоянной и не зависеть от длины и наклона;
         4) рисовать нужно быстро.
 

 
Рисунок 1 - Растровое представление различных векторов
 
Не все из перечисленных критериев могут быть полностью выполнены. Сама природа растрового дисплея исключает возможность генерации абсолютно прямых линий (кроме ряда специальных случаев), равно как и точное совпадение начала и конца отрезка с заданными точками. Тем не менее, при достаточно высоком разрешении дисплея можно получить приемлемую аппроксимацию.
Постоянная вдоль всего отрезка яркость достигается лишь при проведении горизонтальных, вертикальных и наклоненных под углом 45 ° прямых. Для всех других ориентаций разложение в растр приведет к неравномерной яркости. Так как расстояние между соседними пикселами для отрезка под углом 45° больше, чем для вертикальных и горизонтальных прямых, то вертикальные и горизонтальные отрезки будут выглядеть ярче, чем наклонные. Обеспечение одинаковой яркости вдоль отрезков разных длин и ориентации достигается различными специальными алгоритмами.
Существует множество методов генерирования векторов и окружностей. К самым распространенным относятся:
- простой алгоритм ЦДА – цифровой дифференциальный анализатор (DDA, Digital Differential Analyzer) для генерации векторов;
- несимметричный алгоритм ЦДА для генерации векторов;
- алгоритм Брезенхема для генерации векторов;
- алгоритм Кастла-Питвея;
- алгоритм Брезенхема для генерации окружности.
Выше было отмечено, что растровая генерация отрезков имеет следующие недостатки:
- неточное расположение начала и конца;
- ступенчатый вид отрезка;
- яркость зависит от наклона и длины.
Наиболее заметно ухудшает качество изображения ступенчатость. Перечислим некоторые алгоритмы генерации, в которых предприняты меры по борьбе со ступенчатостью:
- модифицированный алгоритм Брезенхема;
- алгоритм Брезенхема для генерации ребер заполненного многоугольника с уменьшением ступенчатости;
- алгоритм Флойда-Стейнберга;
- алгоритмы "размывания" границы вектора с использованием фильтрации изображения.
 
Рассмотрим более подробно работу некоторых из перечисленных алгоритмов [13].
 
Простой ЦДА
Алгоритмы ЦДА выполняют разложение отрезка в растр путем решения дифференциального уравнения вида:

 
Где:   xH и yH - координаты начала отрезка;
xK и yK - координаты конца отрезка.
Графически такое разложение можно представить на рисунке 2:
 

 
Рисунок 2 - Дифференцирование отрезка
 
Решением уравнения (1) является:              xo = xH  ;          xi+1 = xi + ∆x/N
 
yo = yH  ;          yi+1 = yi + ∆y/N
 
где:  xi  и  yi - начальные значения координат для очередного шага вдоль отрезка;
         N - количество узлов, используемых для аппроксимации отрезка.
 
Алгоритм простого ЦДА имеет следующую последовательность:
1) Определяется количество узлов N, используемых для аппроксимации отрезка.
2) Затем за N циклов вычисляются координаты очередных узлов:
3) Получаемые значения  xi, и yi  преобразуются в целочисленные значения координаты очередного подсвечиваемого пиксела либо округлением, либо отбрасыванием дробной части.
Пример работы простого ЦДА представлен на рисунке 3:
 

 
Рисунок 3 - Результаты работы простого ЦДА
 
Недостаток этого метода заключается в том, что точки могут прописываться дважды, что увеличивает время построения отрезка. Кроме того, из-за независимого вычисления обеих координат нет предпочтительных направлений, и построенные отрезки кажутся не очень красивыми.
В [13] приводится алгоритм простого ЦДА, работающий во всех квадрантах декартовой системы координат.
 
Несимметричный ЦДА
В несимметричном ЦДА количество узлов аппроксимации N берется равным числу пикселов вдоль наибольшего приращения, т. е. либо x, либо y (большее из приращений) выбирается в качестве единицы растра.
Обозначим: x как Px   и y как Py, тогда для Px > Py (при Px, Py > 0), можно принять что:
   - координата по X-направлению должна увеличиться на единицу, Px раз;
   - координата по Y-направлению должна увеличиться на Py/Px, также Px раз.
Для генерации отрезка из точки (x1, y1)  в точку (x2, y2) в первом октанте (Px³Py³0) алгоритм несимметричного ЦДА имеет вид:
1) количество узлов аппроксимации берется равным числу пикселов вдоль наибольшего приращения;
2) Вычисляются приращения координат:      Px = x2 - x1;
                                                                                 Py = y2 - y1;
3) Рисуется начальная точка отрезка:             PutPixel (x1, y1);
4) Генерируется весь отрезок:                          while (x1 < x2)
   { x1 = x1 + 1.0;
      y1 = y1 + Py/Px;
      PutPixel (x1, y1);
   }
 
Пример генерации отрезка по алгоритму несимметричного ЦДА приведен на рисунке 4.
 


Рисунок 4 - Генерация отрезка несимметричным ЦДА
 
К недостаткам несимметричного ЦДА относится наличие операции дифференцирования (деления) приращений текущих координат отрезка.
 
Алгоритм Брезенхема
Брезенхем предложил алгоритм, не требующий деления, как в алгоритме несимметричного ЦДА, но обеспечивающий минимизацию отклонения сгенерированного образа от истинного отрезка, как в алгоритме обычного ЦДА.
Основная идея алгоритма состоит в том, что если угловой коэффициент прямой <1/2, то точку, следующую за точкой (0,0), поставить в позицию (1,0) (рис. 5а), а если угловой коэффициент > 1/2, то - в позицию (1,1) (рис. 5б). Для принятия решения, куда заносить очередной пиксел вводится величина отклонения Е точной позиции от середины между двумя возможными растровыми точками в направлении наименьшей относительной координаты. Знак Е используется как критерий для выбора ближайшей растровой точки.
 

 
Рисунок 5 - Алгоритм Брезенхема генерации отрезков
 
Если Е < 0, то точное Y-значение округляется до последнего меньшего целочисленного значения Y, т.е. Y-координата не меняется по сравнению с предыдущей точкой.
Если Е > 0, то Y увеличивается на 1.
Для вычисления Е  без ограничения общности примем, что рассматриваемый вектор начинается в точке (0,0)  и проходит через точку (4, 1.5) (см. рис. 5в), т.е. имеет положительный наклон меньший 1.
Поясним построение вектора на каждом шаге:
1) Из рисунка 5в видно, что отклонение для первого шага:                           Е1 = Py/Px - 1/2 < 0,
Поэтому для занесения пиксела выбирается точка (1,0).
2) Отклонение для второго шага вычисляется добавлением приращения
Y-координаты для следующей X-позиции (см. рис.5в):                             Е2 = Е1 + Py/Px > 0,
Поэтому для занесения пиксела выбирается точка (2,1).
3) Так как отклонение считается от Y-координаты, которая теперь
увеличилась на 1, то из накопленного отклонения для вычисления
последующих отклонений надо вычесть 1:                                                  Е2 = Е2 – 1
4) Отклонение для третьего шага:                                                                        Е3 = Е2 + Py/Px < 0
Поэтому для занесения пиксела выбирается точка (3,1).
5) Суммируя и обозначая большими буквами растровые точки,
а маленькими - точки вектора, получаем:                                                   Е1 = y1 - 1/2 = dY/dX - ½
 
Возможны случаи:
 
Е1 > 0                                      E1 ≤ 0
 
Тогда ближайшая точка есть:
 
X1 = X0 + 1                            X1 = X0 + 1
Y1 = Y0 + 1                             Y1 = Y0
 
E2 = Е1 + Py/Px – 1               E2 = E1 + Py/Px
 
Так как нас интересует только знак Е, то можно избавиться от неудобных частных умножением E на 2×Px:
E1 = 2×Py - Px
 
E1 > 0                 E2 = E1 + 2×(Py - Px)
 
E1 ≤ 0                 E2 = E1 + 2×Py
 
Таким образом, получается алгоритм, в котором используются только целые числа, сложение, вычитание и сдвиг:

X= x1;
Y= y1;
Px= x2 - x1;
Py= y2 - y1;
E= 2*Py - Px;
i= Px;
PutPixel(X, Y);                                          /* Первая точка вектора */
while (i= i- 1 ³ 0)
{if (E ³ 0)
{ X= X + 1;
   Y= Y + 1;
   E= E + 2*(Py - Px);
}
else X = X + 1;
E= E + 2*Py;
PutPixel(X, Y);                                /* Очередная точка вектора */
}

      Этот алгоритм пригоден для случая   0 £ dY £ dX. Для других случаев алгоритм строится аналогичным образом.
На рисунке 6 приведен пример генерации по алгоритму Брезенхема того же самого отрезка, что и показанного на рисунке 4 для генерации по алгоритму несимметричного ЦДА. Из сравнения рисунков видно, что результаты различны.
 

 
Рисунок 6 - Генерация отрезка по алгоритму Брезенхема
 
Алгоритм Брезенхема генерации окружности
Рассмотрим генерацию 1/8 окружности, центр которой лежит в начале координат. Остальные части окружности могут быть получены последовательными отражениями (использованием симметрии точек на окружности относительно центра и осей координат).
Окружность с центром в начале координат описывается уравнением:
 
X2 + Y2 = R2
 
Алгоритм Брезенхема пошагово генерирует очередные точки окружности, выбирая на каждом шаге для занесения пиксела точку растра Pi(Xi, Yi), ближайшую к истинной окружности, так чтобы ошибка была минимальной:
Ei(Pi)  = (Xi2 + Yi2) - R2
 
Также как и в алгоритме Брезенхема для генерации отрезков, выбор ближайшей точки выполняется с помощью анализа знака управляющей переменной.
Рассмотрим генерацию 1/8 окружности по часовой стрелке, начиная от точки X=0, Y=R.
Проанализируем возможные варианты занесения i+1-й точки, после занесения i-й.


Рисунок 7 - Варианты расположения очередного пиксела окружности
 
При генерации окружности по часовой стрелке после занесения точки (Xi, Yi) следующая точка может быть (см. рис. 7а) либо Pg = (Xi+1, Yi) - перемещение по горизонтали, либо Pd = (Xi+1, Yi-1) - перемещение по диагонали, либо Pv = (Xi, Yi-1) - перемещение по вертикали.
Для этих возможных точек вычислим и сравним абсолютные значения разностей квадратов расстояний от центра окружности до точки и окружности:
 
|Dg| = | (X+1)2 + Y2       - R2 |
|Dd| = | (X+1)2 + (Y-1)2 - R2 |
|Dv| = | X2        + (Y-1)2 - R2 |
 
Выбирается и заносится та точка, для которой это значение минимально.
Выбор способа расчета определяется по значению Dd. Если Dd < 0, то диагональная точка внутри окружности. Это варианты 1-3 (см. рис.7б). Если Dd > 0, то диагональная точка вне окружности. Это варианты 5-7. И, наконец, если Dd = 0, то диагональная точка лежит точно на окружности. Это вариант 4. Рассмотрим случаи различных значений Dd для приведенной последовательности.
 
Случай Dd < 0
Здесь в качестве следующего пиксела могут быть выбраны или горизонтальный Pg или диагональный Pd. Для определения того, какой пиксел выбрать: Pg или Pd, составим разность:
 
di = |Dg| - |Dd| = |(X+1)2 + Y2 - R2| - |(X+1)2 + (Y-1)2 - R2|
 
И будем выбирать точку Pg при di ≤ 0, в противном случае выберем Pd.
Рассмотрим вычисление di для разных вариантов из рисунка 7б.
 
Для вариантов 2 и 3:
Dg ≥ 0 и Dd < 0, так как горизонтальный пиксел либо вне, либо на окружности, а диагональный внутри, тогда:
di = (X+1)2 + Y2 - R2 + (X+1)2 + (Y-1)2 - R2
 
Добавляя и вычитая (Y-1)2 получим:            di = 2·[(X+1)2 + (Y-1)2 - R2] + 2·Y - 1
 
В квадратных скобках стоит Dd, так что:     di = 2 (Dd + Y) – 1
 
Для варианта 1:
Ясно, что должен быть выбран горизонтальный пиксел Pg. Проверка компонент di показывает, что Dg < 0 и Dd < 0, причем di < 0, так как диагональная точка больше удалена от окружности, т.е. по критерию di < 0 как и в предыдущих случаях следует выбрать горизонтальный пиксел Pg, что верно.
 
Случай Dd > 0
Здесь в качестве следующего пиксела могут быть выбраны или диагональный Pd или вертикальный Pv. Для определения того, какую пиксел выбрать Pd или Pv составим разность:
 
si = |Dd| - |Dv| = |(X+1)2 + (Y-1)2 - R2| - |X2 + (Y-1)2 - R2|
 
Если si ≤ 0, то расстояние до вертикальной точки больше и надо выбирать диагональный пиксел Pd, если же si > 0, то выбираем вертикальный пиксел Pv.
Рассмотрим вычисление si для разных вариантов.
 
Для вариантов 5 и 6:
Dd > 0 и Dv ≤ 0, так как диагональный пиксел вне, а вертикальный либо вне либо на окружности:
 
si = (X+1)2 + (Y-1)2 - R2 + X2 + (Y-1)2 - R2
 
Добавляя и вычитая (X+1)2 получим:          si = 2·[(X+1)2 + (Y-1)2 - R2] - 2·X - 1
 
В квадратных скобках стоит Dd, так что:    si = 2 (Dd - X) - 1
 
Для варианта 7:
Ясно, что должен быть выбран вертикальный пиксел Pv. Проверка компонент si показывает, что Dd > 0 и Dv > 0, причем si > 0, так как диагональная точка больше удалена от окружности, т.е. по критерию si > 0 как и в предыдущих случаях следует выбрать вертикальный пиксел Pv, что соответствует выбору для вариантов 5 и 6.
 
Случай Dd = 0
Для компонент di имеем: Dg > 0 и Dd = 0, следовательно по критерию di > 0 выбираем диагональный пиксел.
С другой стороны, для компонент si имеем: Dd = 0 и Dv < 0, так что по критерию si ≤ 0 также выбираем диагональный пиксел.
Итак:
Dd < 0           di ≤ 0 - выбор горизонтального пиксела Pg
                      di > 0 - выбор диагонального пиксела Pd
 
Dd > 0           si ≤ 0 - выбор диагонального пиксела Pd
                      si > 0 - выбор вертикального пиксела Pv
 
Dd = 0                     - выбор диагонального пиксела Pd.
 
Выведем рекуррентные соотношения для вычисления Dd для (i+1)-го шага, после выполнения i-го.
1. Для горизонтального шага к Xi+1, Yi
 
Xi+1 = Xi + 1
Yi+1 = Yi
Ddi+1 = (Xi+1+1)2 + (Yi+1-1)2 - R2 = Xi+12 + 2·Xi+1 + 1 + (Yi+1-1)2 - R2 =
(Xi+1)2 + (Yi-1)2 - R2 + 2·Xi+1 + 1 = Ddi + 2·Xi+1 + 1
 
2. Для диагонального шага к Xi+1, Yi-1:
 
Xi+1 = Xi + 1
Yi+1 = Yi - 1
Ddi+1 = Ddi + 2 Xi+1 - 2 Yi+1 + 2
 
3. Для вертикального шага к Xi, Yi-1:
 
Xi+1 = Xi
Yi+1 = Yi - 1
Ddi+1 = Ddi - 2·Yi+1 + 1
 
В подпрограмме V_circle, приведенной в приложении реализуется описанный выше алгоритм, строящий дугу окружности в первой четверти. Начальная инициализация должна быть следующей:
X= 0;
Y= R;
Dd = (X+1)2 + (Y-1)2 - R2 = 1 + (R-1)2 - R2 = 2*(1 - R);
 
Пикселы в остальных четвертях можно получить отражением. Кроме того, достаточно сформировать дугу только во втором октанте, а остальные пикселы сформировать из соображений симметрии, например, с помощью отдельной функции, заносящей симметричные пикселы по часовой стрелке от исходного.

4. Структура и интерфейс проекта
 
Структура проекта при его создании в C++Builder формируется из следующих основных файлов:
- головной файл проекта  - project1.cpp;
- файл опций проекта       - project1.bpr;
- файл ресурсов проекта - project1.rez;
- файлы модуля главной формы Form1:
             - заголовочный файл  - Unit1.h;
             - файл реализации       - Unit1.cpp;
             - файл формы               - Unit1.dfm.
Это те файлы, которые подлежат обязательному переносу, если надо продолжить работу над проектом на другом компьютере. Все остальные файлы разрабатываемого проекта (их более 15) среда C++Builder создает автоматически в процессе компиляции и отладки проекта.
Главной частью проекта является головной файл project1.cpp с функцией WinMain, с которой начинается выполнение программы и инициализация всех её модулей. Этот файл создается и корректируется автоматически. Чтобы просмотреть код этого файла надо выбрать: Project=>ViewSource. Имя этого файла надо всегда изменять, чтобы исключить путаницу с другими  exe-файлами аналогичных проектов.
Основной файл, с которым работает программист – это файл реализации модуля Unit1.cpp, в котором хранится программный код, обеспечивающий непосредственную работу данного модуля проекта.
В заголовочном файле Unit1.h хранится объявление класса формы данного модуля. Его основной текст формируется автоматически, за исключением объявлений функций, типов и переменных, которые вносит сам программист при необходимости.
В файле Unit1.dfm хранится информация о внешнем виде формы модуля, её размерах, местоположении и т.п. Он также создается автоматически.
Дополнительно к этим файлам в нашем проекте необходимо присоединить еще шесть модулей: Form2, Form3, Form4, Form5, Form6, Form7, предназначенных для реализации в них лабораторных работ  lab1, lab2, lab3…..lab6. Подключение каждого дополнительного модуля ведет к добавлению к проекту по три новых файла: Unit2.cppUnit2.h, Unit2.dfm (и т.д.).
Для того, чтобы в exe-файле проекта сохранялся полный набор библиотечных функций (что позволит запускать проект на любой машине) установите следующие настройки в C++Builder:
- Project=>Options=>Linker=>Use dynamic RTL  – выключить "птичку";
- Project=> Options=>Packages=>Build with runtime packages  – выключить "птичку".
После этого надо сохранить и перекомпилировать проект. Объем нового exe-файла будет в 9 раз больше исходного, без библиотек. Не устанавливайте эти настройки до полной отладки программы.


 
Рисунок 8 – Окно главной формы проекта Form1
 
На рисунке 8 изображена главная форма проекта Form1, которая должна быть создана в данной лабораторной работе. Эта форма выполняет функции титульного листа для всего цикла лабораторных работ и, кроме этого служит для запуска конкретной работы. При нажатии на соответствующие кнопки, расположенные на ней, должны запускаться дополнительные формы  Form2, Form3 … Form7.
 
На рисунке 9 изображено окно второй формы Form2, которую надо также создать в данной лабораторной работе. В графическом окне этой формы для примера изображена структурная схема рекурсивного цифрового фильтра. Прорисовка выполнена первым способом, с использованием команд графической библиотеки пакета C++Builder.


 
Рисунок 9 – Окно дополнительной формы проекта Form2. Пример изображения структурной схемы с использованием функций графической библиотеки пакета C++Builder.


 
Рисунок 10 – Окно дополнительной формы проекта Form2. Пример изображения трех линий и окружности с использованием алгоритмов генерации линий ЦДА и Брезенхема.
 
На рисунке 10 показано то же самое окно Form2, после переключения радиокнопок во второе положение. Здесь изображены, в качестве примера, три линии, проведенные под разными углами, и окружность с использованием алгоритмов несимметричного ЦДА (красного цвета) и Брезенхема (синего цвета).
В графическом окне Form2 студентам необходимо изобразить свою, соответствующую варианту задания схему, двумя разными способами.

5. Структура программы
 
Согласно заданным требованиям, работа программы будет заключаться в формировании трех режимов (состояний):
1) исходного, при котором область графического окна представляет пустое белое поле, а также после нажатия на кнопку "Очистка";
2) вывод изображения с использованием стандартных функций пакета С++ Builder;
3) вывод изображения с использованием алгоритмов генерации векторов и окружностей.
Переходы из одного состояния в другое должны осуществляться кнопкой "Пуск", в паре с радиокнопками RadioButton, или кнопкой "Очистка".
Изобразим граф переходов для этих состояний:


Рисунок 11 – Граф переходов состояний программы

 
В данной программе нет анимации, поэтому и нет надобности создавать функцию графического конвейера. Конвейер – это совокупность функций пересчета координат, нормировки и прорисовки, если не вдаваться в частности. А у нас здесь необходимо выполнить только прорисовку схемы, поэтому, поместим функции прорисовки изображений непосредственно в обработчик графического окна PaintBox1.
Другое упрощение, на которое можно здесь пойти: мы не будем создавать локальный конечный автомат, согласно рис.11, а  для выполнения переходов из одного состояния в другое  – применим пару условных операторов if. Так как активных управляющих элементов (кнопок) всего два, такое решение не трудно будет реализовать. В то время как в более сложных программах потребуется обязательно  разрабатывать функцию локального КА, с использованием в ней оператора  case.
Радиокнопки, используемые здесь, работают в пассивном режиме («только чтение»), поэтому для них создавать обработчики не надо.
С учетом вышесказанного структурная схема программы будет иметь следующий вид:
 

Рисунок 12 – Структурная схема программы


Из схемы видно, что обработчики событий для кнопок "Пуск" и "Очистка" работают в режиме обратных связей, используемых в системах автоматического регулирования. В их обязанности входит ожидание события нажатия на кнопку, и затем запуск команд, прописанных в теле этих обработчиков, т.е. конкретно для данной программы – команд присваивания переменной pysk необходимых значений и вызова функции перерисовки графического окна Repaint().
Блоки «Опрос элементов управления», «Событие "Пуск"» и «Событие "Очистка"» формируются самой средой разработки, автоматически, и приведены на схеме только для улучшения понимания работы модуля Form2 в режиме КА.
Результатом работы программы, созданной по данной схеме, будут две одинаковые, неподвижные картинки, согласно варианту задания, прорисовываемые различными способами, в зависимости от положения радиокнопок. Или же вывод пустого белого окна, либо сразу после запуска, либо по команде очистки изображения.
Для закрытия модуля или всей программы будем использовать стандартный способ, представляемый средой разработки – щелчок указателем мышки по крестику, расположенному в верхнем правом углу окна проекта.
Из схемы на рисунке 12 видно, какие функции должен содержать файл реализации Unit1.cpp:
- стандартный обработчик графического окна PaintBox1;
- стандартный обработчик Button1 для кнопки "Пуск";
- стандартный обработчик Button2 для кнопки "Очистка";
- функцию ShowC() прорисовки схемы с использованием стандартных команд пакета С++Builder;
- функцию ShowGenVek() прорисовки этой же схемы с использованием алгоритмов генерации векторов и окружностей;
- функцию V_DDA(xn, yn, xk, yk) - алгоритм ЦДА;
- функцию V_Brezen(xn, yn, xk, yk) - алгоритм Брезенхема;
- функциюV_BRcirc (int xc, int yc, int r) - алгоритм генерации окружности.
 
Студенты выбирают либо функцию с алгоритмом V_DDA() (простым или несимметричным), либо функцию V_Brezen(), это зависит от варианта задания.
 
Ввиду того, что программа данной лабораторной работы достаточно проста, её структурную схему можно сформировать в еще более упрощенном варианте, чем та, что изображена на рисунке 12. Все команды, выполняющие такие действия как забеливание поля графического окна и прорисовку изображения в нем, можно поместить непосредственно в обработчик кнопки "Пуск", или в аналогичные ей компоненты. Именно так поступают неопытные, начинающие студенты-программисты. При таком подходе нет надобности выполнять проверки на то, включены или нет запускающие кнопки. А заодно не потребуется утруждать себя такими понятиями как «граф переходов», «состояния конечного автомата», «звенья обратных связей» и т.п.
Все, кто намерен создавать надежные, грамотные интерактивные программы должны с самого начала обучения освоить определенный стиль в построении структуры программ, при котором должно выполняться четкое разделение обязанностей между объектом управления (графическим окном) и обработчиками управляющих воздействий (кнопками и др.), не допуская вышеуказанных необоснованных упрощений.
Суть этого разделения заключается в том, что обработчики управляющих воздействий выполняют только подготовительную работу по обработке различных параметров и координат, а объект управления обеспечивает весь процесс анимационного преобразования координат и прорисовки фигур.

 
6. СОЗДАНИЕ ПРОЕКТА И ЕГО ФОРМ
 
6.1.  Создание главной формы проекта - Form1
 
Процесс создания проекта и его главной формы включает в себя следующие операции:
 
1) Запуск C++Builder.  Выполните:   Пуск => Программы => Borland C++Builder 6.
На экране откроются четыре окна:
- главное окно, содержащее меню команд, панель инструментов, палитру компонентов;
- окно главной формы Form1, являющееся макетом (конструктором) окна проекта;
- окно Инспектора объектов (Object Inspector), служащее для просмотра и изменения свойств объектов;
- окно редактора кода (модуль главной формы с текстом программы Unit1.cpp).
 
2) Создание проекта.  Выполните:   File => New => Application.
(Этот пункт можно не выполнять, так как сразу после запуска C++Builder форма Form1 создается автоматически, по умолчанию).
 
3) Сохранение проекта в отдельной папке.  Выполните:  File => Save project As…
По умолчанию программа предложит сохранить файл программного кода главного модуля (файла реализации) Unit1.cpp в папке Projects. Не надо соглашаться с этим. Сначала надо создать свою папку под именем, например, "Иванов" (внутри папки Projects), и только после этого сохранить в ней Unit1.cpp. Затем программа предложит сохранить файл опций проекта project1.bpr под этим же именем. С этим также не надо соглашаться, а надо переименовать этот файл на другой, например, Ivanov2D.bpr и сохранить его в той же папке "Иванов". После этого автоматически поменяются имена головного файла проекта на Ivanov2D.cpp и файла ресурсов на Ivanov2D.rez. Переименования проектов необходимы для того, чтобы не смешивать разные проекты, в случае их сохранения под исходными именами.
 
4) Компиляция и запуск проекта на исполнение. Выполните:   Run => Run.
На экране откроется окно, демонстрирующее процесс компиляции. После завершения компиляции и при условии, что ошибок при создании проекта не было выявлено, откроется рабочее окно проекта, его главной формы Form1. Внешне – это обычное окно windows–подобной программы, со всеми атрибутами закрытия, свертывания и развертывания окна. Пока что поле этого окна чистое, без кнопок, движков, и картинок. Все необходимые компоненты управления мы установим далее, при компоновке формы. Нет на нем и сетки, которая нанесена на поверхность формы Form1, когда она открыта в режиме конструктора. По этому признаку всегда можно отличить, что перед нами открыто, конструктор формы или её проект.
Далее, когда мы убедились в том, что проект создан корректно, можно переходить к компоновке формы. Для этого главное окно проекта, модуль Form1, необходимо закрыть, нажав на крестик в верхнем правом углу окна, после чего откроется эта же самая форма Form1, но в режиме конструктора. Если вы каким-то образом случайно закрыли форму в режиме конструктора, то для доступа к ней выберите: View => Forms… В открывшемся окне View Form выберите Form1.
 
6.2.  Компоновка главной формы Form1
 
Главная форма Form1 создается автоматически при создании нового проекта, но при этом её поле – пустое. В задачу программиста входит заполнение поля формы необходимыми элементами интерфейса проекта: окнами ввода данных, кнопками и движками управления, окнами вывода результатов и т.п. Среда визуального проектирования C++Builder предоставляет программистам возможность использовать готовые компоненты управления, которые можно устанавливать на форме проекта методом перетаскивания (Drag and Drop). В данном проекте на главной форме необходимо установить надписи разного рода, панель управления и кнопки запуска лабораторных работ (см. рис.8). Порядок их установки следующий:
 
1) Нанесение титульных надписей на форму и настройка их параметров.
На вкладке Standard палитры компонентов, размещенной в верхней части главного окна среды разработки, щелкните левой кнопкой мыши по значку Label, расположенному в верхней части вкладки, после чего щелкните на поле Form1 в верхней части поля формы. Развернется прямоугольник зоны надписи, который можно растянуть указателем мыши до требуемых размеров.
В окне Object Inspector, в строке Caption (Надпись) вместо записи Label1  введите - СевНТУ.
В окне Object Inspector, в строке Font (Шрифт) щелкните по крестику, развернутся дополнительные настройки. В строке Size измените значение 8 на 10. В строке Color щелкните по черному квадратику, откроется окно с выпадающим меню. В нем выберите желаемый цвет надписи.
Нанесение остальных надписей на форму и настройка их параметров выполняются аналогично, в соответствии с рисунком 8.
 
Примечание 1: Необходимо помнить, чтобы изменять свойства какого-либо компонента, расположенного на форме через окно Object Inspector, надо предварительно выделить этот компонент (щелкнуть по нему мышкой). Иначе вы по ошибке можете изменить свойства другого компонента формы.
  
2). Установка управляющей панели на форму.
На вкладке Standard палитры компонентов щелкните по значку Panel, после чего щелкните на поле Form1 ниже надписи с фамилией студента, развернется прямоугольник панели, который можно растянуть указателем мыши до требуемых размеров;
 
3) Установка кнопок на управляющую панель.
На вкладке Standard палитры компонентов щелкните по значку Button, а затем щелкните внутри области Panel1 поля Form1, возникнет кнопка Button1, которую можно переместить указателем мыши в требуемое место панели и изменить её размеры.
В окне Object Inspector, в строке Caption вместо записи Button1  введите  Lab1.
Установка остальных кнопок  Lab2, Lab3, … Lab6  на управляющую панель и настройка их параметров выполняется аналогично.
Напротив каждой кнопки на панели управления, используя компонент Label, нанесите наименования лабораторных работ, согласно рисунку 8.
 
На этом компоновка главной формы Form1 заканчивается и для проверки работоспособности формы можно запустить проект на исполнение, выбрав на панели меню Run => Run, (F9). Сначала будет выведено окно с сообщениями компилятора, а затем, если ошибки не обнаружатся, появится окно приложения. На данном этапе проектирования главная форма представляет собой простой набор надписей и пассивных управляющих компонентов (кнопок). Процесс создания функций обработки событий ButtonClick на щелчки по кнопкам Lab1, Lab2, Lab3… Lab6 будет рассмотрен далее.
 
Примечание 2. Операцию запуска проекта на исполнение желательно делать после каждого добавления новых компонентов на форму или изменений в тексте программы файла реализации, это облегчит процесс отладки приложения.
 
Примечание 3. Если вы запустили проект на исполнение через Run => Run, а затем вам потребовалось вернуться к конструктору формы, чтобы выполнить, например, какую-либо корректировку на форме или в программном коде, то обязательно перед этим закройте развернутые окна проекта. Если вы этого не сделаете, программа начнет выводить запросы по поводу того, где сохранить изменения. Как правило, студенты слабо представляют структуру проекта и часто принимают неправильные решения по этим запросам. Поэтому, лучше сначала закрыть все окна проекта, и только после этого начинать корректировку, иначе можно столкнуться с самыми непредсказуемыми изменениями в программе. Одним из признаков того, что уже можно вносить изменения в программу, будет возвращение окна Object Inspector на свое место.
 
Примечание 4. Если работа какого-либо компонента вам не понравилась, и вы решили его удалить с поля конструктора формы с использованием команды Delete, то, в некоторых случаях, при последующей компиляции, программа требует вернуть объект назад. Чтобы устранить возникшее недоразумение, зайдите в файл Unit.h (его вкладка находится в нижней части окна редактора кода), найдите там описание прототипа удаленного компонента и удалите его вручную. После этого еще раз пересохраните и перекомпилируйте проект.
 
6.3.  Подключение  дополнительного  модуля  Form2  к  проекту
 
Согласно предлагаемой стратегии формирования структуры проекта, дополнительные модули необходимы для того, чтобы разместить в их окнах (формах) лабораторные работы №1, 2, 3…6. Вызовы каждой из шести лабораторных работ (т.е. инициализация соответствующих форм) должны осуществляться с главной формы Form1 по нажатию на одну из кнопок Lab1, Lab2, Lab3… Lab6, расположенных не ней. Последовательность операций по созданию дополнительного модуля Form2  и его подключения к Form1 приведена ниже.
 
1) Добавление нового модуля.
Выполните: File => New => Form. Появится вторая форма Form2.
 
2) Изменение заголовка формы.
В окне Object Inspector, в строке Caption вместо записи Form2  введите  Form2_Lab1.
 
3) Сохранение новой формы в папке проекта. Выполните: File => Save All.
 
4) Перенастройка параметров инициализации формы:
Для того чтобы новая форма не создавалась сразу после запуска проекта, а только после нажатия на кнопку Lab1 , выполните следующее. Выберите: Project=>Options. Откроется окно Project Options for Ivanov2D.exe. В нем, в окошке Auto-create forms будут прописаны две формы: Form1 и Form2. Выделите запись Form2 и нажмите кнопку со значком ">", после этого запись "Form2" переместится в окошко Available forms. Для завершения – нажмите "OK".
 
5) Сохранение проекта. Выполните: File => Save All.

6) Создание обработчика события на нажатие кнопки Lab1.
Перейдите на форму Form1 и дважды щелкните по кнопке Lab1 (Button1). В окне редактора кода Unit1.cpp появится заголовок новой функции:
 
void __fastcall TForm1::Button1Click(TObject *Sender){}
 
Внутри фигурных скобок этой функции необходимо вписать следующую команду инициализации формы:
Application->CreateForm(__classid(TForm2), &Form2);
 
(Ранее эта команда была прописана в головном файле проекта Ivanov2D.cpp до перенастройки параметров инициализации, см. п. 4)).
 
7) Добавление команды препроцессорной обработки.
В файле Unit1.cpp после записи  #include "Unit1.h" допишите:  #include "Unit2.h".
 
8) Сохранение всех файлов проектаFile => Save All.
 
9) Установка параметра видимости для формы Form2.
В окне Object Inspector, в строке Visible установите параметр true.
 
10) Сохранение и компиляция проекта. Выполните: File => Save All, а затем Run => Run.
 
После всех этих операций, для полной проверки выполните перезапуск всего проекта:
          сначала закройте его со всеми его формами:      (File => Close All), 
          а затем снова откройте:                                              (File => Project…=> <имя папки проекта> => <имя проекта>.bpr).
     После этого запустите проект на исполнение (Run => Run). При этом должно развернуться только окно главной формы Form1, в котором нажмите на кнопку Lab1. Только после этого должно открыться второе окно - дополнительный модуль Form2_Lab1. Пока это окно пустое, процесс его заполнения будет рассмотрен ниже.
    Аналогично создаются остальные дополнительные модули для лабораторных работ №2…6, и будет лучше, если вы сделаете это сейчас, пока весь алгоритм их создания перед вами.
 
6.4. Компоновка формы Form2
 
Форма Form2 дополнительного модуля предназначена для демонстрации на её поле структурной схемы, согласно варианту задания из таблицы 1 данной лабораторной работы.
Так как форма Form2 инициализируется только после нажатия кнопки, расположенной на Form1, то для доступа к конструктору Form2  надо выполнить: View => Forms… В открывшемся окне View Form  выберите Form2. Аналогично выполняется доступ к файлу Unit2.cpp – программному коду формы: View => Unit… .Порядок заполнения формы Form2 элементами визуализации и управления, в соответствии с рисунком 9, приведен ниже.
 
1). Установка управляющей панели на форму.
На вкладке Standard палитры компонентов щелкните по значку Panel, после чего щелкните на поле Form2 в правой части, развернется прямоугольник панели, который можно растянуть указателем мыши до требуемых размеров;
Привяжите панель Panel1 к верхнему, правому и нижнему краю формы, для чего в окне Object Inspector переустановите свойство Anchors панели в состояние [akTop, akRight, akBottom].
В окне Object Inspector, в строке Caption удалите запись Panel1.
 
2). Установка на форму панели GroupBox.
На вкладке Standard палитры компонентов щелкните по значку GroupBox, затем щелкните на поле управляющей панели Panel в верхней части, после чего разверните GroupBox до требуемых размеров.
В окне Object Inspector, в строке Caption вместо записи GroupBox1 введите "Графические функции".
 
3). Установка радиокнопок RadioButton1  и  RadioButton2 на панель GroupBox:
На вкладке Standard палитры компонентов щелкните по значку  RadioButton, а затем щелкните внутри области  GroupBox  поля Form2, возникнет радиокнопка  RadioButton1, которую можно переместить в требуемое место панели. Затем то же самое сделайте при создании  RadioButton2.
В окне Object Inspector, в строке Caption вместо записи RadioButton1 (или RadioButton2)  введите  "C++Builder" или "Алгоритмы генерации линий" соответственно.
Выделите на Form2 радиокнопку RadioButton1 и затем в окне Object Inspector, в строке Checked установите свойство true. Это необходимо для того, чтобы эта кнопка была выбрана первой по умолчанию после запуска проекта.
 
4). Установка кнопки "Пуск" на панель:
На вкладке Standard палитры компонентов щелкните по значку Button, а затем щелкните внутри области Panel1 поля Form2, возникнет кнопка Button1, которую можно переместить в требуемое место панели и изменить её размеры.
В окне Object Inspector, в строке Caption вместо записи Button1  введите:  Пуск.
 
5). Установка кнопки "Очистка" на панель. Установка выполняется аналогично п. 4).
 
6). Установка компонента PaintBox1 на форме Form2:
Компонент PaintBox (а также компоненты Image, Bitmap) обладает свойством Canvas, представляющим собой ограниченную область, на которой можно рисовать или отображать графические объекты.
Откройте вкладку System палитры компонентов и щелкните по значку PaintBox, затем щелкните на рабочем поле формы Form2. Появившийся объект растяните и разместите в левой части формы, он и будет полем графического окна, в котором можно будет помещать изображение.
 
На этом компоновка главной формы Form2 заканчивается, сохраните и запустите проект на исполнение. На поле окна Form2 проекта вы увидите все компоненты, установленные в процессе компоновки, за исключением компонента PaintBox. Этот компонент станет видимым только после того, как в состав его обработчика будут введены необходимые команды закраски поля канвы. Как это сделать будет оговорено ниже.
 
7. РАЗРАБОТКА ФАЙЛА РЕАЛИЗАЦИИ Unit2.cpp
 
7.1. Основные сведения
 
Файл реализации Unit2.cpp является основным файлом программного кода модуля Form2  и содержит описания всех функций, обеспечивающих его непосредственную работу.
Файл реализации в общем случае состоит из следующих основных частей:
1) Директивы препроцессора:
- подключение библиотеки визуальных компонентов vcl.h;
- подключение заголовочного файла главного модуля;
- дополнительные директивы препроцессора;
- макросы пользователя.
2) Описание глобальных переменных;
3) Объявления прототипов функций разработчика;
4) Объявление формыForm1;
4) Вызов конструктора формы Form1;
6) Инициализация исходных параметров;
7) Описания функций, обработки событий компонентов формы;
8) Описания функций входящих в конвейер обработки изображения;
9) Описания вспомогательных функций.
 
Исходный текст файла реализации, создаваемый средой разработки по умолчанию, имеет вид:
 
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
 
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
// макросы пользователя
//---------------------------------------------------------------------------
// описания глобальных переменных
// объявления прототипов функций разработчика
//---------------------------------------------------------------------------
TForm1 *Form1;                                                                             // объявление объекта Form1
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)                      // вызов конструктора Form1
        : TForm(Owner)
{                                                                                                      // здесь можно поместить
}                                                                                                      //  команды настройки формы
//---------------------------------------------------------------------------
// Инициализация исходных параметров
// описания функций разработчика
// описания обработчиков событий
//---------------------------------------------------------------------------
 
Здесь синим цветом обозначены те части программы, которые позже добавляет сам разработчик.
 
Алгоритм создания файла реализации будет состоять из следующих основных этапов:
      
1) Объявление переменных и функций;
      
2) Создание обработчика для графического окна, реализуемого с помощью компонента PaintBox1;
3) Создание обработчика для кнопки "Пуск";
4) Создание обработчика для кнопки "Очистка";
5) Разработка функции ShowC() для прорисовки схемы с использованием команд С++Builder;
6) Разработка функции ShowGenVek() для вызова алгоритмов генерации векторов и окружностей;
7) Разработка функции V_DDA() для прорисовки схемы с использованием алгоритма ЦДА;
8) Разработка функции V_Brezen() для прорисовки схемы с использованием алгоритма Брезенхема;
9) Разработка функции V_BRcirc() для прорисовки схемы с использованием алгоритма генерации окружности.
 
7.2. Объявление переменных и функций
 
В соответствии с рисунком 12 структура программы достаточно проста: по команде обработчика события для кнопки "Пуск" должны запускаться функции прорисовки заданной схемы: либо с использованием команд C++Builder, либо с использованием генераторов векторов и окружностей.
По команде обработчика события для кнопки "Очистка" должна запускаться стандартная функция перерисовки графического окна PaintBox1Paint, что приведет к перезабеливанию изображения.
Поэтому, в файле реализации Unit2.cpp  модуля Form2  разработаем четыре функции: ShowC(), ShowGenVek(),  V_DDA()V_Brezen() , и V_BRcirc () (порядок их разработки приведен далее), а в начальной части программы объявим их прототипы:
 
void ShowC(void);           //функция прорисовки схемы с использованием C++Builder
void ShowGenVek(void);      //функция прорисовки схемы с использованием
                            // алгоритмов генерации векторов
void V_DDA(int xn, int yn, int xk, int yk);     //функция генерации вектора ЦДА
void V_Brezen(int xn, int yn, int xk, int yk);  //функция генерации вектора Брезенх.
void V_BRcirc(int xc, int yc, int r);           //функция генерации окружности
 
Здесь каждый студент вправе выбирать свои названия функций, потому что это не стандартные функции, а функции разработчика. Но лучше этого не делать, чтобы не создавать путаницу.
Функции  V_DDA() или V_Brezen()  выбираются в зависимости от варианта задания.
 
7.3. Создание  обработчика события для  PaintBox1
 
Стандартный компонент PaintBox пакета C++Builder обладает свойством Canvas, благодаря чему на графическом поле его окна можно производить различные прорисовки. Порядок создания обработчика PaintBoxPaint следующий. На поле конструктора формы Form2 выделите компонент PaintBox1 (щелкните по нему левой кнопкой мыши). Откройте вкладку Events окна Object Inspector и щелкните по строке  OnPaint. Затем дважды щелкните по открывшемуся белому окошку.
Перейдите в окно Unit2.cpp с текстом программы. В нем появился заголовок новой функции:
 
void __fastcall TForm2::PaintBoxPaint(TObject *Sender) {}
 
Внутри фигурных скобок этой функции впишите следующие команды:
 
   W=PaintBox1->Width;
   H=PaintBox1->Height;
 
   Form2->PaintBox1->Canvas->Brush->Style=bsSolid;
   Form2->PaintBox1->Canvas->Brush->Color=clWhite;
 
   Form2->PaintBox1->Canvas->Rectangle(Rect(0,0,W,H));
 
   if (pysk==true)
        if (RadioButton1->Checked) ShowC();
        else ShowGenVek();
 
Здесь переменные W и H  задают ширину и высоту графического окна.
Объявите переменные W и H глобально, в начальной части программы, после команд препроцессорной обработки в виде:   static int W, H;
Третья и четвертая команды задают стиль и цвет закраски поля канвы (Canvas):
- Brush    - обозначает инструмент закраски – кисть;
- bsSolid   - означает сплошную закраску;
- clWhite - обеспечивает закраску окна белым цветом.
Команда Rectangle() устанавливает форму канвы (прямоугольник) и её расположение. Параметры (0,0,W,H) - это координаты верхнего левого и нижнего правого углов прямоугольника канвы, заданные в пикселях. Координаты нижнего угла преднамеренно задаются как переменные параметры W и H, значения которых связаны с размерами Width (шириной) и Height (высотой) формы Form2. Тогда, при растягивании или развертывании формы Form2 до полноэкранных размеров поле канвы также будет увеличиваться пропорционально.
Последние три строки содержат команды, обеспечивающие вызовы функций прорисовки изображений в зоне графического окна. Операторы if служат для выбора одного из трех состояний (режимов) программы, в зависимости от комбинации нажатых кнопок.
Здесь, как видим, сначала проверяется, была ли нажата кнопка Пуск, затем состояние радиокнопок RadioButton1 и RadioButton2, и, в зависимости от того, какая из них включена, вызываются соответствующие функции прорисовки схемы: ShowC() или ShowGenVek().
Переменную pysk объявите как static bool в разделе объявления глобальных переменных в начальной части Unit2.cpp.
Если вы запустите на этом этапе программу, то компилятор выведет сообщение об ошибке. Действительно, функции ShowC() и ShowGenVek()  пока еще не описаны, это будет сделано позже. А пока, чтобы можно было продолжать формировать текст программы и выполнять отладку её функций, надо временно закомментировать нерабочие элементы, т.е. заглушить вызовы этих функций в следующем виде:

   //if (pysk==true)
        //if (RadioButton1->Checked) ShowC();
        //else ShowGenVek();
 
После этого можно спокойно запускать программу на исполнение для проверки процесса компиляции. Убедитесь, что на форме Form2 в его левой части появился белый прямоугольник графического окна.
 
7.4. Создание  обработчика события FormResize
 
Эта команда обеспечит пропорциональную перерисовку графического окна и всех рисунков на его поле при развертывании формы (при условии, что размеры всех объектов связаны с размерами Width и Height самой формы). Перерисовку можно сделать разными способами, например: перезапустить функцию PaintBox1Paint(TObject *Sender). Для этого достаточно внутри обработчика событий FormResize  вызвать одну из стандартных функций перерисовки окна: Invalidate() или Repaint().
Для создания этого обработчика, на поле формы Form2 выделите компонент PaintBox1.
Откройте вкладку Events окна Object Inspector и щелкните по строке OnResize, затем дважды щелкните по открывшемуся белому окошку.
Перейдите в окно Unit2.cpp. В нем появилась новая функция:
 
void __fastcall TForm2::FormResize(TObject *Sender){}
 
Внутри фигурных скобок этой функции впишите команду:
 
Invalidate();
 
7.5.  Создание обработчика события для кнопки "Пуск"
 
Как уже говорилось ранее, функции ShowC() и ShowGenVek()  должны запускаться по нажатию на кнопку Button1 ("Пуск"), установленной на поле конструктора формы Form2. Для этого в тексте программы Unit2.cpp необходимо создать обработчик события OnClick для кнопки "Пуск".
Порядок его создания следующий: на форме Form2  дважды щелкните по кнопке "Пуск" (Button1). В окне редактора кода Unit2.cpp появится заголовок новой функции:
 
void __fastcall TForm2::Button1Click(TObject *Sender){}
 
Внутри фигурных скобок этой функции впишите команды инициализации переменной pysk и вызов функции перерисовки графического окна:
 
pysk=true;
Repaint();
 
Запустите программу на исполнение для проверки отсутствия ошибок.
 
7.6. Разработка функции прорисовки изображения ShowC()
 
В качестве примера приведем текст функции, которая прорисовывает структурную схему рекурсивного цифрового фильтра с использованием команд пакета C++Builder (см. рис.9). Для этого в окне редактора кода Unit2.cpp надо вписать следующее:
 
void ShowC(void)
{
Form2->PaintBox1->Canvas->Pen->Color=clBlue;      //цвет линий - синий
 
Form2->PaintBox1->Canvas->MoveTo(50,80);          //верхняя горизонтальная линия
Form2->PaintBox1->Canvas->LineTo((W-50),80);
 
Form2->PaintBox1->Canvas->TextOut(50,(80-15),"X[N]");     //входной сигнал X[N]
Form2->PaintBox1->Canvas->TextOut((W-70),(80-15),"Y[N]")  //выходн. сигнал Y[N]
 
Form2->PaintBox1->Canvas->MoveTo(100,80);          //правая вертикальная линия
Form2->PaintBox1->Canvas->LineTo(100,(320));
 
Form2->PaintBox1->Canvas->MoveTo((W-100),80);      //левая вертикальная линия
Form2->PaintBox1->Canvas->LineTo((W-100),(320));
 
for (int i=1; i<4; i++)              //цикл прорисовки повторяющихся элементов
{
 Form2->PaintBox1->Canvas->MoveTo(100,(80+(i*80)));    //левые горизонтальные
 Form2->PaintBox1->Canvas->LineTo((W/2-50),(80+(i*80)));
 Form2->PaintBox1->Canvas->LineTo(W/2,80);             //левые наклонные линии
 
 Form2->PaintBox1->Canvas->MoveTo((W-100),(80+(i*80))); //правые горизонт.
 Form2->PaintBox1->Canvas->LineTo((W/2+50),(80+(i*80)));
 Form2->PaintBox1->Canvas->LineTo(W/2,80);             //правые наклонные линии
 
                                                   //рисуем прямоугольники слева
 Form2->PaintBox1->Canvas->Rectangle(80,(30+(i*80)),120,(50+(i*80)));
 Form2->PaintBox1->Canvas->TextOut((100-5),(35+(i*80)),"T");
 
                                                   //рисуем прямоугольники справа
 Form2->PaintBox1->Canvas->Rectangle((W-120),(30+(i*80)),(W-80),(50+(i*80)));
 Form2->PaintBox1->Canvas->TextOut((W-100),(35+(i*80)),"T");
 
                                                   //инициализируем матрицу
 TPoint points1[]={TPoint(170,(80 +(i*80))),       // координат треугольника
                    TPoint(130,(60 +(i*80))),
                    TPoint(130,(100+(i*80))),
                   };
 Form2->PaintBox1->Canvas->Polygon(points1,2);    //рисуем треугольники слева
 
                                              //надписи в треугольниках слева
 Form2->PaintBox1->Canvas->TextOut(135,(75+(i*80)),"B"+IntToStr(i));
 
                                                    //инициализируем матрицу
 TPoint points2[]={TPoint((W-170),(80 +(i*80))),    // координат треугольника
                    TPoint((W-130),(60 +(i*80))),
                    TPoint((W-130),(100+(i*80))),
                   };
 Form2->PaintBox1->Canvas->Polygon(points2,2);    //рисуем треугольники справа
 
                                              //надписи в треугольниках справа
 Form2->PaintBox1->Canvas->TextOut((W-150),(75+(i*80)),"A"+IntToStr(i));
 
}                                             //цикл завершен
 
Form2->PaintBox1->Canvas->Ellipse((W/2-20),60,(W/2+20),100);      //окружность
Form2->PaintBox1->Canvas->TextOut((W/2-10),72,"SYM");   //надпись в окружности
 
Form2->PaintBox1->Canvas->Pen->Color=clBlack;   //возвр. цвет линий в исх. сост.
}
 
Здесь: - команда MoveTo(X, Y)  - устанавливает перо в заданные координаты начала линии, в пикселах;
- команда  LineTo(X, Y)  - рисует линию в заданные координаты конца линии;
- команда  Ellipse(X1, Y1, X2, Y2)        - рисует окружность в заданных координатах;
- команда  Rectangle(X1, Y1, X2, Y2)   - рисует прямоугольник в заданных координатах;
- команда Polygon(points, 2)                  - рисует многоугольник в заданной матрице координат;
- команда TextOut(X, Y, "<текст>")   выводит заданный текст в заданное место окна;
 
Отсчет координат элементов схемы производится непосредственно от начала системы координат, принятой для графического окна, т.е. от его верхнего левого угла.
Фактические параметры всех стандартных функций прорисовки линий, окружностей и других примитивов (MoveTo(X, Y), LineTo(X, Y), Ellipse(X1, Y1, X2, Y2), Rectangle(X1, Y1, X2, Y2) и т.д.), которые определяют их местоположение на поле графического окна, задаются в пикселах (тип int).
Цикл  For использован для прорисовки повторяющихся элементов схемы.
 
Здесь студенты должны написать свой набор команд прорисовки изображения, который соответствует варианту задания.
После того, как вы впишете все необходимые команды в тело функции ShowC(), сохраните проект. Раскомментируйте вызов функции ShowC() в обработчике PaintBox1Paint  и запустите программу на исполнение. В результате, после нажатия на кнопку Пуск должно появиться изображение заданной схемы.
 
Примечание 5. Иногда при внесении изменений в описания типов переменных и функций, программа не хочет запускаться на исполнение, а компилятор выводит сообщение об ошибке компиляции. В этом случае попробуйте еще раз сохранить проект и перезапустить. Такая повторная перекомпиляция довольно часто помогает, если конечно нет других ошибок в программе.
 
Примечание 6. В случае зависания программы при её отладке, выберите: Run=>Program Reset. Одним из признаков того, что программа "отвисла", будет возвращение окна Object Inspector на свое место.
 
Примечание 7. При отладке проекта может возникнуть ситуация, когда вроде бы безупречная программа не хочет выводить изображение. В этом случае ошибку можно выявить только в пошаговом режиме запуска программы. Для этого надо выполнить следующее:
1) Установите точку прерывания Breakpoint напротив требуемой строки программного кода, например с той, с которой начинается процесс вывода изображения на экран. Для установки точки прерывания щелкните мышкой на сером поле окна программного поля левее требуемой строки, после чего там появится красная точка, а сама строка окрасится в красный цвет.
2) Нажмите клавишу F9 для запуска проекта. Приложение будет выполняться самостоятельно до тех пор, пока не будет достигнута точка прерывания. Здесь программа остановится, и будет ожидать от вас нажатия на клавиши последующего пошагового выполнения команд: F7  или F8.
3) В этом состоянии программы вы можете просмотреть значения переменных, ответственных за вывод изображения на экран, например координаты вершин прорисовываемых отрезков и т.п. Для просмотра значений переменных достаточно навести указатель мыши на имя любой переменной. Рядом с ним возникнет окошко с данными, соответствующими этой переменной. Надо иметь в виду, что это можно сделать только для тех переменных, которые находятся выше красной строки пошагового выполнения команд. Это и понятно: программа не располагает реальными значениями тех переменных, которые еще не обработаны. Чтобы добраться до требуемого места в программе для проверки значений переменных, последовательно нажимайте F7  или F8.
4) Для выхода из пошагового режима отключите точку прерывания (щелкните по ней мышью) и выберите Run => Program Reset  (Ctrl-F2).
 
7.7. Разработка функции прорисовки изображения ShowGenVek()
 
Процесс разработки функции прорисовки изображения ShowGenVek() аналогичен разработке функции ShowC(), за исключением того, что вместо графических функций пакета С++Builder, надо вызывать функции генерации векторов собственноручной разработки.
В качестве примера рассмотрим работу функции прорисовки трех линии и окружности с использованием алгоритмов несимметричного ЦДА и Брезенхема.
 
void ShowGenVek(void)
{
 int xn=40, yn=160, xk=420, yk=280;       // задаем координаты 1-го вектора
 V_DDA(xn,yn,xk,yk);                      // вызов алгоритма несимм. ЦДА
 
 xn=40, yn=220, xk=420, yk=220;           // задаем координаты 2-го вектора
 V_Brezen(xn,yn,xk,yk);                   // вызов алгоритма Брезенхема
 
 xn=40, yn=260, xk=420, yk=380;           // задаем координаты 3-го вектора
 V_Brezen(xn,yn,xk,yk);                   // вызов алгоритма Брезенхема
 
 int xc=300, yc=120, r=50;                // задаем координаты окружности
 V_BRcirc (xc, yc, r);                    // вызов алгоритма рис. окружности
}
 
Как видно из текста программы, здесь сначала присваиваются значения переменным, являющимися координатами начала и конца отрезка xn, yn, xk, yk, или переменным, являющимися параметрами окружности xc, yc, r, а затем выполняется вызов функций V_DDA(), или V_Brezen(), или V_BRcirc () являющиеся собственно алгоритмами генерации линий: ЦДА или Брезенхема.
Реально, в вашем проекте, в функции ShowGenVek() должно содержаться гораздо больше команд, так как по варианту задания потребуется вызывать функцию V_Brezen() (или V_DDA()) столько раз, сколько линий будет в заданной структурной схеме. То же самое касается и окружностей.
Перед запуском для отладки не забудьте раскомментировать вызов функции ShowGenVek() в обработчике PaintBox1Paint  и наоборот, закомментировать вызовы неработающих пока функций V_DDA (или V_Brezen и V_BRcirc). При этом никакие векторы еще прорисовываться не должны.
 
7.8. Разработка функции генерации векторов несимметричного ЦДА
 
Если у вас по варианту задан несимметричный ЦДА, то для прорисовки своей структурной схемы можно использовать нижеприведенный алгоритм [13]. Здесь имеются небольшие изменения, связанные с тем, что команда попиксельной прорисовки, которая используется в исходном коде, отличается от аналогичной команды пакета C++Builder. В языке С пикселы выводятся на экран командой PutPixel(X,Y), а в пакете C++Builder – командой Canvas->Pixels[x][y].
 
void V_DDA (int xn, int yn, int xk, int yk)
{
  int dx, dy, s;
 
// Упорядочивание координат и вычисление приращений
   if (xn > xk) {
      s= xn; xn= xk; xk= s;
      s= yn; yn= yk; yk= s;
   }
   dx= xk - xn; dy= yk - yn;
 
// Занесение начальной точки вектора
   Form2->PaintBox1->Canvas->Pixels[xn][yn]=clFuchsia;
 
   if (dx==0 && dy==0) return;
 
// Вычисление количества позиций по X и Y
   dx= dx + 1; dy= dy + 1;
 
// Собственно генерация вектора
   if (dy == dx)                // Наклон == 45 градусов
      { while (xn < xk)
         { xn= xn + 1;
           Form2->PaintBox1->Canvas->Pixels[xn][yn]=clFuchsia;
         }
      }
   else if (dx > dy)            // Наклон < 45 градусов
           { s= 0;
             while (xn < xk)
              { xn= xn + 1;
                s= s + dy;
               if (s >= dx)
                   { s= s - dx;
                     yn= yn + 1;
                   }
                Form2->PaintBox1->Canvas->Pixels[xn][yn]=clFuchsia;
              }
           }
        else                    // Наклон > 45 градусов
        { s= 0;
          while (yn < yk)
           { yn= yn + 1;
             s= s + dx;
             if (s >= dy)
                { s= s - dy;
                  xn= xn + 1;
                }
             Form2->PaintBox1->Canvas->Pixels[xn][yn]=clFuchsia;
           }
        }
}
 
Здесь надо иметь ввиду, что данный алгоритм проводит линии только с наклоном от 0 до –90о. Для наклона от 0 до +90о в алгоритм необходимо внести необходимые изменения.
Сохраните проект, раскомментируйте вызов алгоритма V_DDA() в функции ShowGenVek()  и запустите на исполнение. В результате, при переключении радиокнопок во второе положение и нажатии на кнопку "Пуск", должно появиться изображение линий, соответствующее заданным координатам векторов, с заданным цветом.
 
7.9. Разработка функции генерации векторов Брезенхема
 
Тексты программ алгоритма Брезенхема, а также других алгоритмов генерации простых фигур являются общеизвестными функциями, используемыми самыми различными графическими библиотеками. Ниже приведен один из вариантов такой программы, взятый из учебника по КГ Вельтмандера П.В. [13] с небольшими изменениями. Хотя абсолютно не запрещается составить код этого алгоритма самостоятельно.
 
void V_Brezen(int xn, int yn, int xk, int yk)
{
  int dx, dy, s, sx, sy, kl, swap, incr1, incr2;
 
   // Вычисление приращений и шагов
   sx= 0;
   if ((dx= xk-xn) < 0) {dx= -dx; --sx;} else if (dx>0) ++sx;
   sy= 0;
   if ((dy= yk-yn) < 0) {dy= -dy; --sy;} else if (dy>0) ++sy;
 
   // Учет наклона
   swap= 0;
   if ((kl= dx) < (s= dy)){dx= s; dy= kl; kl= s; ++swap;}
 
   s= (incr1= 2*dy)-dx; // incr1 - констан. перевычисления разности если
                         // текущее s<0 и s - начальное значение разности
   incr2= 2*dx;          // константа для перевычисл. разности для тек. s>=0
 
   Form2->PaintBox1->Canvas->Pixels[xn][yn]=clBlue; // первый пиксел вектора
 
   while (--kl >= 0)
    { if (s >= 0)
       {if (swap) xn+= sx; else yn+= sy;
         s-= incr2;
       }
      if (swap) yn+= sy; else xn+= sx;
      s+= incr1;
      Form2->PaintBox1->Canvas->Pixels[xn][yn]=clBlue;// тек. точка вектора
    }
}
 
Сохраните проект, раскомментируйте вызов этой функции в функции ShowGenVek() и запустите на исполнение. В результате, при переключении радиокнопок во второе положение и нажатии на кнопку "Пуск" должно появиться изображение, соответствующее варианту задания.
 
7.10. Разработка функции генерации окружности по алгоритму Брезенхема
 
Эта функция используется в том случае, если в изображении схемы по варианту задания присутствует окружность. Ниже приведен пример программного кода этой функции [13].
 
void V_BRcirc(int xc, int yc, int r)
{
  int x, y, z, Dd;
   x=0; y=r; Dd=2*(1-r);
 
   while (x < y)
    { Form2->PaintBox1->Canvas->Pixels[xc+x][yc+y]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc+y][yc+x]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc+y][yc-x]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc+x][yc-y]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc-x][yc-y]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc-y][yc-x]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc-y][yc+x]=clBlue;
      Form2->PaintBox1->Canvas->Pixels[xc-x][yc+y]=clBlue;
 
      if (!Dd)            goto Pd;
      z= 2*Dd - 1;
      if (Dd > 0)
       {if (z + 2*x <= 0) goto Pd;
        else              goto Pv;
       }
      if (z + 2*y > 0)    goto Pd;
 
Pg:   ++x;
      Dd= Dd + 2*x + 1;
      continue;                     // Горизонт
Pd:   ++x;
      --y;
      Dd= Dd + 2*(x-y+1);
      continue;                     // Диагонал
Pv:   --y;
      Dd= Dd - 2*y + 1;             // Вертикал
    }
    if (x == y)
       {Form2->PaintBox1->Canvas->Pixels[xc+x][yc+y]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc+y][yc+x]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc+y][yc-x]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc+x][yc-y]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc-x][yc-y]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc-y][yc-x]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc-y][yc+x]=clBlue;
        Form2->PaintBox1->Canvas->Pixels[xc-x][yc+y]=clBlue;
       }
}
 
Сохраните проект, раскомментируйте вызов этой функции в функции ShowGenVek()  и запустите на исполнение. В графическом окне должна появиться окружность.
 
7.11. Создание  обработчика  события  для  кнопки "Очистка"
 
Назначение обработчика события для кнопки "Очистка" в том, чтобы, используя вызов функции PaintBox1Paint(TObject *Sender) с помощью команды Repaint(), выполнить перерисовку графического окна, при которой осуществляется перезабеливание поля канвы, т. е. его очистка от элементов исходного изображения. Создайте обработчик события для кнопки "Очистка" аналогично тому, как это делалось для кнопки "Пуск" и впишите в него две команды:
 
void __fastcall TForm2::Button2Click(TObject *Sender)
{
 pysk=false;
 Repaint();
}
 
Запустите программу на исполнение и убедитесь в работоспособности кнопки "Очистка".
 
На этом создание программы для лабораторной работы №1 закончено.
Сохраните исходный вариант проекта, а последующие добавления модулей в проект при разработке лабораторных работ №2…6 производите на копиях исходного проекта, чтобы при необратимых сбоях в программе не лишиться всего проекта и не переделывать его заново.
 
 
 
8. Контрольные вопросы
 
1. Что такое графические примитивы, и на какие виды они разделяются?
2. Как строится растровое изображение?
3. Как строится векторное изображение?
4. Перечислите основные файлы проекта, созданного в C++Builder.
5.  Перечислите требования к рисованию отрезков?
6.  Напишите дифференциальное уравнение разложения отрезка в растр.
7.  Перечислите известные Вам методы генерации векторов и окружностей.
8.  Перечислите известные Вам алгоритмы уменьшения ступенчатости.
9.  Напишите алгоритм простого ЦДА.
10. Напишите алгоритм симметричного ЦДА.
11. Напишите алгоритм Брезенхема для генерации векторов.
12. Напишите алгоритм Брезенхема для генерации окружностей.
13. Сравните достоинства и недостатки простого ЦДА, симметричного ЦДА и алгоритма Брезенхема для генерации векторов.
14. Что такое графический конвейер и для чего он нужен?
15. По какому основополагающему принципу должна строится структура программ, если в ней   необходимо создать интерактивный интерфейс?