1.2.7. Пример GDIDraw
Программа GDIDraw демонстрирует некоторые возможности GDI, которые не поддерживаются классом TCanvas. Выбраны только те возможности, которые поддерживаются не только в Windows NT/2000/XP, но и в 9x/ME. Окно программы показано на рис. 1.11.
В своей работе программа использует рисунок из стандартных картинок Delphi, предполагая, что эти картинки установлены в папку "С:Program FilesCommon FilesBorland SharedImages". Если у вас эти картинки установлены в другую папку, или по каким-то причинам вы хотите выбрать другой рисунок, измените обработчик события OnCreate формы так, чтобы он загружал рисунок из нужного вам файла. Загруженный рисунок сохраняется в поле FBitmap формы.
Рис. 1.11. Окно программы GDIDraw
Основная работа выполняется в обработчике события OnPaint формы. Мы здесь будем разбирать этот обработчик не целиком, а по частям в соответствии с тем, что каждая часть рисует. Начнем с надписи Delphi Kingdom в левом верхнем углу окна (листинг 1.34).
Листинг 1.34. Вывод надписи
Delphi Kingdomvar
R: TRect;
...
// Формируем регион, использующийся для отсечения.
// Формируем его только при первом вызове метода, а при
// дальнейших используем созданный ранее. Поле FRgn
// содержит дескриптор этого региона
if FRgn = 0 then
begin
Canvas.Font.Name := 'Times New Roman';
Canvas.Font.Style := [fsBold];
Canvas.Font.Height := 69;
// Начинаем рисование траектории. Все вызовы
// графических функций, находящиеся между BeginPath
// и EndPath, не будут приводить к выводу на экран.
// Вместо этого информация о том, что рисуется, будет
// сохраняться а специальном объекте GDI - траектории.
BeginPath(Canvas.Handle);
R := Rect(10, 10, 10 + FBitmap.Width, 10 + FBitmap.Height);
// Если не установить с помощью SetBkMode прозрачный
// фон, в траекторию попадут не только контуры букв,
// но и контуры содержащих их прямоугольных знакомест.
SetBkMode(Canvas.Handle, TRANSPARENT);
// Выводим текст "Delphi Kingdom", выравнивая его по
// центру по вертикали и горизонтали.
DrawText(Canvas.Handle, 'Delphi'#13#10'Kingdom', -1, R,
DT_CENTER or DT_VCENTER);
EndPath(Canvas.Handle);
// Превращаем траекторию в регион. В результате вызова
// этой функции получится регион, контуры которого
// совпадают с контурами надписи "Delphi Kingdom",
// сделанной в указанных координатах выбранным шрифтом.
FRgn := PathToRegion(Canvas.Handle);
end;
// Устанавливаем регион отсечения. Все, что не будет
// попадать в выбранный регион, при выводе будет
// игнорироваться.
SelectClipRgn(Canvas.Handle, FRgn);
// Выводим изображение. Все, что не попадает в область
// региона, отсекается. Таким образом, получаем надпись
// "Delphi Kingdom", подсвеченную выбранным изображением.
Canvas.Draw(10, 10, FBitmap);
// Отменяем отсечение по региону
SelectClipRgn(Canvas.Handle, 0);
Если присмотреться к надписи, видно, что внутренняя часть контуров букв содержит тот самый рисунок, который был загружен в обработчик OnCreate (как будто мы нарисовали этот рисунок через трафарет, имеющий форму надписи). По сути, так оно и есть, только называется это не трафарет, а регион отсечения. Регион — это специальный объект, который хранит область произвольной формы. Способы применения регионов различны (см. разд. 1.3.3), и один из них — это использование региона для отсечения графического вывода. Если установить регион отсечения для контекста устройства, то, что бы мы ни выводили потом в данный контекст, все, что лежит за пределами региона отсечения, игнорируется.
Соответственно, чтобы сделать такую надпись, нужно создать регион, совпадающий по форме с этой надписью. В GDI есть целый ряд функций для создания регионов различной формы, но вот для создания региона в форме букв функции нет. Зато GDI поддерживает другие объекты — траектории. Строго говоря, это не совсем объекты, траектория не имеет дескриптора (по крайней мере, API не предоставляет этот дескриптор программам), и в каждом контексте устройства может быть только одна траектория. Создание траектории начинается с вызова функции BeginPath, заканчивается вызовом функции EndPath. Графические функции, вызванные между BeginPath и EndPath, не выводят ничего в контекст устройства, а то, что должно быть выведено, вместо этого запоминается в траектории (которая представляет собой совокупность замкнутых кривых). С траекторией можно выполнить много полезных операций (см., например, разд. 1.3.4). В нашем случае между вызовами BeginPath и EndPath мы вызываем DrawText. формируя таким образом траекторию, состоящую из контуров букв. Затем с помощью функции PathToRegion мы создаем регион, границы которого совпадают с контурами траектории, т.е., в данном случае, регион, совпадающий по форме с надписью.
Примечание
На самом деле не все графические функции, вызванные между BeginPath и EndPath, добавляют контуры к траектории. Это зависит от версии операционной системы. Подробнее этот вопрос обсуждается в разд. 1.3.4.
В ходе работы программы регион не меняется, так что нет нужды создавать его каждый раз при обработке события OnPaint. Он создается только один раз, и его дескриптор сохраняется в поле FRgn формы для дальнейшего использования.
Все, что осталось сделать, — это установить регион отсечения с помощью функции SelectClipRgn, отобразить рисунок и убрать регион отсечения, чтобы не мешал в дальнейшем.
Теперь рассмотрим, как рисуются звезды в правом верхнем углу окна (листинг 1.35).
Листинг 1.35. Рисование звезд
var
I: Integer;
Star: array[0..4] of TPoint;
...
// Следующая группа команд рисует две звезды справа от
// надписи. Эти звезды демонстрируют использование двух
// режимов заливки: WINDING и ALTERNATE. Для простых
// фигур эти режимы дают одинаковые результаты, разница
// возникает только при закрашивании сложных фигур,
// имеющих самопересечения.
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
Canvas.Pen.Color := clRed;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clRed;
// Вычисляем координаты вершин звезды. Они помещаются
// в массив Star в следующем порядке (если первой
// считать верхнюю вершину и нумеровать остальные по
// часовой стрелке от нее): 1-3-5-2-4
for I := 0 to 4 do
begin
Star[I].X := Round(380 + 90 * Sin(0.8 * I * Pi));
Star[I].Y := Round(100 - 90 * Cos(0.8 * I * Pi));
end;
// Устанавливаем режим заливки WINDING. При
// использовании этого режима закрашивается все
// содержимое многоугольника независимо от того,
// как именно он нарисован.
SetPolyFillMode(Canvas.Handle, WINDING);
Canvas.Polygon(Star);
// Сдвигаем координаты звезды, чтобы нарисовать ее
// правее с другим режимом заливки.
for I := 0 to 4 do Inc(Star([I].X, 200);
// Устанавливаем режим заливки ALTERNATE. При
// использовании этого режима заполняются горизонтальные
// линии, лежащие между нечетной и четной сторонами
// многоугольника. В результате пятиугольник в центре
// звезды оказывается незаполненным.
SetPolyFillMode(Canvas.Handle, ALTERNATE);
Canvas.Polygon(Star);
Самое интересное здесь то, что обе звезды рисуются практически одинаково, меняется только режим заливки. Сначала с помощью простейшей тригонометрии вычисляются координаты вершин звезды, помещаются в массив Star и эта звезда рисуется с режимом заливки WINDING. При этом закрашиваются все точки, для которых выполняется условие, что луч, выпущенный из этой точки, пересекает контур многоугольника нечетное число раз, т.е. всю внутренность контура. Затем координаты вершин звезды смещаются вправо, и такая же звезда рисуется точно так же, но уже с режимом заливки ALTERNATE. В этом режиме закрашиваются только те точки, которые оказались между четной и нечетной сторонами многоугольника, и пятиугольник внутри звезды остается незакрашенным. Обратите внимание, что звезду мы здесь рисуем с помощью класса TCanvas, и только режимы заливки переключаем API-функциями.
Следующий шаг — это рисование черной прямоугольной рамки на фоне пересекающихся зеленых линий. Линии рисуются до рамки для того, чтобы показать, что центр рамки действительно остается прозрачным, а не заливается цветом фона. Сама рамка рисуется вызовом одной функции PolyPolygon, позволяющей за один раз нарисовать фигуру, ограниченную несколькими замкнутыми многоугольными контурами (листинг 1.36).