GDI: графика в Delphi

GDI: графика в Delphi.

Жаргон GDI

GDI расшифровывается как Graphics Device Interface, и представляет собой интерфейс, который Windows использует для рисования 2D графики.

GDI обычно используют для создания простых эффектов с минимальными усилиями GDI.

С GDI тесно связана ещё одна аббревиатура - DC ("Device Context" - контекст устройства). В Delphi контекст устройства представлен как TCanvas. Идея контекста устройства заключается в том, что это универсальное устройство вывода, поэтому можно использовать одинаковые функции как для экрана, так и для принтера.

Все графические функции в Delphi являются надстройками над стандартными GDI функциями Windows. Позже мы поговорим об этих функциях.

А теперь самое время приступить к рассмотрению того, как устроен GDI. Ниже, в таблице, представлены некоторые важные классы:

Имя

Описание

Pen

Используется для рисования простых линий. Обычно применяется для функции LineTo или при рисовании рамки для определённой фигуры (например для функции Rectangle).

Brush

Кисть используется для заполнения области определённым цветом. Применяется в функциях Rectangle, FillRect или FloodFill.

Font

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

Region

Позволяет задать регион (замкнутое пространство). Регионом может быть круг, квадрат или произвольная фигура. Позволяет так же делать дырки в фигурах.

Рисование линий

Сперва необходимо чётко уяснить, что координата (0,0) это верхний левый угол экрана. То есть значения по оси y увеличиваются вниз экрана. Соответственно, координата (0, 50) означает, что мы просто отступили на 50 пикселей от верха экрана.

Самое главное, что надо знать при рисовании линий и фигур, это различие между пером (Pen) и кистью (Brush). Всё очень просто: перо (Pen) используется при рисовании линий или рамок, а кисть (Brush) для заполнения фигуры.

Ниже приведены две функции, которые используются для рисования линий и обе принадлежат TCanvas:

Имя

Описание

Пример

MoveTo

Перемещает точку начала рисования линии в указанные координаты x и y

Canvas.MoveTo(50, 100);

LineTo

Рисует линию начиная с текущей позиции (см. MoveTo) до указанных координат x и y.

Canvas.LineTo(50, 100);

Эффект перемещения точки начала рисования линии так же достигается при помощи установки своства PenPos в канвасе... например, "Canvas.PenPos.x := 20;", "Canvas.PenPos.y := 50", или "Canvas.PenPos := Point(20,50);".

По умолчанию, точка начала рисования установлена в (0,0), то есть, если сразу вызвать "Canvas.LineTo(100,100);" то будет нарисована линия из точки (0,0) в точку (100, 100). Точка начала рисования автоматически переместится в (100, 100), то есть, если выполнить команду "Canvas.LineTo(200, 100);", то следующая линия будет нарисована из точки (100, 100) в (200, 100). Поэтому, если мы хотим рисовать линии несоединённые друг с другом, то придётся воспользоваться методом MoveTo.

Линия, нарисованная при помощи LineTo использует текущее перо канваса (типа TPen). Основные свойства пера, это ширина - "Canvas.Pen.Width := 4;" (при помощи которого можно задавать различную ширину линий), и цвет "Canvas.Pen.Color := clLime;".

Взглянем на простой пример беспорядочного рисования разноцветных линий:

procedure TForm1.FormCreate(Sender: TObject);

begin

// инициализируем генератор

// случайных чисел

Randomize;

end;

const NUM_LINES = 2000;

procedure TForm1.DrawLines;

var

i: Integer;

begin

for i := 0 to NUM_LINES - 1 do

begin

Canvas.Pen.Color :=

RGB(Random(256),

Random(256),

Random(256)

);

Canvas.LineTo

(Random(ClientWidth),

Random(ClientHeight));

end;

end;

Процедура DrawLines вызывается из обработчика кнопки OnClick. Количество линий задаётся в константе NUM_LINES. Между прочим, функция RGB, составляет цвет каждой линии из трёх основных составляющих: красного, зелёного и синего (значения от 0 до 255) и возвращает нам цвет в виде TColor. О цветах поговорим немного позже, а вот так выглядит нарисованный пейзаж:

Рисование фигур

Для рисования фигур, в TCanvas предусмотрены следующие функции:

ИМЯ

ОПИСАНИЕ

ПРИМЕР

Ellipse

Рисует элипс, вписанный в невидимый квадрат с координатами верхнего левого угла и правого нижнего. Если координаты х и y у углов будут совпадать, то получится круг.

Canvas.Ellipse(0,0,50,50);

FillRect

Заполняет прямоугольник цветом текущей кисти (brush), но никак не за пределами него.

Canvas.FillRect( Bounds(0,0,100,100));

FloodFill

Заполняет данную область цветом текущей кисти, до тех пор пока не будет достигнут край.

Canvas.FloodFill(10, 10, clBlack, fsBorder);

Rectangle

Рисует прямоугольник (или квадрат), заполненный цветом текущей кисти и обрамлённый цветом текущего пера

Canvas.Rectangle( Bounds(20, 20, 50, 50));

RoundRect

Тоже, что и Rectangle, но с загруглёнными углами.

Canvas.RoundRect( 20, 20, 50, 50, 3, 3);

Ещё есть очень нужная функция TextOut, которая позволяет рисовать текст, используя шрифт, заданный в канвасе:

ИМЯ

ОПИСАНИЕ

ПРИМЕР

TextOut

Рисует данную строку на канвасе начиная с координат (x,y) - фон текста заполняется текущим цветом кисти.

Canvas.TextOut(10, 10, 'Some text');

Кстати, функция позволяет рисовать текст, не заполняя его фон. Если Вам необходимо изменить шрифт, используемый в TextOut, то необходимо изменить свойство Font канваса (это свойство имеет тип TFont) - например "Canvas.Font.Name := 'Verdana';", "Canvas.Font.Size := 24;" или "Canvas.Font.Color := clRed;".

Вкратце хотелось бы обратить Ваше внимание на довольно полезный класс TRect, который умеет хранить в себе значения лево, право, верха и низа (кстати, в Windows API это RECT). То есть, достаточно указать левую и верхнюю координату и ширину и высоту области, а TRect автоматически подставит в виде (лево, верх, лево + ширина, верх + высота). Ещё есть другая функция Rect(), которая делает тоже самое, но координаты в ней задаются напрямую как лево, право, верх и низ. Ну и по желанию, можно использовать API функцию SetRect.

Ниже представлен пример, который рисует случайным образом различные фигуры:

const NUM_SHAPES = 200;

procedure TForm1.DrawShapes;

var

i,

ShapeLeft,

ShapeTop: Integer;

begin

for i := 0 to NUM_SHAPES - 1 do

begin

Canvas.Brush.Color :=

RGB(Random(256),

Random(256),

Random(256));

ShapeLeft := Random(ClientWidth);

ShapeTop := Random(ClientHeight);

// теперь, случайным образом, решаем что рисовать

case Random(3) of

0: Canvas.Rectangle(ShapeLeft,

ShapeTop,

ShapeLeft + Random(50),

ShapeTop + Random(50));

1: Canvas.Ellipse(ShapeLeft,

ShapeTop,

ShapeLeft + Random(50),

ShapeTop + Random(50));

2: begin

Canvas.Font.Size := 10 + Random(7); // от 10 до 16

Canvas.TextOut ( ShapeLeft, ShapeTop, 'Some text');

end;

end;

end;

end;

Как Вы уже успели заметить, некоторые фигурки имеют цвет рамки, отличающийся от того цвета, которым заполнена фигура. Кистью мы заполняем объекты, а пером обрамляем. Если цвет кисти (brush) меняется случайным образом, то цвет пера(pen) остаётся постоянным.

Перерисовка окна

Теперь давайте разберёмся, почему в самом первом примере у нас стирались линии при перемещении формы за границы экрана. А именно, нам нужно выяснить разницу между "рисованием" и "перерисовкой".

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

Перерисовка несколько отличается от понятия "рисование". Когда окну необходимо перерисоваться, то Windows посылает определённое сообщение. Это сообщение поступает в обработчик события "OnPaint". Любой код, который поместить в обработчик OnPaint будет вызван каждый раз, когда форме необходимо обновиться.

Для примера, поместите следующий код в проект:

procedure TForm1.DrawSomeText;

begin

Canvas.TextOut(10, 10, 'Some text');

end;

Если поместить на форму кнопку и вызывать DrawSomeText из обработчика кнопки OnClick, то проблема с исчезновением текста при перемещении формы останется. ОДНАКО, если вызвать DrawSomeText из обработчика формы OnPaint, то текст останется на своём месте окончательно.

Дескрипторы, или как пользоваться аналогичными API функциями

Итак, мы научились рисовать линии, различные фигуры, научились делать так, чтобы наше творение не стиралось при перемещении формы, и проделали мы всё это при помощи стандартных функций VCL (таких как Canvas.TextOut и т.д.). Однако, что делать, если Вы не хотите пользоваться графическими функциями VCL, которые всего навсего являются надстройками над аналогичными функциями из Windows API? Пожалуйста! Никто нам не запрещает пользоваться API функциями напрямую! Но постойте-ка, все они требуют какого-то HDC! Что такое HDC?

Почти всё в Windows использует "Дескриптор" (Handle). Дескриптор, это способ идентификации объекта в системе. У каждого окна есть свой дескриптор, у каждой кнопки тоже есть свой дескриптор и т.д. Именно поэтому все наши объекты имеют дескриптор в качестве свойства - например, "MyForm.Canvas.Handle".

Тип HDC это Дескриптор(Handle) Контекста Устройства (Device Context). Поэтому, мы спокойно можем подставлять свойство канваса Handle везде, где нам это потребуется.

Ради интереса можно взглянуть на таблицу, в которой представлены примеры вызовов некоторых функций из VCL и их аналогов из Windows API.

VCL WINDOWS API

Canvas.TextOut(x,y,myString);

TextOut(Canvas.Handle, x, y, PChar(myString), Length(String));

Canvas.FloodFill(X, Y, Color,fsBorder);

ExtFloodFill(Canvas.Handle, x, y, YourColour, FLOODFILLBORDER);

Canvas.LineTo(x,y); LineTo(Canvas.Handle, x, y);

Canvas.MoveTo(x,y); MoveToEx(Canvas.Handle, x, y, nil);

Так же можно использовать разные дескрипторы, чтобы рисовать в разных местах. Например, можно использовать "SomeBmp.Canvas.Handle" для рисования на картинке (битмапе), либо "Form1.Canvas.Handle", чтобы рисовать на форме.

В API версии функции TextOut необходимо передавать строку завершённую нулём. Это значит, что вместо того, чтобы передать строку в функцию напрямую, необходимо передать её как PChar. Так же не забывайте передавать в функцию длину строки. Для этого можно воспользоваться функцией Length.

Ну что, Вам уже захотелось поместить на форму какую-нибудь красивую картинку ?

Что такое Битмапы (Bitmaps)?

Рисование линий, это, конечно, хорошо, но рано или поздно Вам понадобится нарисовать более реалистичную картнику в своём приложении. Поэтому совершенно необходимо научиться работать с растровыми изображениями, или как их называют в среде программистов - битмапами.

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

Битмапы можно рисовать не только на форме, но и по всему экрану. Может это и может показаться немного странным, но иногда это бывает полезно, особенно при создании скринсейвера. Однако, сначала нам необходимо разобраться с тем, как работать с битмапами. Вот небольшой пример:

procedure Form1.DrawBitmap(const Filename: String; const x,y: Integer);

var

Bmp: TBitmap;

begin

// Сперва убедимся, что файл существует!

if not FileExists(Filename) then

begin

ShowMessage('The bitmap ' + Filename + ' was not found!');

Exit;

end;

Bmp := TBitmap.Create;

try

Bmp.LoadFromFile(Filename);

Canvas.Draw(x, y, Bmp);

finally

Bmp.Free;

end;

end;

Эта функция пытается загрузить и показать картинку, (с именем Filename, например 'myBitmap.bmp') начиная с точки (x,y).

Эта функция довольно неэффективна. Она создаёт и уничтожает битмап каждый раз когда вызывается, а так же каждый раз проверяет существование файла. Лучше объявлять объект TBitmap как часть формы, создавать и загружать картинку в FormCreate, а освобождать её в FormDestroy.

Функции рисования в GDI

TCanvas имеет несколько полезных функций, которые работают с типом TGraphic. Тип TGraphic является базовым классом для графических объектов в Delphi, таких как: битмапы (TBitmap), иконки (TIcon), метафайлы (TMetafile) и JPEG-и (TJPEGImage). Все они используют одни и те же функции, которые приведены в таблице:

Все эти функции являются методами TCanvas.

ИМЯ

ОПИСАНИЕ

ПРИМЕР

Draw

Рисует TGraphic на канвасе так как он есть, не растягивая.

Canvas.Draw(5,10,MyGraphic);

StrechDraw

Рисует TGraphic на канвасе, подгоняя (растягивая) его под заданную область.

Canvas.StretchDraw( Bounds(0,0,32,32), MyGraphic);

CopyRect

Копирует часть TCanvas-а в другой, при необходимости растягивая его.

Canvas.CopyRect( Bounds(0,0,32,32), MyBmp.Canvas, Bounds(0, 0, 640, 480));

TCanvas.Draw является обёрткой для API функции BitBlt:

function BitBlt(

hdcDest: HDC; // дескриптор конечного контекста устройства

nXDest, // коорд. x верхнего левого угла конечного прямоугольника

nYDest, // коорд. y верхнего левого угла конечного прямоугольника

nWidth, // ширина конечного прямоугольника

nHeight: Integer; // высота конечного прямоугольника

hdcSrc: HDC; // дескриптор исходного контекста устройства

nXSrc, // коорд. x верхнего левого угла исходного прямоугольника

nYSrc: Integer; // коорд. y верхнего левого угла исходного прямоугольника

dwRop: DWORD // код растровой операции

): Boolean;


GDI: графика в Delphi