А. Григорьев - О чём не пишут в книгах по Delphi
Следующий шаг — это рисование черной прямоугольной рамки на фоне пересекающихся зеленых линий. Линии рисуются до рамки для того, чтобы показать, что центр рамки действительно остается прозрачным, а не заливается цветом фона. Сама рамка рисуется вызовом одной функции PolyPolygon, позволяющей за один раз нарисовать фигуру, ограниченную несколькими замкнутыми многоугольными контурами (листинг 1.36).
Листинг 1.36. Рисование рамки с использованием PolyPolygonconst
Pts: array[0..7] of TPoint = (
(X: 40; Y: 230), (X: 130; Y: 230),
(X: 130; Y: 320), (X: 40; Y: 320),
(X: 60; Y: 250), (X: 60; Y: 300),
(X: 110; Y: 300), (X: 110; Y: 250));
Cnt: array[0..1] of Integer = (4, 4);
...
// Следующая группа команд рисует прямоугольную рамку
Canvas.Pen.Color := clLime;
Canvas.Pen.Width := 3;
// Эти линии рисуются для того, чтобы показать, что
// центр рамки остается прозрачным.
Canvas.MoveTo(30, 220);
Canvas.LineTo(140, 330);
Canvas.MoveTo(140, 220);
Canvas.LineTo(30, 330);
Canvas.Pen.Color := clBlack;
Canvas.Brush.Color := clBlack;
// Функция PolyPolygon позволяет нарисовать несколько
// многоугольников одной командой. Второй параметр
// задает координат всех многоугольников, третий
// параметр задает массив, содержащий число вершин
// каждого из многоугольников. В нашем случае массив
// Cnt имеет значение (4, 4). Это значит, что первые
// четыре элемента массива PCs задают координаты первого
// многоугольника, следующие четыре - второго. Отметим,
// что указатели на массивы приходится передавать не
// очень простым способом: сначала нужно получить
// указатель на массив с помощью оператора @, а потом
// этот указатель разыменовать. Формальные параметры,
// определяющие указатели на массив, при импорте функции
// PolyPolygon в модуле Windows.dcu объявлены как
// нетипизированные параметры-переменные, поэтому
// компилятор не разрешает просто передать Pts и Cnt в
// качестве фактических параметров - он запрещает
// использовать константы там, где требуются переменные.
// Это не совсем корректно, т.к. локальная
// типизированная константа - это на самом деле не
// константа, а глобальная переменная с локальной
// областью видимости. Тем не менее компилятор имеет
// такую особенность, которую приходится учитывать.
// В данном примере функция PolyPolygon используется для
// рисования двух квадратов, один из которых целиком
// лежит внутри другого. При этом содержимое внутреннего
// квадрата остается незаполненным. Обратите внимание,
// что квадраты рисуются в разных направлениях: внешний
// по часовой стрелке, внутренний - против. Если
// установлен режим заполнения ALTERNATE, это никак не
// влияет на результат, но если установить режим WINDING,
// внутренний квадрат не будет закрашен только в том
// случае, если квадраты рисуются в противоположных
// направлениях.
PolyPolygon(Canvas.Handle, (@Pts)^, (@Cnt)^, 2);
Вся хитрость в этом коде — как передать параметры в функцию PolyPolygon. Ее второй параметр — это указатель на массив элементов TPoint, содержащий координаты вершин всех контуров в массиве: сначала все вершины первого контура в нужном порядке, затем — все вершины второго контура и т.д. Третий параметр — это указатель на массив, содержащий число точек в каждом контуре: первый элемент массива содержит число точек в первом контуре, второй — во втором и т.д. Общее число контуров определяется четвёртым, последним параметром функции PolyPolygon. Число элементов во втором массиве должно быть равно значению четвертого параметра, a число элементов в первом массиве — сумме значений элементов второго массива. За выполнением этих требований должен следить сам программист, если он ошибется, функция может обратиться к памяти, лежащей за пределами массивов, и последствия будут непредсказуемыми.
В оригинале параметры-массивы функции PolyPolygon объявлены как указатели на типы элементов массива. В модуле Windows при импорте этой функции, как это часто бывает в подобных случаях, эти параметры стали нетипизированными параметрами-переменными. В нашем случае массивы объявлены как локальные типизированные константы. По сути, в этом случае они являются глобальными переменными с локальной областью видимости, т.е., как обычные глобальные переменные, хранятся в сегменте данных и существуют на протяжении всего времени работы программы, но компилятор разрешает использовать их только внутри той процедуры, в которой они объявлены. И, несмотря на то, что по сути такие "константы" являются переменными, компилятор их рассматривает как константы и запрещает подставлять там, где требуются параметры-переменные. Поэтому приходится "обманывать" компилятор, получая указатель на эти константы, а затем разыменовывая его. Если бы наши массивы хранились в обычных переменных, нужды прибегать к такому приему не было бы.
Нетрудно убедиться, что первые четыре элемента массива Pts содержат координаты вершин внешнего квадрата рамки, последние четыре — внутреннего квадрата. Массив Cnt, соответственно, содержит два элемента, оба из которых имеют значение 4. Это означает, что в нашей фигуре два замкнутых контура, и оба содержат по четыре вершины. Порядок следования вершин во внешнем квадрате — по часовой стрелке, во внутреннем — против. Это имеет значение, если выбран режим заливки WINDING, тогда направления обхода контуров должны быть противоположными, иначе отверстие тоже окажется закрашенным. Для режима заливки ALTERNATE направление обхода контуров не имеет значения.
Далее программа GDIDraw демонстрирует работу функции InvertRect, которая инвертирует цвета в заданной прямоугольной области контекста устройства. Для того чтобы это было нагляднее, мы сначала выведем на форму загруженный в OnCreate рисунок (только на этот раз без региона отсечения) и инвертируем область, частично пересекающуюся с областью рисунка (листинг 1.37).
Листинг 1.37. Пример использования функции InvertRect// Следующая группа команд выводит рисунок и конвертирует
// его часть
Canvas.Draw(300, 220, FBitmap);
// Функция InvertRect делает указанный прямоугольник
// "негативом".
InvertRect(Canvas.Handle, Rect(320, 240, 620, 340));
Ещё одна забавная функция GDI, которая почему-то не нашла отражения в классе TCanvas — это GrayString. Она предназначена для вывода "серого" текста, т.е. текста, который по яркости находится посредине между черным и белым. Обычно для этого просто устанавливается цвет RGB(128, 128, 128), но некоторые черно-белые устройства не поддерживают полутона (это касается, прежде всего, старых моделей принтеров) — именно на них и ориентирована функция GrayString. Она позволяет рисовать серый текст произвольным образом с помощью функции обратного вызова, но эту функцию можно не указывать, и тогда рисование осуществляется функцией TextOut. Но при этом текст выводится через промежуточную растровую картинку в памяти, что обеспечивает полупрозрачность текста, т.к. закрашиваются не все пикселы, а только половина в шахматном порядке. На черно-белых принтерах с большим разрешением это действительно выглядит как серый текст, на экране же можно получать "полупрозрачные" надписи. Пример использования функции GrayString приведен в листинге 1.38.
Листинг 1.38. Пример использования функции GrayString// Следующая группа команд выводит "полупрозрачную"
// надпись "Windows API"
Canvas.Brush.Color := clBlue;
// Функция GrayString при выводе текста закрашивает
// заданной кистью не все пикселы подряд, а в шахматном
// порядке, оставляя остальные нетронутыми. Это создает
// эффект полупрозрачности.
Canvas.Font.Name := 'Times New Roman';
Canvas.Font.Style := [fsBold];
Canvas.Font.Height := 69;
GrayString(Canvas.Handle, Canvas.Brush.Handle, nil, LPARAM(PChar('Windows API')), 0, 20, 350, 0, 0);
Обратите внимание на второй параметр — через него передается дескриптор кисти, с помощью которой будет осуществляться закраска пикселов в выводимой строке. Функция GrayString игнорирует ту кисть, которая выбрана в контексте устройства и использует свою. Здесь для простоты мы передаем ей кисть контекста устройства, но, в принципе, это могла бы быть любая другая кисть. Третий параметр — это указатель на функцию обратного вызова. В нашем случае он равен nil, что указывает на использование функции TextOut. Четвертый параметр имеет тип LPARAM и содержит произвольные данные, которые передаются функции обратного вызова. В случае использования TextOut это интерпретируется как указатель на строку, которую нужно вывести, поэтому здесь приходится возиться с приведением типов. Пятый параметр содержит длину выводимой строки. Это очень характерно для функций GDI, предназначенных для вывода текста, конец строки в них определяется не обязательно по завершающему символу #0, можно вывести только часть строки, явно задав нужное число символов. Но этот же параметр можно сделать равным нулю (как в нашем случае), и тогда длина строки определяется обычным образом — по символу #0. Прочие параметры функции определяют координаты выводимой строки.