    ЧАСТЬ VI 

    Сложный материал 

    22. Работа с графикой 
    23. Печать в Delphi 
    24. Работа с подзадачами 
    25. Программирование DLL: введение 
    26. Программирование DLL: сложный материал 
    27. Тестирование и отладка 
    28. Создание приложений для Web-сервера 
    29. Работа с интерфейсом Open Tools API 
    30. Содружество Delphi и C++ 
    31. Работа с интерфейсом Windows API 
    32. Катание на санках 

515

    Работа с графикой 

    ГЛАВА 22 

    Джо Хечт 

    В старые добрые времена DOS вы без проблем могли отображать текстовую 
информацию, а вывод графики на различные устройства считался нетривиальной 
задачей. Microsoft Windows сделала эту работу более простой, предоставив почти 
независимый от устройства интерфейс с аппаратным обеспечением. Этот интерфейс 
известен как Windows GDI, или интерфейс графического устройства (Graphic 
Device Interface). GDI состоит из графических объектов, таких как карандаши, 
или перья (реп), кисти (brush) и шрифты (font), а также графических функций 
для использования этих объектов. До появления Delphi и VCL программист должен 
был следить за созданием, использованием и удалением GDI-объектов. Часто 
некоторые из этих о бъектов забывали освободить, создавая тем самым напрасный 
перерасход ресурсов, в результате чего Windows "падала на колени". 

    Работа с объектами TCanvas и ТРеп 

    Объект TCanvas в Delphi упростил вам жизнь, вобрав в себя большинство 
GDI-объектов и графических команд рисования, которые могут понадобиться вашему 
приложению для вывода графики. Хотя и сейчас в Delphi можно программировать 
GDI непосредственно, большинство приложений может воспользоваться свойствами и 
методами объекта TPrinter для вывода графики без непосредственного обращения к GDI. 
    Объект TCanvas присущ почти всем графическим составляющим Delphi. Канва— 
основа, на которую наносится рисунок. У этой основы есть методы для выполнения 
собственно вывода рисунка и дополнительные объекты, такие как карандаши, кисти 
и шрифты, для управления атрибутами данного рисунка. 
    Наиболее важные свойства объекта TCanvas рассматриваются в следующих разделах. 

    Как работать с объектами TCanvas И ТРеп 

    Описываются использование объекта TCanvas и аспекты создания объектов ТРеп 
вашего проекта. 

    Как работать с объектами TBrush 

    Научитесь создавать кисти для вашей канвы. 
    Как работать с объектами TFont 
    Вы узнаете все об использовании шрифтов в Windows (от простейших 
назначений шрифтов до специальных шрифтов), а также о том, как профессионально 
вывести текст. 

    Как работать с объектами TImage, TPicture и TBitmap 

    Рассказывается о работе с изображениями, включая прямое манипулирование 
битами растрового изображения. 

    Графика Windows: дополнительные возможности 

    Приводятся дополнительные сведения о графике: областях, метафайлах, 
режимах отображения и потоках. 

516


    Handle 

    Handle — это дескриптор контекста устройства Windows (или DC — Device 
context), т.е. поверхности, на которой вы [ рисуете. Он может использоваться 
всякий раз, когда требуется НОС. Это свойство класса обеспечивает 
непосредственный | вызов функций Windows GDI, когда вы захотите расширить 
возможности объекта TCanvas или получить дополнительную информацию о DC канвы. 
Вот простой пример, когда использование дескриптора Handle может быть полезно. 
Провери ^7кЖШСГявля|ётсТустройство, на котором вы рисуете (цветным или монохромным). 

    I	If GetDeviceCaps(Canvas.Handle,   BITSPIXEL)    * 
    I	GetDeviceCaps(Canvas.Handle,   NUMPLANES)   >   1   then 
    l	ThisIsAColorDevice  =  True; 

    |	Другим примером использования свойства Canvas. Handle может служить его 
применение при необходимости вывать функцию GDI для рисования чего-то такого, 
что не поддерживается методами TCanvas. Иллюстрацией может служить 
^yijKjw^Wj^nd^gws_PolyP?l^gon (). PolyPolygon означает много многоугольников и 
используется для рисования одного объекта из многих многоугольников. 
Впечатляющим преимуществом функции PolyPolygon () является ее способность 
рисовать части форм прозрачно. Одним из примеров может быть использование этой 
возможности для изображения текстовых форм. Рассмотрим букву «О», которая 
нарисована как единая форма, но с использованием двух элементов. Один из них 
описывает внешнюю сторону «О», а другой — внутреннюю. Внутренняя область «О» 
должна быть изображена прозрачно; ничего подобного вы не смогли бы сделать, 
изображая эту форму как два независимых объекта и заполняя внешний объект 
черным, а внутренний — белым цветом. 

    Вот пример использования функции PolyPolygon () для изображения полого ящика. 

    procedure   DrawPolyPolygon(Canvas:   TCanvas); var 
    PtArray   :   array[0..9]   of  TPOINT; PtCounts   :   array[0..1]   of  
integer; begin 
    PtArray[0] = Point(0, 0) PtArray[1] = Point(0, 40) PtArray[2] = Point(40, 
40) PtArray[3] = Point(40, 0) PtArray[4] = Point(0, 0) PtCounts[0] := 5; 
PtArray[5] = Point (10, 10) PtArray[6] = Point(10, 30) PtArray[7] = Point(30, 
30) PtArray[8] = Point (30, 10) PtArray[9] = Point(10, 10) PtCounts[1] := 5; Windows.PolyPolygon(Canvas.Handle, 
    PtArray, 
    PtCounts 
    abend; 
    procedure TForml.ButtonlClick(Sender:   Tobject); begin 
    DrawPolyPolygon(Forml.Canvas); end; 

    Для того чтобы нанести рисунок на канву формы, была вызвана функция 
DrawPolyPolygon (), заполнившая массив точек с координатами двух 
многоугольников, которые мы решили изобразить, и массив подсчета точек. Так 
что функция Windows PolyPolygon О знает, сколько элементов в массиве точек 
соответствует каждому многоугольнику. Затем мы вызываем функцию Windows 
PolyPolygon (), передавая дескриптор канвы, массив точек, массив подсчета 
точек к количества мкагаугашккков, и рксуемк<зжж'/гщж. 

    Реп 

    Свойство Canyas_. Pen определяет, как будут нарисованы линии и границы 
формы. Для каждого карандаша можно выбрать ширину, цвет, стиль и режим 
рисования. Ширина является основной характеристикой и определяется следующим образом: 

    Pen.Width   :=   10; 


    Глава 22. Работа с графикой 517 

    Эта строка кода создает карандаш, ширина которого— 10 единиц. Если 
используется режим отображения, принятый в Delphi по умолчанию, единица равна 
одному пикселю. Для того чтобы получить линию минимальной ширины, какую только 
способно создать устройство, установите нулевую ширину карандаша. Это полезно 
делать в других режимах отображения, при которых ширина, равная единице* 
можёГЖрёводться в любой произвольный размер, определяемый самим режимом. 
Имейте в виду, что стандартные стили карандаша не позволяют масштабировать 
ширину произвольно по направлениям х и у. 
    Свойство Canvas . Color определяет цвет, который будет использовать 
карандаш. Вы можете использовать один из предопределенных цветов или назначить 
свой собственный. Особо отметим, что цвет карандаша будет соответствовать 
ближайшему чистому цвету, доступному в системе, если только карандаш не имеет 
стиля psInsideFrame и шире одного пикселя. 
    Теперь приведем три примера установки цвета карандаша: 

    Pen.Color	:=	clRed; 
    Pen.Color	:=	cllnactiveCaption; 
    Pen.Color	:=	TColor(RGB(133,24,83)); 

    В первом случае используется один из предопределенных цветов. В Delphi 
предварительно определенные цвета включают основные чистые цвета и 
определенные системные цвета Windows; таким образом, пользователь может 
выбрать установку с помощью панели управления Windows (Control Panel). Во 
втором случае используется определяемый пользователем системный цвет, чтобы 
установить для карандаша тот же цвет, который применен для надписей заголовков 
неактивных окон. В третьем случае используется RGB-цвет. RGB-цвет — смесь 
красного, зеленого и синего. Значение каждого цвета может изменяться от 0 до 
255, предоставляя вам в общей сложности 16 777 216 возможных смешанных цветов. 
Если устройство поддерживает созданные вами смешанные цвета, вы их получите. В 
противном случае цвет будет заменен ближайшим доступным чистым цветом^если 
только вы не установили стиль карандаша psInsideFrame и ширину карандаша, 
большую_«аил ницы (при этом вы получите размытый цвет, в котором узоры из 
красных, синих и зеленых точек объединены для создания иллюзии требуемого 
цвета). Вы можете получить ближайший чистый цвет, в который преобразится 
заданный, используя функцию Windows GetNearestColor (). GetNearestColor () 
получает цвета HDC и RGB, а возвращает затем ближайший к RGB цвет системной 
палитры. Вы можете вызвать эту функцию следующим образом. 

    var Acolor : TColorRef; begin 
    Acolor := GetNearestColor(Forml.Canvas.Handle, 
    RGB(133, 64, 22)); 
    Shapel.Pen.Color   :=  TColor(Acolor); end; 

    Существует несколько функций преобразования цветов в Windows GDI. 
Некоторые из наиболее полезных включают функции для получения красного, 
зеленого и синего компонентов RGB-цвета (GetRValue (), GetGValue(). GetBValue 
()) и функции преобразования цветов для установки цвета RGB по индексу 
палитры. Кроме того, в Delphi имеются полезные функции преобразования, такие 
как ColorToRgb (), йсгГользуемая"для*пЬлучения значения RGB одного из 
предопределенных цветов Delphi. 
    Способ отображения цвета карандашом можно изменить, установив для стиля 
карандаша значение psInsideFrame. Существуют и другие стили, которыми вы 
можете воспользоваться. Др^/малчанию для карандаша устанавливается стиль 
psSolid; в этом случае карандаш рисует сплошную линию. Стиль psClear похож на 
NULL_PEN в Windows (при таком стиле карандаш не оставляет пометок). Этот стиль 
может быть полезен, когда вы работаете сразу и карандашом, и кистью и хотите 
получить рисунок без контура. Другие стили включают шаблоны точек и пунктирных 
линий, как показано ниже. 

    Стиль	Описание 

    psDash	Карандаш рисует пунктирную линию 
    psDot	Карандаш рисует линию из точек 
    psDashDot	Карандаш рисует линию из штрихов и точек 
    psDashDotDot	Карандаш рисует линию, создавая шаблоны "пунктир — точка — точка" 

    Шаблоны "пунктир — точка" определяются драйвером и разрешением устройства 
вывода. Шаблон, который вы применяете на одном устройстве, может быть не похож 
на тот же шаблон на другом. Если вы хотите добиться полного соответствия, 
засучите рукава и рисуйте шаблоны самостоятельно. 
    Вернемся вновь к достоинствам стиля psInsideFrame. Этот стиль 
соответствует области внутри замкнутого контура, не включая самог6"контураГЭта 
установка очень похожа на способ отображения линий на принтерах Adobe PostScript. 

    518	Часть VI. Сложный материал 

    Свойство карандаша Mode определяет, как объединяются шаблоны 
линий_и_заливок при рисовании, и соответствует кодам Windows ROP (Raster 
Operations — операции с растром). Ниже перечислено 16 предопределенных кодов 
ROP, доступных для нерастрового графического объекта. 

    Код	Результат выполнения растровой операции 

    pmBlack	Всегда черный цвет 
    pmWhite	Всегда белый цвет 
    pmNop	Невидимый цвет 
    pmNot	Инверсный цвет экрана под линией 
    ртСору	Цвет, определенный свойством карандаша Color 
    pmNotCopy	Инверсный цвет карандаша 
    pmMergePenNot	Одновременно цвет карандаша и инверсный цвет экрана 
    pmMaskPenNot	Объединяет цвета, общие для карандаша и инверсного цвета экрана 
    pmMergeNotPen	Одновременно цвет экрана и инверсный цвет карандаша 
    pmMaskNotPen	Объединяет цвета, общие для экрана и для инверсного цвета карандаша 
    pmMerge	Одновременно цвет карандаша и цвет экрана 
    pmNotMerge	Обратный pmMerge 
    pmMask	Объединяет цвета, общие для экрана и карандаша 
    pmMaskNot	Обратный pmMask 
    pmXor	Комбинация цветов либо карандаша, либо экрана, но не обоих сразу 
    pmNotXor	Обратный pmXor 

    Ниже приведен пример использования кодов ROP. Напишем программу, которая 
позволит рисовать эллипсы. Создадим контур типа "резиновой ленты", 
показывающий пользователю пример эллипса. Благодаря выбранному режиму 
карандаша (pmNot) контур стирается там, где эллипс был нарисован в последний 
раз, и рисуется вновь, как только указатель мыши переместится по экрану. 
Контур будет виден всегда — не имеет значения, _какой цвет под контуром, 
потому что карандаш 6y^eTjpHCOBaTb цветом, инвер^сншГпо отношению к тому, на 
котором рисуется эллипс. Это, кроме всего r^gc^jerjOjjipjBOfiaeT 
бь1стро,стереть контур, рисуя поверх него второй р^з, так как при этом 
создается цвет, инверсный уже инверсному и вы возвращаетесь к первоначальному изображению.	•••.,.. 

    var 
    StartPt : TPoint; EndPt   : TPoint; Capture : bool; procedure 
TForml.FormMouseDown(Sender: Tobject; 
    Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin 
    StartPt.X := X; StartPt.Y := Y; EndPt := StartPt; 
SetCapture(Forml.Handle); Capture := True; with Canvas do begin Pen.Mode := 
pmNot; Pen.Color := clBlack; Brush.Style := bsClear; end; end; 
    procedure TForml.FormMouseMove(Sender: Tobject; 
    Shift: TShiftState; 
    X, Y: Integer); begin if (Capture) and ((EndPt.X <> X) or (EndPt.Y <> Y)) then 


    Глава 22. Работа с графикой 519 


    begin 
    Canvas.Ellipse(StartPt.X,   StartPt.Y,   EndPt.X,   EndPt.Y); 
Canvas.Ellipse(StartPt.X,   StartPt.Y,   X,   Y); EndPt.X   :=  X; EndPt.Y   
:=  Y; end; end; 
    procedure TForml.FormMouseUp(Sender:   Tobject; 
    Button:   TMouseButton; Shift:   TShiftState; X,   Y:   Integer); begin 
    ReleaseCapture; Capture   :=  False; with  Canvas   do begin 
    Ellipse(StartPt.X,   StartPt.Y,   EndPt.X,   EndPt.Y); Pen.Mode   := 
pmCopy; Pen.Color   :=  clBlue; Brush.Style   := bsSolid; Ellipse(StartPt.X,   
StartPt.Y,   X,   Y); end; end; 

    Логика программы такова: когда пользователь нажимает кнопку мыши, мы 
собираем все входные данные мыши, запоминаем ее координаты х и у в переменной 
StartPt и инициализируем переменную EndPt. Затем мы устанавливаем карандаш и 
цвет кисти так, что рисуется контур фигуры, устанавливаем режим карандаша 
pmNot, чтобы карандаш мог рисовать в цвете, инверсном цвету подложки. Теперь 
всякий раз при движении мыши мы рисуем форму дважды: один раз — на старом 
месте, чтобы стереть контур, и второй раз, чтобы показать контур в его новом 
положении. Затем мы запоминаем новое положение, сохраняя текущие координаты 
мыши в переменной EndPt. В результате, после того как пользователь отпустит 
кнопку мыши, мы стираем последний контур и рисуем фигуру в окончательном виде. 

    Изменение и копирование карандашей 

    Остерегайтесь ошибок неправильного определения графических объектов 
Delphi, подобно установке ThisPen : = That Pen. Хотя на первый взгляд кажется, 
что все нормально, это не так— вместо присваивания вы должны использовать 
метод Assign (), как показано ниже: 

    ThisPen.Assign(ThatPen); 


    Создание расширенных карандашей 

    Объект ТРеп и его свойства необходимы для быстрого программирования 
больших графических приложений. Если нужно много графических "лошадиных сил", 
свойство Handle объекта ТРеп позволяет применить любой стиль карандаша, какой 
только способна использовать Windows. Возьмем, например, "геометрические" 
карандаши, которые доступны в Win32. В отличие от косметических, 
геометрические карандаши позволяют создавать стили карандашей с 
дополнительными атрибутами, отсутствовавшими в предыдущих версиях Windows, или 
использовать доступные атрибуты объектов Delphi ТРеп. Дополнительные атрибуты 
позволяют установить способ рисования линий слитыми воедино и способ, при 
котором концы линий выделяются. Для того чтобы использовать эти новые 
возможности, вы должны в ходе выполнения программы создать Windows-карандаш и 
присвоить ссылку на только что созданный карандаш свойству Handle карандаша канвы. 
    Ниже показаны различия между косметическим и геометрическим карандашами. 

    procedure  TForml.ButtonlClick(Sender:   Tobject); var 
    Ib   :   TLogBrush; begin 
    with  Forml.Canvas  do begin 
    {   Рисуем треугольник косметическим карандашом.   } 
    Pen.Width   :=   10; 
    Pen.Color   :=  clRed; 
    MoveTo(50,   50); 
    LineTo(150,   50); 
    LineTodOO,   150); 


    520	Часть VI. Сложный материал 


    LineTo(50, 50); 
    { Рисуем треугольник геометрическим карандашом. } 
    Ib.lbStyle := BS_SOLID; 
    Ib.lbColor := RGB(255, 0, 0); 
    Ib.lbHatch := 0; 
    Pen.Handle := ExtCreatePen(PSJ3EOMETRIC or 
    PS_INSIDEFRAME or 
    PS_ENDCAP_SQUARE or 
    PS_JOIN_MITER, 10, Ib, 0, nil) ; 
    BeginPath(Handle); MoveTo(200, 50); LineTo(300, 50); LineTo(250, 150); 
LineTo(200, 50); CloseFigure (Handle) ; EndPath'fHandle) ; StrokePath(Handle); 
    Возвращаемся к косметическому карандашу. } Pen.Width := I Pen.Color := 
clBlack; end; end; 

    В этом примере на канве формы рисуется два треугольника (первый — 
нормальным карандашом с шириной 10, второй — геометрическим карандашом). Во 
время работы программы обратите внимание, что в первом треугольнике линии 
соединяются с округлением, а во втором — под углом. Округление кромок является 
методом, установленным по умолчанию, который используется Windows 
д]тя^тгандартных карандашей. Применение геометрических карандашей — 
единственный способ изменить такое поведение. Для того чтобы создать 
геометрический карандаш, во-первых, следует установить для карандаша структуру 
кисти. Геометрические карандаши позволяют использовать кисть для атрибутов 
заполнения при рисовании линии. Мы вызвали функцию Win32 ExtCreatePen (), 
передали ей параметры для карандаша и присвоили результат функции (НРеп) 
свойству канвы Pen. Handle. Нам требовался стиль, который включает в себя 
стили PS_ENDCAP_SQUARE и PS_JOIN_MITER. Эти стили требуют, чтобы рисуемые 
линии отсекались на концах и соединялись под углом. 
    Чтобы использовать геометрический карандаш, необходима многослойная 
траектория (sandwich). Траектории применяются только в 32-разрядной версии 
Windows, если, конечно, вы не используете библиотеку, которая поддерживает 
траектории в 16-разрядной версии Windows. Для того чтобы использовать 
траектории, следует, прежде чем что-либо нарисовать, вызвать 
    функцию Windows BeginPath (), а закончив описание траектории, вызвать 
функцию Windows EndPath (). Затем необходимо сделать обводку контура, заливку 
или и то, и другое. Чтобы правильно завершить рисование формы с выбранным 
стилем соединения линий, нужно вызвать функцию Windows CloseFigure (). 
    Отметим, что не все функции рисования доступны при использовании 
траектории под Windows 95, и не все методы рисования Delphi будут работать при 
использовании траекторий в любой другой 32-разрядной системе. Чтобы получить 
обновленный список функций, обратитесь к документации по функции Windows 
BeginPath (). 
    После окончания работы геометрическим карандашом сделайте карандаш канвы косметическим. 

    TBrush 

    Delphi-объект TBrush включает в себя стандартный объект Windows кисть и 
используется для заполнения сплошных форм, таких как прямоугольники и 
многоугольники. Кисти могут быть пустыми, сплошными, штриховыми и растровыми. 
Пустые кисти эквивалентны NULL_BRUSH Windows Ти позволяют "делать форму 
прозрачной, отображая только_ее контур выбранным карандашом. Сплошные кисти 
заполняют форму сплошным цветом, а штриховые"— "штриховыми шаблонами. И 
наконец, растровые кисти заполняют форму мозаикой шаблонов заданных растровых 
изображений. Как уже отмечалось, сплошные кисти имеют цвет, и установить это 
свойство так же легко, как и свойство Color карандаша: 

    Brush.Color := clRed; 
    или Brush.Color := TColor(RGB(255, 0, 0)); 


    Глава 22. Работа с графикой	521 

    Использование штриховой кисти тоже не вызывает затруднений: 
    Brush.Style   :=  bsBDiagonal; Существует шесть предопределенных стилей штриховки. 
    Стиль	Описание 
    bsHorisontal	Горизонтально заштрихованный шаблон 
    bsVertical	Вертикально заштрихованный шаблон 
    bsFDiagonal	Направленный вверх слева направо под углом 45° заштрихованный шаблон 
    bsBDiagonal	Направленный вниз слева направо под углом 45° заштрихованный шаблон 
    bsCross	Горизонтальная и вертикальная пересекающиеся штриховки 
    bsDiagCross	Перекрестная штриховка под углом 45° 
    Отметим, что подложка заштрихованного шаблона прозрачна; другими словами, 
все, расположенное под тем, что вы нарисовали, будет видно. Вы можете изменить 
это,*?станодив^вет_подложки для цвета между линиями штриховки и изменив 
прозрачный режим подложки в непрозрачный, как показано нижеТ" ' 
    procedure  TForml.FormPaint   (Sender:   Tobject); begin 
    with  Canvas  do begin 
    Brush.Style   := bsHorisontal; 
    Brush.Color   :=   clRed; 
    SetBkColor(Handle,   RGB(0,    0,   255)); 
    SetBkMode(Handle,   OPAQUE); 
    Rectangle(0, 0, Forml.Width, Forml.Height); end; end; 
    В этом примере красным цветом изображается горизонтально заштрихованный 
шаблон. Цвет подложки — синий, а режим подложки — непрозрачный (чтобы GDI 
использовал цвет подложки). Подложка формы раскрашена выбранной кистью. 
    Штриховые кисти сделаны из растровых изображений размером 8x8 пикселей, 
следовательно, утверждение, что можно сделать свою собственную кисть из 
растровых изображений, верно (вы действительно можете это сделать). 
Поэкспериментируйте с этой программой, указав реальный путь к файлу растра в 
строке метода Bitmap. LoadFromFile (). 
    procedure  TForml.FormPaint    (Sender:   Tobject); var 
    Bitmap:   TBitmap; begin 
    Bitmap   := TBitmap.Create; try 
    Bitmap.LoadFromFile('c:\Borland\Delphi2.0\Images\'+ 
    'Icons\Earthl6.bmp'); Forml.Canvas.Brush.Bitmap   := Bitmap; 
    Forml.Canvas.FillRect(Rect(0,   0,   Forml.Width,   Forml.Height)); finally 
    Forml.Canvas.Brush.Bitmap   :=  nil; Bitmap.Free; end; end; 
    В этом примере создан объект с растровым изображением и загружено 
изображение из файла. Для свойства Canvas . Brush. Bitmap установлено Bitmap, 
форма заполнена мозаикой шаблона. Сделав это, мы устанавливаем свойство кисти 
Bitmap равной nil и освобождаем объект с растровым изображением. Это 
необходимо сделать, потому что Delphi не будет освобождать ресурсы 
самостоятельно. Заметим, что в изображении будут использоваться только первые 
восемь пикселей в первых восьми строках. Особо отметим, что на мониторах с 
низким разрешением шаблон 8x8 может выглядеть больше, а при высоком 
разрешении, характерном для принтеров, превращается в кляксу. 
    Помимо этого, можно создать кисть с помощью GDI-функции Windows и 
установить ссылку вновь созданной кисти для~св"ойства 
Canvas7Bru*sh.'Handle.	•-•••——"~~ "• 

    procedure  TForml.ButtonlClick(Sender:   Tobject); begin 
    with Forml.Canvas  do begin 


    522	Часть VI. Сложный материал 


    Forml.Canvas.Brush.Handle := 
    CreateHatchBrush(HS_BDIAGONAL,RGB(255, 0, 0)); Rectangle(0, 0, 
Forml.Width, Forml.Height); end; end; 

    Хотя это и сложнее, чем простая установка стиля кисти, здесь наглядно 
показывается, как использовать одну из многих доступных в Windows функций 
создания кисти. 
    Отметим, что положение кисти выравнивается по рабочей области заданного 
окна. Если вы не удовлетворены таким выравниванием, можете изменить стартовую 
точку кисти, вызвав^HKU^oJ^iijdows SetBrushOrgEx () непосредственно перед 
установкой дескриптора. Заметьте, что 
ycTaH(3^Ka?jj?TOBO_ft,TO4j<Hj^cJHj^^iaicTH Ое1р^}^_не_гюддерж.и.вается. Чтобы 
сделать это, создайте свою собственную киспГи* присвойте ссылку на нее 
свойству Canvas . Brush. Handle. 
    Ниже приведен пример изменения стартовой точки штриховой кисти. 

    procedure  TForml.ButtonlClick(Sender:   Tobject); var 
    org   :   TPoint; begin 
    with  Forml.Canvas  do begin 
    Forml.Canvas.Brush.Handle   := 
    CreateHatchBrush(HS_BDIAGONAL,RGB(255,    0,    0)); Rectangle(0,    0,    
Forml.Width  div  2,   Forml.Height); SetBru>-ah,Q?aExjHandle,   0,   6,   
Org) ; 'ForruTcanvas.Brush.Handle   := 
    CreateHatchBrush(HS_BDIAGONAL,RGB(255,    0,    0)); Rectangle(Forml.Width 
 div  2,    0,   Forml.Width,    Forml.Height); end; end; 

    Сначала создается штриховая кисть и рисуется прямоугольник, который 
заполняет половину формы. Затем изменяется стартовая точка для кисти, и 
следует создать вторую кисть, которая будет иметь новый шаблон с заданным 
отступом. Вы можете сдвигать стартовую точку кисти в направлениях х, у^лжсимум 
на семь пикселей. Обратите внимание, что в Wi_ndows^5_uia6^oH кисти в дочернем 
окне может выравниваться по родительскому не так, как нужно. К счастью, в 
Windows NT этой проблемы не существует — система автоматически приводит в 
порядок все стартовые точки кисти. 

    TFont 

    Работа с используемыми шрифтами была, пожалуй, одной из самых сильных 
головных болей до тех пор, пока разработчики Delphi благодаря объекту TFont 
значительно не упростили выбор шрифтов. Вы можете обнаружить класс TFont 
искусно встроенным в свойства многих визуальных элементов управления и объекта 
TCanvas. Еще никогда изменение атрибутов не было таким легким, а выбор шрифтов 
с помощью диалога TFontDialog таким пустяком. 
    Ниже приведен пример использования компонента TFontDialog для выбора 
шрифта для надписи кнопки. 

    procedure TForml.ButtonlClick(Sender:   TObject); begin 
    FontDialogl.Font.Assign(Buttonl.Font); 
    if  FontDialogl.Execute  then 
    Buttonl.Font.Assign(FontDialogl.Font) ; end; 

    В первой строке свойство Font для объекта FontDialog установлено таким же, 
как.шрифт кнопки; таким образом, в диалоговом окне объекта FontDialog будет 
отображаться текущий шрифт. Если шрифт и атрибуты выбраны и пользователь 
щелкнул на кнопке ОК, а не на Cancel, для свойства Button. Font будет 
установлен шрифт, возвращенный диалогом FontDialog. 
    TFontDialog имеет несколько интересных свойств, которые описаны в табл. 22.1. 
    Заметьте, что свойство device объекта TFontDialog, подобно опции 
fdWysiwyg, определяет, какие шрифты будет показывать FontDialog: экранные, 
принтерные или_и те, и другие. К этому добавим, что ни одна из опций 
определения шрифтов принтера не работает в Delphi. Если вы хотите, чтобьГв 
диалоговом окне был список шрифтов для принтера, вызовите общее диалоговое 
окно Windows ChooseFont, а не используйте для этого механизм Delphi. 
Пожалуйста, обратите внимание на то, что, если принтер поддерживает графику, 
выбранная опция f dTrueTypeOnly будет работать всегда, не создавая проблем, 
связанных со шрифтами TrueType. 

    Глава 22. Работа с графикой	523 


    Значение	Описание 

    f dAniOnly	На экран выводятся только шрифты с набором символов ANSI 
    f dApplyButton	На диалоговой панели выводится кнопка Apply (Применить). 
Эта кнопка появляется автоматически, если назначено событие OnApply 
    f dEf f ects	В диалоговом окне отображается список цветов; кроме того, 
пользователь может выбрать такие атрибуты, как подчеркнутый или перечеркнутый текст 
    f dFixedPitchOnly	Показаны только шрифты с фиксированной шириной символа 
    fdForceFontExist	В строку редактирования можно ввести названия только 
зарегистрированных шрифтов 
    fdLimitSize	Для выбора шрифтов и размеров диалоговое окно использует 
свойства MinFontSize и  MaxFontSize 
    f dNoFaceSel	При появлении диалогового окна в комбинированном списке 
шрифтов нет автоматически выбранного шрифта 
    f dNoOEMFonts	В диалоговом окне не показаны векторные шрифты 
    f dScalabelOnly	В диалоговом окне не показаны масштабируемые шрифты 
    fdNoSimulations	В диалоговом окне не показаны стили шрифтов 
    f dNoSizeSel	В диалоговом окне, в комбинированном списке размеров, нет 
автоматического выбора размера шрифта 
    f dNoStyleSel	В диалоговом окне, в комбинированном списке стилей, нет 
автоматического выбора стиля 
    fdNoVectorFonts	Аналогично значению f dNoOEMFonts 
    f dShowHelp	В диалоговом окне имеется кнопка Help 
    f dTrueTypeOnly	в диалоговом окне показаны только шрифты TrueType 
    f dWysiwyg	В диалоговом окне показаны только шрифты, подходящие и для 
принтера, и для экрана 
    Вызвав функцию Windows GetRasterizerCaps (), как показано ниже, можно 
посмотреть, какие шрифты TrueType доступны в вашей системе. 

    procedure TForml.ButtonlClick(Sender:   Tobject); var 
    rs   :   TRasterizerStatus; begin 
    if GetRasterizerCaps(rs,   sizeof(rs))   then 
    if   ((rs.wFlags   and  TT_AVAILABLE  =  TT_AVAILABLE)   and 
    (rs.wFlags   and  TT_ENABLED  =  TT_ENABLED))    then 
ShowMessage('TrueType  is  available  on  this  system1); end; 

    Вы можете также создать свой собственный диалог выбора шрифта и заполнить 
список шрифтов, используя строковый список Screen. Fonts или Printer. Fonts, 
или оба сразу. Это делается так: 

    ListBoxl.Item  :=  Screen.Fonts; 

    Кроме того, можно предварительно установить список размеров шрифтов, из 
которого пользователь будет выбирать нужный размер. Размер шрифта измеряется в 
пунктах. Пункт— это 1/72 дюйма, что соответствует измерению квадрата "ЕМ" 
шрифта. Квадрат "ЕМ" выбрали еще тогда, когда печать выполнялась вручную и 
слова набирались из свинцовых блоков. Размер этого блока соответствовал 
квадрату, на котором размещалась заглавная буква "М"'. Этот блок чуть больше 
буквы "М", однако без учета пространства между строками и размера надстрочных 
и подстрочных выносных элементов букв, например, как у строчной буквы "р". 
Поэтому, когда вы измеряете размер шрифта в пунктах в соответствии с заглавной 
буквой, он не сов падает с выбранным вами размером. Тем не менее это 
правильный размер. 
    Когда-то для увеличения междустрочного интервала в типографиях 
использовались металлические бруски, которые прокладывали между печатными 
строками. Delphi имеет два свойства для установки размера шрифта: Font. Size и 
Font. Height. Если для установки размера шрифта вы используете свойство Font. 
Size, то шрифт масштабируется до 

    524	Часть VI. Сложный материал 

    размера, который вы установили в пунктах. Если же вы используете свойство 
Font. Height, то шрифт определяется в пикселях. Если вы определили высоту как 
положительную величину, то в расчеты будет включен междустрочный интервал. При 
отрицательном значении высоты шрифта этот отступ не будет учитываться. 
Обратите внимание, что может быть возвращено отрицательное значение для 
свойств высоты (Height) или размера (Size). Для перевода любого числа в 
положительное можно использовать функцию Abs (). 
    Если вам интересно, как установить свойство Height при измерении в 
пунктах, воспользуйтесь следующим фрагментом кода. 

    Font.SizelnPoints   :=   72; 
    Font.Height   :=  -Round((GetDeviceCaps(Forml.Canvas.Handle, 
    LOGPIXELSY)    /   72)    *   Font.SizelnPoints); 

    Он пригодится вам, если вы захотите самостоятельно создать шрифт и 
установить высоту в пунктах (нечто подобное мы сделаем чуть позже в этой 
главе). Эти и другие свойства шрифтов описаны в следующих разделах. 

    Font.Color 

    Это свойство устанавливает цвет шрифта. Отметим, что, как и для карандаша, 
цвета шрифта отображаются ближайшим чистым цветом, а цвет и режим фона влияют 
на то, как будет показан текст. При установке режима фона канвы в непрозрачный 
(opaque) используется цвет фона. Цвет фона игнорируется, если вы установили 
для фона прозрачный (transparent) режим. 
    Ниже приведен пример установки цвета и режима фона объекта TCanvas. 

    with   Forml.Canvas   do  begin; 
    SetBkColor(Handle,   RGB(0,255,0)); 
    SetBkMode(Handle,   OPAQUE); 
    TextCort(50,   50,    'Hello'); end;         "" 
    Font. Name 
    Свойство Name шрифта можно установить следующим образом: 
    Font.Name   :=   'Arial'; 
    Font.Style 

    Это свойство позволяет установить такие атрибуты шрифта, как полужирный 
(bold), курсив (italic), подчеркивание и перечеркивание текста. Если шрифт не 
поддерживает одного из этих атрибутов, то для создания недостающего можно 
использовать GDI, или же GDI может заменить шрифт другим. Ниже приведен пример 
установки шрифта, обладающего всеми возможными атрибутами. 

    Font.Style   :=   [fsBold,   fsltalic,   fsUnderlined,   fsStrikeout]; 

Если хотите, можете убрать ненужные вам атрибуты. 

    Font. Pitch 

    Это свойство позволяет выбрать моноширинный или пропорциональный шрифт. 
При моноширинном шрифте все знаки имеют одну и ту же ширину, и он удобен при 
выравнивании столбцов данных. Хорошим примером таких шрифтов является 
семейство шрифтов Courier, а примером пропор'циональных шрифтов может служить 
семейство Times. Ниже перечисляются возможные атрибуты. 

    Атрибут	Описание 

    fpDefault	Свойство Pitch устанавливается по умолчанию 
    f pFixed	Шаг фиксированный. Все знаки шрифта будут иметь одинаковую ширину 
    f pVariable	Шаг переменный. Знаки шрифта будут иметь различную ширину 

    И опять-таки, если шрифт не поддерживает выбранного вами атрибута, GDI 
может его заменить. 

    Глава 22. Работа с графикой	525 


    Font.Charset 

    Это новое свойство для Delphi 3.0 применяется довольно просто. Вы можете 
установить для свойства Charset любой за^гис^и?ованный_в Windows набор 
символов следующим образом: 

    Font.Charset   := ANSI  CHARSET; 


    ПРЕДОСТЕРЕЖЕНИЕ 
    Имейте в виду, что система может заменить один шрифт другим, если набор 
символов, который вы назначили, недоступен для выбранного вами шрифта. 

    Font. Handle 

    У вас есть шанс докопаться до сути установки правильных атрибутов шрифтов. 
Вы можете определить почти все атрибуты, какие только захотите, с помощью 
команд Windows GDI для создания желаемого шрифта с последующим присвоением 
свойству TFonts . Handle его дескриптора. Для этого заполните структуру 
TLogFont и вызовите функцию Windows CreateFontlnderect (). ** 
    Если вы хотите в качестве стартового варианта использовать текущий шрифт, 
найдите его структуру TLogFont, используя функцию Windows GetObject (), а 
затем создайте новый шрифт, внеся необходимые изменения в структуру текущего 
шрифта. Обратите внимание, что простая замена дескриптора шрифта в элементе 
VCL не всегда дает хорошие результаты. Гораздо лучше создать объект TFont, 
который не связан с текущим шрифтом элемента управления, внести изменения, 
а уж затем назначить этому элементу управления созданный шрифт. 
    Ниже приведен пример создания шрифта с поворотом на 45°. 

    procedure  TForml.ButtonlClick(Sender:   Tobject); var 
    If   :   TLogFont; 
    tf : TFont; begin 
    tf := TFont.Create; 
    tf.Assign(Buttonl.Font); 
    GetObject(tf.Handle, sizeof(lf), @lf); 
    If .IfEscapement := 450; 
    If.IfOrientation := 450; 
    tf.Handle := CreateFontlnderect(If); "Buttonl.ParentFont := false; 
    Buttonl.Font.Assign(tf); 
    tf.Free; end; 

    В этом примере TFont был создан и установлен в качестве текущего шрифта 
кнопки. Сначала для_нового шрифта копируются все. атрибуты шрифта кнопки. 
Затем вызывается функция Windows GetObject (), чтобы определить "логическую 
структуру шрифта нового TFont. После этого изменяются элементы Escapement и 
Orientation в этой логической структуре, чтобы повернуть шрифт на 45° при 
установленных единицах измерения в 1/10 градуса. 
    Теперь создадим шрифт, базирующийся на логической структуре, используя 
GDI-команду Windows CreateFontlnderect (). Возвращаемым значением будет 
дескриптор шрифта Windows, который мы установим для нашего объекта TFont. 
После этого присвоим свойству кнопки ParentFont значение False, так что в 
дальнейшем CBoficTBO_Font не будет определяться шрифтом формы. Затем установим 
TFont для свойства кнопки Font и освободим его. Заметьте, что TupffljTf, 
которьш вы хотите повернуть, должен быть "вращаемым", но не все шрифты 
являются таковыми. Рекомендуется использовать шрифты TrueType, так как их 
всегда можно повернуть. 
    Существует много атрибутов, которые следует выбирать при создании 
собственного шрифта. Обратившись к справочной системе Delphi для поиска 
LogFont, вы обнаружите длинный перечень приемлемых атрибутов. Имейте в виду, 
что разметчик шрифтов в Windows не всегда подбирает шрифт, точно отвечающий 
вашим требованиям, так как при выборе шрифта он опирается на средневзвешенное 
для атрибутов, которые вы указали. Когда разметчик рассматривает ваши 
требования, одни атрибуты могут быть для него более важными, чем другие. 

    526	Часть VI. Сложный материал 


    Получение дополнительной информации о шрифтах 

    Один из наиболее важных моментов, который необходимо учитывать при 
отображении текста, — сколько места требуется символьной строке при выводе 
данным шрифтом. В Delphi имеется два метода представления этой информации во 
время работы: Canvas .TextWidth и Canvas . TextHeight. Эти методы используют 
функцию Windows GetTex-tExtentPoint (). 
    Если вам действительно нужна информация о шрифте, используйте функцию 
Windows GetTextMetrics (). С ее помощью можно получить зарегистрированный 
набор символов этого шрифта, тип шрифта (TrueType, Device, Vector и др.) и 
даже символ конца абзаца, используемый выбр;анным.шрифтрм. 
    Если вы планируете выполнять^.много вычисле,ний, основанных на ширине 
символов шрифта, и для вас важна скорость расчетов, то для заполнения массива 
ширины символов шрифта можете использовать функцию Windows GetCharWidth () 
,ji_HejBbi3bmaTb метод канры TextWidth. Заметим, что для расчета ширины 
выбранной символьной строки могут быть полезными и другие факторы, включая 
внешнее пространство символа, кернинг и др. Если вы планируете сделать это, 
вам может понадобиться точная установка местоположения символа при 
использовании функции Windows ExtTextOut (). Эта удобная, на все случаи жизни, 
функция позволяет создать массив, в котором содержатся o?H?cjjjmHbi?;t-j<oojp^HaTb]Kj^ 
    Ниже приведен пример получения характеристики текста и ширины символов для 
текущего выбранного шрифта формы. Здесь же выводится перечень кодов символов и 
связанных с ними размеров символов в компонент ТМето. В этом примере также 
строка текста размещается на канве формы, используя десять дополнительных 
пробелов между всеми символами. 

    type  TIntArray =  array[0..0]   of  integer; 
    procedure  TForml.ButtonlClick(Sender   :   TObject); var 
    tm	:   TTextMetric; widths             :   Array[0... 255]   of  integer; 
PNpyjW. irH-h.q   ;   *TTn.tArray; i                        :   integer!'|v, 
begin                                                  \ 
    if  GetTextMetrics(Forml.Canvas.Handle,   tm)   then  begin 
GetCharWidth(Forml.Canvas.Handle,0,255,widths); for   i   :=  
ord(tm.tmFirstXhar)   to  ord(tm.tmLastChar)   do 
    Memol. Lines. Add (   Int't.oStr (i)   +   '    '   +  intToStr (widths 
[i] )); end; if  Length(Editl.Text)   >   0   then\begin 
    GetMem(PNewWidths,   sizeof(integer)    *   length(Editl.Text)); for  i   
:=  0  to   (length(Editl.Text)   - 1)   do 
    PNewWidths'4 [i]    := Widths [&rd (pchar (Editl. Text) [i] )]   +  10; Windows.ExtTextOut(Forml.Canvas.Handle,100,200,0,nil, 
    pchar(Editl.Text),length(Editl.Text), @PNewWidths'N [0] ) ; 
    FreeMem(PNewWidths,   sizeof(integer)   *   length(Editl.Text)); end; end; 

    В этом примере сначала объявляется новый тип массива TIntArray, объема 
которого достаточно для размещения одной целой переменной. Это сделано для 
того, чтрбы иметь возможность динамически помещать в массив целочисленные 
данные во время работы программы и иметь доступ ко всем элементам массива, не 
зная во время компиляции,, сколько их существует в действительности. Этот 
"волшебный" кусочек необходим нам, чтобы хитростью заставить компилятор 
предоставить "доступ к дополнительным элементам и не наделать ошибок во время 
компиляции— нечто такое, что программисты на С считают само собой разумеющимся. 
    Чтобы этот трюк сработал, учтите следующие условия. 
    1.	Доступ к массиву следует.&СУЩССХВдат^через переменную типа указатель. 
    2.	Проверка диапазона массива должна быть отключена. 
    3.	Доступ к индексу массива необходимо осуществлять через переменную. 
    Если последнее условие вам непонятно, рассмотрите следующую строку программы: 

    MylntegerArray*[2] := 10; 


    Глава 22. Работа с графикой	527 

    Эта строка не будет откомпилирована, так как ваш массив четко определен 
как имеющий один элемент и нулевой начальный индекс, и компилятор легко 
обнаружит ошибку. Если же вы обращаетесь к элементу массива через индексную 
переменную, компилятор будет спокоен и счастлив: 

    i   :=  2; 
    MyIntegerArrayA[i] := 10; 

    Единственная неприятность — вы должны быть уверены, что выделили 
достаточно памяти и элемент, к которому вы обращаетесь, действительно 
существует; в противном случае ваша программа может вызвать кучу неприятностей 
в виде исключительной ситуации или краха. Проверка диапазона должна быть 
отключена, иначе вы получите сообщение об ошибке во время выполнения. И 
наконец, вы должны обращаться к массиву через переменную типа указатель, так 
как без этого компилятор обязательно выдаст сообщение об ошибке. Увы, 
совместить строгость Pascal и гибкость С в Delphi можно только такими трюками... 
    В декларативной части программы были объявлены структура TTextMetrics, 
целочисленный массив для хранения значений ширины элементов, указатель на 
целочисленный массив для хранения значений новых размеров, используемый для 
установки нашей символьной строки, и переменная цикла с именем i. 
    Затем была вызвана функция GetTextMetrics () для получения характеристики 
текста шрифта и функция GetChar-Width () — для получения размеров символов 
шрифта. Заметьте, что мы опрашиваем все, возможно, даже незарегистрированные, 
символы. Обратите также внимание на то, что каждый шрифт имеет символ, 
заданный по умолчанию, который подставляется, как только понадобится 
незарегистрированный символ. Функция GetCharWidth () будет назначать ширину 
незарегистрированным символам в соответствии с шириной символа, установленного 
по умолчанию. После этого использовались такие элементы структуры 
TTextMetrics, как tm. tmFirstChar и tm. tmLastChar, для отображения в элементе 
ТМето всей таблицы зарегистрированных символов шрифта вместе с их шириной. 
    Наконец, самая приятная часть — использование функции Windows ExtTextOut 
() для представления символьной строки из редактирующей строки на канве формы 
с помощью десяти пробелов между символами (вы, конечно же, можете применить 
другую формулу для получения желаемого результата). 
    В этом фрагменте программы мы, во-первых, убеждаемся, что редактируемая 
строка не является пустой. Если в ней есть хоть какие-то символы, мы 
продолжаем динамически выделять в памяти место для хранения новых размеров (по 
одному элементу для каждого символа). Затем мы возвращаемся к символам 
редактируемой строки, восстанавливаем правильную ширину для каждой буквы, 
добавляем 10 и вновь присваиваем результат нашему динамическому массиву. После 
этого вызываем функцию Windows ExtTextOut (), передаем ей свойство формы 
Canvas . Handle, координаты х и у для размещения текста, символьную строку, 
которую нужно разместить, и новый массив размеров символов. И наконец, мы 
освобождаем память, которую заняли под новый массив. 
    Этот.пример демонстрирует грубый, но надежный способ управления точным 
размещением каждого символа, без дополнительных расходов на установку каждого 
символа в отдельности, но в Windows доступны и другие функции для получения 
информации и изменения ориентации, выравнивания и размещения символьной 
строки. Ниже перечислены наиболее полезные функции. 

    DrawText () GetKerningPairs() GetTextAlign() SetTextAlign() 
GetTextCharacterExtra() SetTextCharacterExtra () SetTextJustification() TabbedTextOut() 

    Все эти функции документированы в разделе Win32 справочной системы Delphi 
и действительно могут облегчить жизнь, когда вам нужны несложные действия по 
управлению установкой текста. Пример использования функции SetTextAlign () 
приводится в главе о печати далее в этой книге. И, наконец, если вам нужен 
действительно простой метод размещения текста в прямоугольной области с 
дополнительными пробелами между словами и вы не хотите раздумывать над этим 
процессом, используйте функцию Windows DrawText (). 

    procedure TForml.ButtonlClick(Sender : TObject); var 
    r : TRect; begin 
    if length(Editl.Text) = 0 then exit; 
    r.Top := 50; 
    r.Left := 50; 
    r.Right := 150; 
    r.Bottom := 150; 


    528	Часть VI. Сложный материал 


    DrawText(Forml.Canvas.Handle, pChar(Editl.Text), length(Editl.Text), r, 
    DT_WordBreak  or   DT_Left); end; 

    В этом примере текст, который содержится в редактируемой строке, был 
размещен на канве формы с помощью функции Windows DrawText (). Если 
редактируемая строка пуста, процедура заканчивается, в противном случае 
определяется формат прямоугольника для размещения текста и вызывается функция 
DrawText (), требующая, чтобы текст был выровнен по левому краю, а новые 
символы переноса строк были вставлены между словами именно так, как нужно для 
размещения текста между левой и правой границами прямоугольника. 
    Возможность передачи функции DrawText () других дополнительных опций 
делает ее одной из наиболее полезных функций размещения текста в Windows. 
Одной из моих любимых опций является DT_CALCRECT. Она поможет вам узнать, не 
выводя текста, прямоугольник какого размера необхрдим^аля вывода на экран 
текста заданным стилем (тем самым значительно упрощается расчет необходимого 
пространства для текстового блока). 

    Tlmage 

    Если уж тысячи слов можно заменить одной картиной, то работа с 
изображениями в Delphi наверняка стоит целой книги. К счастью, разработчики 
Delphi так упростили работу с ними, что предмет обсуждения уместился в одном 
маленьком разделе главы! 
    Раньше, когда не было Delphi, за надежную загрузку и сохранение на диске 
растровых изображений нужно было постоянно бороться, привлекая руководства и 
инструкции Microsoft. В те дни требовались программы в тысячи строк, чтобы 
достичь того же, что сведено разработчиками Delphi в два простых метода— 
LoadFromFile О и SaveToFile (). 
    Приведем пример использования объекта Tlmage для загрузки растрового 
изображения из файла, рисования и повторной записи на диск. 

    begin 
    Imagel.Picture.LoadFromFile('с:\somepath\some.bmp'); 
    Imagel.Canvas.Ellipse(0,   0,   50,   50); 
    Imagel.Picture.SaveToFile('с:\somepath\some.bmp'); end; 

    Элемент Image имеет несколько интересных свойств. Для рисования 
предназначено свойство Canvas. Свойство AutoSize изменяет размеры объекта для 
наилучшего размещения, свойство Stretch изменяет размеры картинки в 
соответствии с размерами изображения. Свойство Center разместит картинку по 
центру изображения. Но наиболее важное свойство элемента Tlmage — это, 
конечно, свойство Picture. 

    TPicture 

    Объект TPicture может содержать растровые изображения, метафайлы, 
пиктограммы и даже определяемые пользователем графические объекты. Создать его 
совсем не сложно: загрузите и сохраните объект на диске и в буфере обмена 
Clipboard. Когда-то сохранение графического объекта в буфере считалось черной 
работой! Сейчас же мы просто добавляем две строки в программу приложения, и 
работа выполняется сама собой. 
    Ниже приведен пример сохранения графического объекта в буфере информационн^го_об\1?на?ИЕЬоас1. 

    uses Clipbrd; 
    procedure TForml.ButtonlClick(Sender: TObject); 
    begin 
    Clipboard.Assign   (Imagel.Picture); end; 
    А вот пример загрузки графического объекта из буфера обмена. 
    procedure  TForml.ButtonlClick(Sender:   Tobject); begin 
    Imagel.Picture.Assign(Clipboard); end; 


    Глава 22. Работа с графикой	529 

    Кроме того, передать свойство объекта TPicture Graphic как параметр 
методам канвы Draw()  или Scretch-Draw () для выведения картинки на канву 
можно следующим образом. 

    procedure TForml.ButtonlClick(Sender:   Tobject); begin 
    Forml.Canvas.Draw(300,   100,   Imagel.Picture.Graphic); end; 
    TBitmap 

    В объекте TBitmap содержится и растровый объект Windows, и связанная с ним 
палитра (если, конечно, таковая имеется). Как и объект TPicture, TBitmap 
содержит методы работы с диском и буфером обмена для загрузки и сохранения 
изображений. Фактически для загрузки и хранения образа в растровом виде 
TPicture вызывает эти методы TBitmap. Другим местом хранения растровых 
изображений является файл ресурсов. Во время компиляции файлы ресурсов 
связываются с вашим приложением и будут предоставлять растровое изображение 
выполняемому файлу, облегчая распространение приложения, так как вам уже не 
надо беспокоиться о загрузке и инсталляции дополнительных файлов. 
    Для создания файла ресурсов можно воспользоваться редактором ресурсов 
Resource Workshop фирмы Borland, который поставляется в составе пакета Borland 
RAD, или же использовать компилятор ресурсов командной строки Borland Resource 
Command Line Compiler (BRCC32), поставляемый с Delphi. Resource Workshop — это 
полноценный редактор ресурсов, который может создавать, редактировать и 
размещать как 16-, так и 32-битовые файлы ресурсов из ряда источников, включая 
RC-файлы, выполняемые модули, DLL, драйверы, шрифты, пиктограммы, курсоры, 
определяемые пользователем ресурсы и пр. Хотя в основном пакет RAD связан с 
16-битовой разработкой, поставляемый в его составе Resource Workshop 4.5 
широко доступен для всех, кто хочет работать с ресурсами. 
    В следующем примере используется пакетный компилятор ресурсов BRCC32, 
поставляемый в составе Delphi. Выполняемый модуль находится в каталоге Delphi \Bin. 
    Сначала создадим в каталоге вашего проекта ASCII-файл TEST.RC, содержащий 
одну только строку: 

    MYBITMAP BITMAP  "c:\download\some.bmp" 

    Теперь вызовем компилятор ресурсов, так как необходимо создать растровый 
ресурс, который содержит изображение из файла test. bmp, и назовем его MYBITMAP. 
    Создайте файл с растровым изображением и назовите его SOME.BMP. 
    И, наконец, откомпилируйте RC-файл, задавая полные пути для пакетного 
компилятора и для КС-файла вашего проекта (замените на правильный путь для 
вашей системы): 

    с:\borland\delphi\bin\brcc32.ехе  с:\download\test.гс 

    Если все сделано верно, в каталоге вашего проекта появится бинарный файл 
TEST.RES, который можете связать с вашей программой, добавив в код одну строку: 

    {$R  TEST.RES} 

    Вы можете поместить эту строку, в общем-то, в любое место программы, но 
лично я предпочитаю помещать ее непосредственно под строкой {$R * . DFM}, 
находящейся в разделе implementation программы. Обратите внимание, что 
ресурсы, в отличие от других описаний выполняемой части, будут видны из любого 
места вашей программы. 
    Чтобы использовать новый ресурс, просто вызовите метод LoadFromResource () 
объекта TBitmap следующим образом. 

    procedure TForml.ButtonlClick(Sender:   Tobject); begin 
    Imagel.Picture.Bitmap.LoadFromResourceName(hlnstance,    'MYBITMAP'); end; 

    Ну что может быть проще! Обратите внимание на то, что переменная hlnstance 
передана методу LoadFromResourceName (). Это сделано для того, чтобы 
идентифицировать модуль, из которого загружается ресурс. Ресурсы могут 
загружаться из ряда источников, включая динамически связываемые библиотеки 
(DLL). Фактически многие динамические библиотеки содержат только ресурсы, 
заметно упрощая локализацию приложений для данного рынка (сводя ее к 
инсталляции различных DLL). Хорошим примером DLL, содержащих только ресурсы, 
является файл MORICONS. DLL, поставляемый вместе с Windows. Эта DLL заполнена 
пиктограммами, которые вы можете использовать, например, при работе с ярлыками 
на рабочем столе. 
    С объектом TBitmap связано много свойств и методов. Наиболее полезным 
является, пожалуй, метод Create. 

    530	Часть VI. Сложный материал 

    Сохранение канвы на диске — хороший пример создания растрового изображения 
"на ходу". Хотя многие элементы имеют свойство Canvas, лишь некоторые содержат 
метод SaveToDisk(}. Возьмем, например, компонент TPaintBox. Несмотря на то 
что TPaintBox выполняет большую работу, чтобы помочь вам добавить раскрашенное 
окно в приложение, он не имеет метода, позволяющего сохранить результат в 
файле. Представляется удобный случай создать новое растровое изображение, 
перенеся полученный пользователем результат с канвы PaintBox в растровое 
изображение и записав его на диск. Приведем пример формы с Paintbox, который 
позволяет пользователю "карлякать'' мышкой. Как только пользователь щелкнет на 
кнопке формы , все созданные им художества будут записаны на диск. 

    procedure  TForml.PaintBoxlMouseMove(Sender:   Tobject; 
    Shift   :   TShiftState; X,   Y   :   Integer); begin 
    if  ssLeft   in  Shift  then 
    PaintBoxl.Canvas.Pixels[x,y]    :=  clBlack; end; 
    procedure  TForml.ButtonlClick(Sender   :   TObject); 
    Bitmap	: TBitmap ; 
    r	: TRect ; 
    begin 
    r.Left	:= 0; 
    r .Top	:= 0; 
    r .Right	:= PaintBoxl. Width; 
    r. Bottom := PaintBoxl . Height; 
    Bitmap	:= TBitmap. Create; 
    Bitmap.	Width  := r. Right; 
    Bitmap.Height   :=   r.Bottom; Bitmap.Canvas.CopyRect(r,   
PaintBoxl.Canvas,   r) Bitmap.SaveToFile('с:\download\paintbox.bmp'); 
Bitmap.Free; end; 

    Необходимо лишь добавить фрагмент кода для события MouseMove объекта 
PaintBox, чтобы при нажатии левой кнопки мыши выполнялось закрашивание черными 
точками. Для того чтобы сохранить раскраску, мы просто назначаем 
прямоугольнику размеры PaintBox', создаем изображение "на ходу", снимаем 
размер изображения и используем метод CopyRect, чтобы скопировать образ канвы 
PaintBox на_канву Bitmap. И, наконец, записываем образ на диск и освобождаем Bitmap. 

    Побитовый и прямой доступ к DIB 

    В Delphi 3 добавлено новое замечательное свойство для объекта TBitmap — 
указатель на биты растрового изображения. В отличие от предыдущих версий 
Delphi, использующих аппаратно-зависимые форматы растровых изображений (DDB — 
device-depended bitmaps), в Delphi 3 используются аппаратно-независимые 
форматы растровых изображений (DIB — device-mSepended bitmaps). Установить 
указатель на DIB-биты несложно, надо просто обратиться к свойству Bitmap. 
DibMemory. Приведем пример доступа к памяти DIB и изменения пикселей в первой 
строке таким образом, чтобы их цвет соответствовал первому цвету в палитре. 
(Замечание. Эта процедура написана для растрового изображения с 256-цветной 
палитрой. Для других форматов необходимо переделать код так, чтобы он работал 
с различным количеством битов на пиксель, различными палитрами, сжатием и т.д). 

    Type TByteArray = Array[0...0] of byte; 
    procedure TForml.ButtonlClick(Sender: TObject); var 
    p : л TByteArray; i : integer; begin 
    Imagel.Picture.LoadFromFile('С:\DOWNLOAD\TEST.BMP'); 
    p := Imagel.Picture.Bitmap.DibMemory; 
    for i := 0 to (Imagel.Picture.Bitmap.Width — 1) 
    do pA[i] := 0; end; 


    Глава 22. Работа с графикой	531 

    В этом примере определены байтовый тип массива, переменная— указатель на 
массив ByteArray и переменная цикла с именем i. После этого загружено 
растровое изображение с диска и установлен указатель на DibMemory. Затем 
организован цикл для первой строки DIB и изменен цвет на первый цвет палитры DIB. 

    DIB или DDB 

    Как уже говорилось, в ранних версиях Delphi для хранения изображений в 
памяти использовались аппаратно-зависимые форматы растровых изображений. В 
Delphi 3 хранение изображений в памяти можно осуществлять, используя DDB или 
аппаратно-независимые форматы растровых изображений. По умолчанию в Delphi 3 
используется DIB, но вы можете изменить этот выбор, назначив свойству Bitmap. 
HandleType или bmDIB, или bmDDB. Обратите внимание, что при таком 
преобразовании может быть потеряна часть информации о цвете. 

    Прозрачность 

    Работа с прозрачными образами в системе Windows 95 настолько проста, что 
вряд ли стоит особо на ней останавливаться. Укажем только, что она выполняется 
благодаря элементу TImageList. Хотя вы можете использовать функции нижнего 
уровня Windows и ROP-коды, элемент TImageList вряд ли доставит вам 
беспокойство. Фактически даже метод копирования Canvas.Brush вводится только 
для обратной совместимости. 

    Области 

    Безусловно, Windows GDI проделал длинный путь в области областей (простите 
за случайный каламбур!). Области (region) позволяют создавать вырезанные 
фрагменты такой формы, какой вы только пожелаете, так что все, что бы вы потом 
ни нарисовали, будет размещено (или наложено) на ней. В 16-разрядной версии 
Windows области обычно вызывают много трудностей, связанных с памятью Windows 
GDI, которая переполняется при преобразовании форм в линейные сегменты. При 
работе в Windows 95 эта проблема значительно смягчилась, а для Windows NT ее, 
можно сказать, и вовсе не существует. Несмотря на то что Delphi не имеет 
никаких методов, относящихся к установкам вырезанных областей, вы можете 
создать их, используя Windows GDI Ниже приведен пример создания эллиптической 
области с помощью команд GDI. 

    procedure TForml.PaintBoxlMouseMove(Sender:   TObject); var 
    MyRgn     :   hRgn; OldRgn   :   hRgn; begin 
    MyRgn   :=  CreateEllipticRgn(0,   0,   100,   100); Old.Rgn   :=  
SelectObject(Forml.Canvas.Handle,   MyRgn); PatBlt(Forml.Canvas.Handle,0,0, 
Forml.Width, Forml.Height, blackness); 
    SelectObject(Forml.Canvas.Handle,   OldRgn); DeleteObject(MyRgn); end; 

    В этом примере определены две переменные области: одна — для хранения 
дескриптора области, которую мы создадим, вторая — для хранения дескриптора 
области формы, в которой мы размещаемся (так как мы хотим восстановить ее, 
когда закончим работу). Сначала создадим эллиптическую область и выделим ее на 
канве формы. Выделенный объект возвращает дескриптор предыдущей выделенной 
области, чтобы можно было сохранить ее для последующего использования. Чтобы 
область действительно начала функционировать, нарисуем квадрат, который 
закрывает форму полностью, но так, чтобы эллипс был окрашен. Затем вновь 
выделим прежнюю область на канве и удалим область, которую создали. 
    Области удобны сами по себе, а когда они объединяются с траекториями, вы 
получаете все, что необходимо для создания впечатляющих"ЗффектовГЕсть много 
функций, работающих с областями и доступных для выполнения всех видов 
операций: от поворота траектории в области до проверки, находится ли точка 
внутри области (что полезно при проверке попадания). 

    Метафайлы 

    Метафайл в Windows — это набор функций вывода Windows GDI, который может 
быть сохранен на диске или в Clipboard, а затем быстро воспроизведен на канве. 
Поскольку в действительности в метафайлах хранятся вызовы результатов GDI, 
обычно они значительно меньше, чем растровые изображения. Метафайлы могут быть 
также масштабированы без потерь'деталей. Хотя метафайл в Windows и проделал 
больию"й Ттуть вперед к аппаратной независимости, обратите внимание на 
некоторые моменты, которые следует иметь в виду. 

    532	Часть VI. Сложный материал 

    •	В метафайле вырезанные области не всегда хорошо масштабируются. 
    И	В метафайле текст не всегда хорошо масштабируется или выравнивается. 
    *	Канва метафайла не всегда возвращает информацию о канве без искажений. 
    Компонент TMetaf ile инкапсулирует Windows Metafile. Как уже отмечалось, 
есть три формата метафайлов, используемых в Windows: Standart, Aldus и 
Enhanced. Исходным был стандартный метафайл Windows. С ним связана следующая 
проблема: когда вы загружали такой метафайл с диска, не прочитав всех записей 
метафайла, не имели никакой информации о том, как его масштабировать, и для 
вывода изображения на экран приходилось рассчитывать все масштабирующие 
факторы. Aldus-метафайл помог разрешить эту проблему, добавив к файлу 
22-битовый заголовок, содержащий информацию, необходимую для аккуратного 
вывода образа на экран. Сегодня многие программы экспортируют метафайлы, 
используя заголовок формата Aldus, и большинство программ, в том числе и 
Delphi, отказывается загружать метафайлы, не содержащие такого заголовка. И 
наконец, существует улучшенный формат метафайла (EMF), который не только 
содержит внутреннюю информацию о масштабировании, но и поддерживает 
дополнительные функции рисования, доступные в Windows NT. Обратите внимание, 
что не все функци и рисования, доступные.J.Windows NT, доступны в Windows 95! 
Однако в документации к Windows 95 говорится, что она способна отображать 
любой метафайл, созданный в Windows NT. Ну что ж, попробуйте! 
    В Delphi вывести метафайл на экран очень легко. Вы можете просто вставить 
элемент Т Image в форму, а потом либо загрузить его с помощью инспектора 
объектов, либо динамически загрузить метафайл, применяя метод Т Image. 
LoadFromFile (). 
    Процесс рисования с объектом TMetaf ile отличается от рисования на канве 
объекта TBitmap. Сначала необходимо создать объект TMetaf ile, затем перед 
рисованием в метафайле создать объект Metaf ileCanvas. Завершив рисунок, 
следует освободить Metaf ileCanvas и лишь затем сохранить рисунок на диске, в 
буфере обмена или перевести ещ на другую канву 
    Рассмотрим пример. 

    procedure TForml.ButtonlClick(Sender:   Tobject); var 
    MyMeta	:   TMetafile   ; 
    MyMetaCanvas   :   TMetafileCanvas   ; begin 
    MyMeta	:=  TMetafile.Create; 
    MyMeta.Width     :=   100; 
    MyMeta.Height   :=   100; 
    MyMetaCanvas     :=  TMetafileCanvas.Create(MyMeta,   0); 
    MyMetaCanvas.Ellipse(0,   0,    100,   100); 
    MyMetaCanvas.Free; 
    Forml.Canvas.Draw(0,    0,   MyMeta); 
    MyMeta.Free; end; 

    В этом примере описаны TMetafile и TMetaf ileCanvas. Затем, до создания 
канвы, создается метафайл и устанавливается его высота и_ши?ина, после чего 
создается канва метафайла. В этом примере образ метафайла был перенесен на 
канву формы, но вы можете сохранить метафайл на диске или в буфере обмена. И 
напоследок мы освобождаем объект метафайла. 

    Режимы отображения 

    До сих пор мы использовали стандартный режим отображения Windows MM_TEXT. 
В этом режиме все координаты рисунка выражаются в пикселях и отсчитываются от 
верхнего левого угла канвы (начало координат), положительное значение х 
возрастает слева направо, а положительное значение у возрастает сверху вниз. 
    В Windows доступны следующие режимы отображения. 

    Режим	Описание 
    ММ_ТЕХТ	Каждая единица отображается одним пикселем (положительное значение 
у возрастает сверху вниз) 
    MM_LOMETRIC	Каждая единица отображается 0.1 мм (положительное значение у 
возрастает снизу вверх) 
    MM_HIMETRIC	Каждая единица отображается 0.01 мм (положительное значение у 
возрастает снизу вверх) 
    MM_LOENGLISH	Каждая логическая единица отображается 0.01 дюйма 
(положительное значение у возрастает снизу вверх) 


    Глава 22. Работа с графикой	533 


    MM_HIENGLISH	Каждая логическая единица отображается 0.001 дюйма 
(положительное значение у возрастает снизу вверх) 
    MM_TWIPS	Каждая логическая единица отображается 1/20 пункта (1/1440 дюйма) 
(положительное значение у возрастает снизу вверх) 
    MM_ISOTROPIC	Каждая единица отображается произвольной единицей с равным 
масштабированием осей. 
    Используйте SetWindowExtEx и SetViewportExtEx, чтобы установить желаемые 
единицы и ориентацию осей. Для того чтобы размеры х, у единицы были 
одинаковыми, при необходимости GDI делает небольшую подгонку. (По умолчанию 
положительное значение у возрастает сверху вниз и может быть переопределено.) 
    MM_ANISOTROPIC	Каждая единица отображается произвольной единицей с 
произвольным масштабированием осей. 
    Используйте SetWindowExtEx и SetViewportExtEx, чтобы установить желаемые 
единицы, ориентацию и масштабирование осей. (По умолчанию положительное 
значение у возрастает сверху вниз и может быть переопределено.) 
    Для всех режимов отображения по умолчанию начало отсчета (точка с 
координатами 0, 0) находится в верхнем левом углу канвы. Обратите внимание, 
что при выводе на принтер размеры должны сработать точно, а для дисплея могут 
варьироваться, так как доступное количество пикселей для любого режима 
неизменно, тогда как размер монитора изменяется. 
    Работая в режимах отображения, вы используете несколько функций для 
управления преобразованием и отображением координат. Все функции, которые вы 
используете для управления преобразованием, возвращают предыдущие установки, и 
будет лучше, если вы сначала сохраните эти установки во временной переменной и 
лишь затем переопределите их. Вы всегда должны восстанавливать установки в 
порядке, обратном тому, который использовали для изменения. Вот наиболее 
важные установки. 

    Установка	Описание 

    SetMapMode	Устанавливает режим отображения 
    ^SetWindowOrgEx	Устанавливает начало координат (местоположение точки с 
координатами О, О) 
    SetWindowExtEx	Устанавливает логические единицы. Имеется в виду размер 
единицы, которую вы хотите использовать. Применяется только для режимов 
MM_ISOTROPIC и MM_ANISOTROPIC 
    SetViewportExtEx	Устанавливает аппаратные единицы. Имеется в виду размер 
единицы, в которую отображается логическая единица. Применяется только для 
режимов MM_ISOTROPIC и MM_ANISOTROPIC 
    Понимание работы функции SetMapMode () не вызывает никаких проблем, тогда 
как другие функции требуют некоторых пояснений. Функция SetWindowOrgEx () 
позволяет изменять место расположения точки с координатами О, О (начало 
координат), и, поверьте мне, вы обязательно будете ее использовать. Рассмотрим 
режим отображения MM_LOENGLISH, при котором положительное значение координаты 
у возрастает в направлении снизу вверх. Из-за того, что начало координат по 
умолчанию находится вверху канвы, вы (если, конечно, не хотите рисовать в 
отрицательных координатах) должны будете перенести начало координат вниз (или 
рисуйте от верха канвы...). Функции SetWindowExtEx () и SetViewportExtEx () 
работают совместно, чтобы определить, как именно вы хотите масштабировать 
рисунок при работе в режимах MM_ISOTROPIC и MM_ANISOTROPIC. Предположим, вы 
решили создать рисунок так, что каждые ^две_единиць^ вашего рисунка будут 
равны пяти аппаратным единицам. Короче говоря, вы вызываете функцию 
Set-WindowExtfExl j, испол ьзуТощую две единицы, и функцию SetViewportExtEx 
(), использующую пять единиц. Вы можете даже изменить направление увеличения 
координат х и у, передав этим функциям отрицательные величины. 
    Приведем пример использования режима отображения MM_LOENGLISH для 
рисования окружности диаметром один дюйм. 

    procedure TForml.ButtonlClick(Sender:   Tobject); var 
    OldMapMode   :   integer; 
    OldOrigin     :   TPoint   ; begin 
    OldMapMode   := SetMapMode(Forml.Canvas.Handle, 
    MM_LOENGLISH); 
    SetWindowOrgEx(Forml.Canvas.Handle, 0,   100,   @01dOrigin); 
    Forml.Canvas.Ellipse(0,   0,   100,   100); 


    534	Часть VI. Сложный материал 


    SetWindowOrgEx(Forml.Canvas.Handle, 
    OldOrigin.x, 
    OldOrigin.y, 
    nil); 
    SetMapMode(Forml.Canvas.Handle,   OldMapMode); end; 

    Обратите внимание, каким образом сохранены старый режим отображения и 
начало координат окна, а затем установлены вновь в обратном порядке. Интересно 
и то, как смещено начало координат на 100 единиц вниз, иначе окружность была 
бы нарисована сразу под верхним краем канвы. 
    Приведем пример использования режима отображения MM_ANISOTROPIC для 
рисования эллипса. Мы определили эллипсу равные оси, так что в результате 
должна была бы получиться окружность, но из-за того что мы установили 
масштабирующий фактор по координате х так, чтобы каждая наша единица 
отображалась двумя аппаратными единицами, мы получим рисунок эллипса , ширина 
которого будет в два раза больше высоты, 

    procedure  TForml.Button2Click(Sender:   Tobject); var 
    OldMapMode	:   integer; OldOrigin                    :   TSize; 
OldWindowExtent      :   TSize; OldViwPortExtent   :   TSize; begin 
    OldMapMode   :=  SetMapMode(Forml.Canvas.Handle, 
    MM_ANISOTROPIC); 
    SetWindowExtEx(Forml.Canvas.Handle, 100,   100, 
    SOldWindowExtent); 
    SetViewportExtEx(Forml.Canvas.Handle, 200, 100, 
    @01dViewPortExtent); SetWindowOrgEx(Forml.Canvas.Handle, 0,   -100, @01dOrigin); 
    Forml.Canvas.Ellipse (0,   0,    100,    100); 
SetWindowOrgEx(Forml.Canvas.Handle, OldOrigin.ex, OldOrigin.cy, nil) ; 
    SetViewportExtEx(Forml.Canvas.Handle, OldViwPortExtent.ex, 
OldViwPortExtent.cy, nil) ; 
    SetWindowExtEx(Forml.Canvas.Handle, OldWindowExtent.ex, 
OldWindowExtent.cy, nil) ; 
    SetMapMode(Forml.Canvas.Handle,   OldMapMode); end; 

    В этом примере мы также подправили начало координат окна, хотя это и не 
было необходимо, так как при использовании режима MM_ANISOTROPIC координаты у 
возрастают сверху вниз. Опять-таки, мы запоминаем все установки перед тем, как 
изменить их, и восстанавливаем эти установки в порядке, обратном их изменению. 
    Наконец, если вам необходимо преобразовать точку из логических единиц в 
аппаратные или из аппаратных — в логические, используйте такие функции 
Windows, как DPtoLP () и LPtoDP (). Они будут конвертировать точку или группы 
точек между пользовательским и аппаратным пространством. 

    Подзадачи (threads) 

    Я счастлив сообщить, что для графических классов Delphi добавлена защита 
подзадач. Теперь TCanvas имеет два метода для работы с подзадачами— Lock и 
Unlock. Метод Lock гарантирует исключительный доступ к канве, а метод Unlock 
отменяет его. Отметим, что объект TBitmap имеет неявные блокирования процессов 
только во время работы метода Assign. Если у вас есть подзадачи, написанные 
для одного и того же растрового изображения, заблокируйте одновременный доступ 
к канве изображения. 

    Глава 22. Работа с графикой	535 


    Резюме 

    Разработчики Delphi выполнили огромную работу, чтобы сделать 
программирование графики в Delphi легким, быстрым и приятным занятием. Они 
также предусмотрели использование всех возможностей доступной вам операционной 
системы, позволяя вам выжать из GDI всю мощь до последнего эрга. Прочтите 
главу 23, "Печать в Delphi", поскольку в ней также содержится много идей, 
фрагментов кода и полезных советов о том, как сделать ваши программы более 
понятными и переносимыми на другие устройства. 

    536	Часть VI. Сложный материал 

