Для рисования в Windows вы должны использовать функции GDI (Graphics Device Interface). Если вы хотите что-то нарисовать на устройстве вывода (экран монитора или принтер), вы должны в самом начале получить описатель контекста устройства (device context), который далее будет называться DC. Полученный описатель передается как параметр большинству функций из GDI, чтобы идентифицировать логическое устройство вывода.
Контекст устройства содержит в себе множество атрибутов, определяющих, как GDI будет работать с этим логическим устройством. Такими атрибутами являются: шрифт, цвет текста, цвет пера, цвет фона текста, кисть и т.д. Чтобы изменить любой из этих атрибутов, вы должны вызвать специальную функцию.
Cуществует множество способов получения контеста устройства, после получения контекста монитора, его необходимо вернуть назад перед выходом из оконной процедуры, то есть контекст дается именно вам на время и он один на всех. Подробнее о взятии и отдаче контекста в сообщении WM_PAINT будет написано ниже. Для получения контекста в произвольном месте программы, вы должны использовать функцию GetDC или GetWindowDC, для возвращения контекста назад – ReleaseDC. Весь блок рисования должен находиться между этими функциями:
hdc := GetDC(hwnd); //взяли контекст //функции рисования ReleaseDC(hwnd, hdc); //вернули назад в систему или hdc := GetWindowDC(hwnd); //функции рисования ReleaseDC(hwnd, hdc); |
Функция GetDC получает контекст клиентской части окна, определяемого hwnd, а функция GetWindowDC получает контекст всего окна, а не только его клентской части. Вы также можете получить контекст всего экрана целиком, если в GetDC задать параметр равным нулю.
Существуют и другие функции
получения контекста (CreateDC, CreateCompatibleDC и т.п.),
но о них как-нибудь потом.
Цвета
Для задания цвета в GDI используется длинное беззнаковое целое (DWORD), в большинстве случаев цвет хранится в виде RGB (Red Green Blue) составляющих, соединенных в одно число, каждая составляющая имеет тип BYTE, для преобразования цвета существуют функции RGB, GetRValue, GetGValue, GetBValue, назначение этих функций понятно и без пояснений. В файле windows.pas определен тип-синоним для DWORD COLORREF, который служит специально для задания цвета. Вот заголовок функции RGB и GetRValue:
function RGB(r, g, b: Byte): DWORD; function GetRValue(rgb: DWORD): Byte;
Далее везде где упоминается слово ЦВЕТ, использутся именно этот способ задания и интерпретации цвета.
Для задания фонового цвета служит функция SetBkColor
function SetBkColor(DC: HDC; Color: COLORREF): COLORREF;
Обратите внимание, что эта функция определяет не цвет фона окна, а цвет фона текста и цвет, которым закрашиваются промежутки в пунктирных линиях! Также используется в некоторых других случаях, для подробностей обращайтесь к справке по win32.
Для получения фонового цвета используется соответственно GetBkColor.
Вместо цвета фона можно использовать прозрачный фон, это достигается вызовом функции SetBkMode:
SetBkMode(dc, TRANSPARENT);//фон прозрачный SetBkMode(dc, OPAQUE); //фон заполняется текущим фоновым цветом |
Для изменения цвета выводимого цвета используется функция SetTextColor:
function SetTextColor(DC: HDC; Color: COLORREF): COLORREF;
Значение, устанавливаемое этой функцией, используется такими функциями как TextOut и ExtTextOut для в качестве цвета выводимого текста. Как и SetBkColor эта функция возвращает предыдущий цветовое значение. Для получения цвета текста в контексте используется функция GetTextColor.
Многие функции GDI принимают
значение цвета как один из параметров.
Перья (pens)
Перо (pen) – это графический инструмент, используемый Windows для рисования линий. Перо характеризуется такими атрибутами как cтиль, цвет, толщина. Здесь будет идти речь только о самых простых типах переьев, для решения более сложных вопросов обращайтесь к соответствующей литературе.
Для создания пера можно использовать функцию CreatePen:
function CreatePen(iStyle, iWidth: Integer; crColor: COLORREF): HPEN;
В случае успеха возвращается описатель нового пера, в противном случае – нуль. Если в пере больше нет нужды, его уничтожают функцией DeleteObject. Пример создания и уничтожения пера:
var hPen : HPEN; ... hPen := CreatePen(PS_SOLID, 10, RGB(255, 0, 0));//создание пера красного цвета толщиной 10 ...//использование пера для рисования, например DeleteObject(hPen); |
Также для создания пера можно использовать функцию CreatePenIndirect, которая в качестве параметра принимает запись (структуру) с описанием всех свойств нового пера. Чтобы ваше перо стало доступным контексту, его надо в него включить, это делается при помощи функции SelectObject:
function SelectObject(DC: HDC; p2: HGDIOBJ): HGDIOBJ;
Эта функция связывает с графическим контекстом некоторый объект GDI, возвращая при этом описатель старого. Возвращаемое значение всегда нужно сохранять, чтобы после завершения работы с контекстом восстановить его предыдущее состояние. Вот пример для CreatePen:
var hPen, hOldPen : HPEN; ... hPen := CreatePen(PS_SOLID, 10, RGB(255, 0, 0)); hOldPen := SelectObject(hDC, hPen);//связываем перо с контекстом ...//рисование пером SelectObject(hDC, hOldPen); //восстанавливаем предыдущее состояние DeleteObject(hPen);//удаляем перо |
Для создания более «продвинутых»
перьев служит функция ExtCreatePen, но о ней я уже
рассказывать не буду – вы можете сами
прочитать описание в справке по win32.
Кисти (brushes)
Кисть (brush) – это графический инструмент GDI, предназначенный для закрашиванию областей (например, внутренности эллипса или прямоугольника). Кисть может быть однотонной (одного цвета), состоять из так называемого узора (pattern), или определяться произвольным битмапом (bitmap). Для программиста кисть определяется своим описателем, получаемым как результат вызова функции создания кисти. Кисть связывается с контекстом аналогично перу при помощи функции SelectObject. Для создания одноцветной кисти используется функция CreateSolidBrush:
function CreateSolidBrush(crColor: COLORREF): HBRUSH;
crColor определяет цвет кисти
Для создания кисти со стандартным узором используется функция CreateHatchBrush:
function CreateHatchBrush(iStyle: Integer; crColor: COLORREF): HBRUSH;
Для создания кистей, основанных на битмапах используется функция CreatePatternBrush:
function CreatePatternBrush(hBitmap: HBITMAP): HBRUSH;
hBitmap – это описатель битмапа, на основе которого будет построена кисть, при применении этой кисти вы получите область «залитую» этими битмапами, повторяющимися по горизонтали и вертикали. Также для создания таких кистей можно использовать функцию CreateDIBPatternBrushPt.
Для создания кисти определенного стиля, цвета и узора используется функция CreateBrushIndirect:
function CreateBrushIndirect(const lb: TLogBrush): HBRUSH; PLogBrush = ^TLogBrush; TLogBrush = packed record lbStyle: UINT; lbColor: COLORREF; lbHatch: Longint; end;
В эту функцию передается предварительно заполненная запись типа TLogBrush. Параметры этой записи такие:
После создания кисть связывается с контекстом при помощи SelectObject. Для получения информации о кисти используется функция GetObject:
var lb : TLogBrush; ... GetObject(hBrush, sizeof(TLogBrush), @lb); |
После такого вызова переменная lb будет содержать информацию о кисти hBrush. Уничтожается кисть при помощи DeleteObject и только после освобождения из контекста. Пример:
var hBr, hOldBr : HBRUSH; ... hBr := CreateSolidBrush(RGB(0, 255, 0));//создаем синюю кисть hOldBr := SelectObject(hDC, hBr);//сохраняем старую кисть ...//рисуем что-нибудь SelectObject(hDC, hOldBr); DeleteObject(hBr);//удаляем кисть |
Графический контекст содержит в себе графический инструмент шрифт, который используется функциями рисования текста для вывода этого самого текста на логическое устройство. Контекст может содержать только один шрифт. Объект шрифт в GDI хранится во внутренем скрытом формате, и программист должен осуществлять все манипуляции с ним используя описатель (handle). Под фразой «создать шрифт» далее будет подразумеваться создание соответствующего объекта GDI, все манипуляции со шрифтом происходят с полученным при его создании описателем. Стандартные системные шрифты можно не создавать, так как они уже существуют в глубине системы; получить их можно при помощи функции GetStockObject, речь о которой пойдет ниже.
Если говорить формально, то шрифт – это набор символов имеющих общий дизайн, тремя самыми главными элементами дизайна шрифта являются гарнитура, стиль, размер (или по-английски – typeface, style, size соответственно).
Гарнитура – это групповое свойство шрифта, семейство. Типичными гарнитурами являются Helvetica или sans-serif. В одну гарнитуру обычно входят шрифты разной жирности, размера, но одинаковые по внешнему виду и дизайну.
Стиль – это термин, включающий в себя толщину штриха шрифта (или жирность) и наклон шрифта (roman – прямой шрифт, oblique – наклоненный, italic – курсив), курсив отличается от наклоненного шрифта тем, что он специально разработан с наклоном, наклоненный же получается простым наклоном букв вправо. Вот пример:
На этом рисунке в первой строчке изображены
четыре буквы шрифта Times New Roman, первая и
третья набраны без наклона, вторая и
четвертая курсивом. Сразу бросается в глаза,
что буквы набранные курсивом имеют
совершенно другую форму, чем набранные
прямым шрифтом. Во второй строке изображены
те же буквы, но уже из шрифта Tahoma, видно, что
наклонное начертание получено простым
наклоном букв вправо.
Размер – это величина характеризующая высоту шрифта, как она определяется можно видеть на рисунке:
Размер измеряется в особых типографских единицах – пунктах, один пункт приблизительно равен 1/72 дюйма (≈0,353мм).
Семейство объединяет в себя шрифты с похожими стилями. В GDI определены константы для семйств шрифтов: FF_DECORATIVE, FF_DONTCARE, FF_MODERN, FF_ROMAN, FF_SCRIPT, и FF_SWISS.
FF_DECORATIVE | декоративные шрифты, например Old English | ![]() |
FF_DONTCARE | используется, когда не важно какой шрифт использовать, берется шрифт по умолчанию. | |
FF_MODERN | моноширинный шрифт, например Courier New | ![]() |
FF_ROMAN | пропорциональный (буквы разной ширины) шрифт с засечками на концах букв, например, Times New Roman | ![]() |
FF_SCRIPT | шрифт, выглядящий как рукописный, например, Kursiv95 | ![]() |
FF_SWISS | пропорциональный шрифт без засечек, например, Verdana | ![]() |
Шрифты бывают векторные (состоящие из отрезков прямых), растровые (состоящие из точек), TrueType, PostScript. Два последних вида являются самыми распространенными форматами и в них символы шрифта хранятся как набор кривых. Кроме того, в каждом шрифте содержится ограниченное число образов букв (так называемых глифов) и поэтому не всегда шрифт можно использовать для вывода национальных символов. Практически в любом шрифте присутствует около сотни стандартных глифов, соответствующих латинскому алфавита, знакам препинания и други служебным символам.
При создании шрифта программист специальным образом указывает свои пожелания по виду, размеру, стилю шрифта, а GDI строит соответствующий логический шрифт и предоставляет программисту его описатель. Существует несколько функций для создания шрифта: ChooseFont, CreateFont, CreateFontIndirect. Первая показывает стандартный диалог выбора шрифта, вторая получает 14 параметров и на их основе создает шрифт, третья аналогично второй, только параметры получает из записи типа TLogFont. Именно третью функцию мы и будем рассматривать подробно.
{ Logical Font } LF_FACESIZE = 32; function CreateFontIndirect(const p1: TLogFont): HFONT; TLogFontA = packed record lfHeight: Longint; lfWidth: Longint; lfEscapement: Longint; lfOrientation: Longint; lfWeight: Longint; lfItalic: Byte; lfUnderline: Byte; lfStrikeOut: Byte; lfCharSet: Byte; lfOutPrecision: Byte; lfClipPrecision: Byte; lfQuality: Byte; lfPitchAndFamily: Byte; lfFaceName: array[0..LF_FACESIZE - 1] of AnsiChar; end; |
MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);Если задать параметр равным нулю, то используется значение по умолчанию, если меньше нуля, то абсолютное значение определяет высоту «чистого» символа (без выступающих элементов букв, например, Й, Ё, Î, Ã). Все станет понятнее после разбора примера ниже по тексту;
Вот пример использования этой функции. (скачать можно отсюда)
var dc : HDC; fnt, oldFnt : HFONT; lf : TLogFont; ... dc := GetDC(wnd);//получаем контекст окна lf.lfHeight := -30;{высоту задаем в пикселах, поэтому и отрицательное число, если бы мы хотели задать размер в пунктах, то написали бы: lf.lfHeight := MulDiv(PointSize, GetDeviceCaps(dc, LOGPIXELSY), 72);} lf.lfWidth := 0; //игнорируем ширину lf.lfEscapement := -90; //угол наклона строки - 9 градусов от оси X вниз lf.lfOrientation := 0; lf.lfWeight := 400; //нормальная жирность lf.lfItalic := 0; lf.lfUnderline := 0; lf.lfStrikeOut := 0; lf.lfCharSet := RUSSIAN_CHARSET; lf.lfOutPrecision := 0; lf.lfClipPrecision := 0; lf.lfQuality := ANTIALIASED_QUALITY; lf.lfFaceName := 'Arial';//имя используемого шрифта fnt := CreateFontIndirect(lf); //создаем шрифт oldFnt := SelectObject(dc, fnt);//выбираем шрифт в контекст SetBkMode(dc, TRANSPARENT); //делаем фон под буквами прозрачным TextOut(dc, 100, 100, 'Sample Пример', 13);//печатаем строку SelectObject(dc, oldFnt); //возыращаем в контекст старый шрифт ReleaseDC(wnd, dc); //освобождаем контекст |
По умолчанию начало координат находится в верхнем левом углу области, ось X направлена вправо, ось Y вниз, логическая единица равна одному пикселю устройства. Все функции GDI принимают в качестве координат или размеров именно логические единицы устройства. Вы можете изменить режим отображения при помощи функции SetMapMode:
function SetMapMode(DC: HDC; fnMapMode: Integer): Integer;
fnMapMode может принимать следующие значения:
Получить текущее значение режима можно при помощи функции GetMapMode:
function GetMapMode(DC: HDC): Integer;
Режимом по умолчанию является режим MM_TEXT, когда логические единицы совпадают с физическими. Например, если dc определяет контекст экрана монитора, тогда вызов функции:
TextOut(dc, 10, 20, 'Sample', 6); |
приведёт к появлению текста Sample на расстоянии 10 пикселов вправо и 20 пикселов вниз от верхнего левого угла рабочей области. По умолчанию начало координат всегда расположено в верхнем левом углу рабочей области.
Режим отображения определяет, как GDI преобразует логические координаты (параметры функций рисования, например) в физические координаты устройства. Далее используется терминология Windows: логические координаты – «window», координаты устройства – «viewport». Я буду далее в тексте переводить их так же, как и в русском переводе книги Ч. Петзольда, «окно» и «область вывода». Слово «окно» в таких кавычках далее будет обозначать именно логические координаты.
Режим отображения определяет, как GDI преобразует логические координаты (параметры функций рисования, например) в физические координаты устройства. Для преобразования координат устройства в логические координаты существует функция DPtoLP:
function LPtoDP(DC: HDC; var Points; Count: Integer): BOOL;
здесь Points – это массив записей типа TPoint, Count – количество точек в массиве. Для обратного преобразования (логические точки в физические) существует функция LPtoDP:
function LPtoDP(DC: HDC; var Points; Count: Integer): BOOL;
параметры Points и Count имеют такой же смысл, что и в предыдущем случае.
По умолчанию начало координат находится в верхнем левом углу области. Для сдвига начала координат существуют две функции SetViewportOrgEx и SetWindowOrgEx. Вы должны использовать только одну из этих функций, чтобы не запутаться. Вот как нужно работать с первой из них. Пусть вам нужно перенести начало отсчета с верхнего левого угла в центр области, ширина области dx, высота области dy.
На рисунке область отмечена пунктиром. Перенос делается так:
SetViewportOrgEx (dc, dx div 2, dy div 2, nil); |
Теперь логическая точка (0,0) будет отображаться в физическую точку с координатами (dx div 2, dy div 2) и можно рабочую область использовать так, как будто она имеет систему координат с началом в точке (dx div 2, dy div 2). Предполагается, что текущий режим отображения MM_TEXT, то есть логическая единица совпадает с физической. Аргументы функции SetViewportOrgEx всегда задаются в физических координатах (координатах устройства), аргументы функции SetWindowOrgEx всегда задаются в логических координатах (координатах окна). После вызова SetWindowOrgEx:
SetViewportOrgEx (dc, newX, newY, nil); |
логическая точка (–newX, –newY) cоответствует физической точке (0,0), т.е. левому верхнему углу рабочей области. Не следует использовать одновременно функции SetWindowOrgEx иSetViewportOrgEx до тех пор, пока вы полностью не разберетесь что делаете.
Как можно заметить в случае режима MM_TEXT эти две функци в некотором роде обратимы, однако в других режимах это не верно! Вызов SetViewportOrgEx выглядит аналогично (т.к. использует физические координаты), а вызов SetWindowOrgEx гораздо сложнее.
О других режимах отображения вы
можете прочитать в справке по win32api или в
книге Ч. Петзольда «Programming Windows».
Процесс рисования
сообщение WM_PAINT
Существует два способа рисования. Во-первых, описатель контекста устройства можно получить в любой точке программы, воспользовавшись функциями GetDC или GetWindowDC. Во-вторых, описатель можно получать в сообщении WM_PAINT (если вы рисуете, например, на принтере, то никакого сообщения WM_PAINT, понятно, нет). Рисунок получаемый при помощи первого способа временный, то есть он будет уничтожен при первой же перерисовке окна, используя второй способ, вы как раз и занимаетесь перерисовкой. Что же такое перерисовка? На рабочем столе Windows одновременно находятся несколько окон, которые еще и перекрывают друг друга. Вот, например, на рисунке одно окно частично перекрывет другое.
Что произойдет, когда окно с заголовком window 2 оттащить в сторону? В этом случае та часть окна, которая ранее была невидима, получит статус поврежденной или по-английски invalidate region. На рисунке она обведена синей рамкой. После этого GDI формирует и посылает два сообщения, информирующие окно, что поврежденную область надо перерисовать, вот эти сообщения WM_PAINT и WM_NCPAINT. Первое предназначается для перерисовки клиентской части окна, второе неклиентской (например, заголовка или полос прокрутки). Если ваша программа не перехватывает сообщение WM_PAINT, то окно закрашивается той кистью, которая была определена при создании класса окна, например:
wc.hbrBackground:=COLOR_BTNFACE+1;
В данном случае окно будет закрашено тем же цветом, что и фон кнопки. Сообщение WM_PAINT не может быть послано вручную, оно всегда генерируется системой. В обработчике этого сообщения всегда должны присутствовать функции BeginPaint и EndPaint, причем именно в таком порядке. После обработки этого сообщения оконная процедура должна возвратить нуль.
var ps : TPaintStruct; dc : HDC; ... WM_PAINT: begin dc := BeginPaint(wnd, ps); EndPaint(wnd, ps); result := 0; end; |
Переменная типа TPaintStruct предназначена для получения информации о рисовании. Этот тип является записью, которая определена в файле windows.pas следующим образом:
TPaintStruct = packed record hdc: HDC; fErase: BOOL; rcPaint: TRect; fRestore: BOOL; fIncUpdate: BOOL; rgbReserved: array[0..31] of Byte; end; |
Первый параметр hdc содержит описатель графического контекста, причем этот же описатель возвпащается функцией BeginPaint. Второй параметр fErase, определяет будет ли перерисовываться фон окна, если он равен TRUE, то GDI сама перерисует фон, использую текущую кисть класса окна. Третий параметр rcPaint определяет поврежденный прямоугольник, значения заданы в пикселях относительно верхнего левого угла рабочей области. Остальные три параметре не документированы и предназначены для внутреннего использования Windows. Таким образом, вам не нужно каждый раз перерисовывать всю область окна, а лишь ту его часть, что задана в параметре rcPaint. Вот пример использования сообщения WM_PAINT и поля rcPaint (скачать можно отсюда). Обработчик сообщения там очень простой – мы просто рисуем на месте поврежденного прямоугольника прямоугольник при помощи функции Rectangle, рисуем текущим пером (черным) и кистью (сплошная белая).
WM_PAINT: begin dc := BeginPaint(wnd, ps);//получаем контекст //рисуем прямоугольник Rectangle(dc, ps.rcPaint.Left, ps.rcPaint.Top, ps.rcPaint.Right, ps.rcPaint.Bottom); EndPaint(wnd, ps);//завершаем рисование result := 0; end; |
Как работает эта программа? Если вы посмотрите на исходный текст, то увидите, что кисть для закрашивания фона поставлена цвета фона кнопки, а окно имеет белый фон. Дело все в том, что когда окно создается и появляется на экране, то вся клиентская область нуждается в перерисовке (а, точнее, в первоначальной нарисовке), поэтому вся клментская часть объявляется как поврежденный прямоугольник и соответственно на его месте рисуется наш прямоугольник с черной рамкой и белам фоном. Точно такая же полная перерисовка присходит и в случае, когда окно целиком покрывается другим. Самое же интересное происходит, когда перекрывается только часть окна, специально для демонстрации этого случая в программе создано ещё одно вспомогательное окно. Если вы будете перетаскивать это окно, то на том месте, где оно только что было, увидите прямоугольник c черной рамкой:
Если вы попробуете изменить размер окна, то увидите, что все нарисованное исчезло. Это происходит оттого, что при создании класса окна мы выставили в записи TWndClassEx поле style таким образом:
wc.style:= CS_HREDRAW or CS_VREDRAW; |
и тем самым указали системе
полностью перерисовывать окно при
изменении горизонтального и
вертикального размеров окна.
Функции рисования линий
функция SetROP2
Это очень важная функция, она позволяет установить как будет присходить рисование, а точнее, как фон будет влиять на цвет рисуемого объекта. SetRop2 определяет логическре правило по которому будет вычисляться итоговый цвет в зависимости от цвета пера (кисти) и цвета пиксела под рисуемым объектом. Заголовок функции следующий:
function SetROP2(DC: HDC; fnDrawMode: Integer): Integer;
где fnDrawMode одна из следующих констант, определяющая, какой в итоге получится цвет пиксела, исходя из цвета фона и переднего плана.
Режимом по умолчанию является режим R2_COPYPEN, остальный режимы позволяют добиться многих интересных эффектов. Программу, демонстрирующую использование этой функции можно скачать отсюда. Вот скриншот с неё:
В этой программе через каждые 20
пикселов проводится вертикальная линия
одним и тем же пером синего цвета,
толщиной 12 пикселов. Горизонтальная
линия проведена тем же пером при значении
по умолчанию. Цифра соответствует
порядковому номеру соответствующей R2_
константе в соответствии с приведённым
списком. Вы можете сами сделать выводы о
том как именно действует SetRop2.
отдельные пикселы и прямые
Для рисования одного пиксела заданного цвета используется функция SetPixel:
function SetPixel(DC: HDC; X, Y: Integer; crColor: COLORREF): COLORREF;
crColor определяет желаемый цвет, возвращается цвет, которым в действительности был нарисован пиксель. Цвет получившегося пиксела устанавливается максимально приближенным к crColor и выбирается из доступных устройству.
Для рисования прямой линии служит функция LineTo:
function LineTo(DC: HDC; X, Y: Integer): BOOL;Она проводит прямую из текужего положения пера до точки (X,Y). Для рисования используется текущее перо. После выполнения этой функции перо нходится в точке (X,Y). Для прямого изменения позиции пера используется функция MoveToEx:
function MoveToEx(DC: HDC; X, Y: Integer; lpPoint: PPoint): BOOL;
она перемещает перо в позицию (X,Y), а координаты старого положения пера сохраняет в переменной lpPoint, которая является указателем на переменную типа TPoint, если этот параметр равен nil, то прежняя позиция не возвращается. Именно эта пара функций использовалась в программе демонстрирующей возможности SetRop2, вот фрагмент из неё:
dc := BeginPaint(wnd, ps);//начинаем отрисовку в соощении WM_PAINT pen := CreatePen(PS_SOLID, 12, RGB(0, 0, 255));//создаем перо толщины 12 синего цвета oldPen := SelectObject(dc, pen); //выбираем перо в контекст, запоминаем старое перо MoveToEx(dc, 0, 40, nil); //перемещаем текущую позицию пера rop := SetRop2(dc, R2_COPYPEN); LineTo(dc, 400, 40); //проводим линию MoveToEx(dc, 10, 40, nil); for i:=1 to 16 do begin SetRop2(dc, rops[i]);//в массиве rops[] хранятся 16 констант R2_XXX MoveToEx(dc, 20*i, 10, nil); LineTo(dc, 20*i, 80); s := inttostr(i); if i<10 then TextOut(dc, 20*i-4, 90, @s[1], 1) else TextOut(dc, 20*i-8, 90, @s[1], 2) end; SetRop2(dc, rop);//восстанавливаем прежнее значение SelectObject(dc, oldPen); EndPaint(wnd, ps);//заканчиваем отрисовку |
Для получения текущих координат пера используется функция GetCurrentPositionEx:
var pt : TPoint; ... GetCurrentPositionEx(dc, @pt); //теперь в pt хранится положение пера |
Для рисования ломаных линий существует функции Polyline, PolylineTo и PolyPolyline:
function Polyline(DC: HDC; var Points; Count: Integer): BOOL; function PolyLineTo(DC: HDC; const Points; Count: DWORD): BOOL; function PolyPolyline(DC: HDC; const PointStructs; const Points; p4: DWORD): BOOL;
здесь Points – массив из Count
точек (переменных типа TPoint). Функция Polyline
просто последовательно соединяет точки,
перечисленные в массиве прямыми, не меняя
при этом положение пера. Функция PolylineTo
использует в качестве стартовой точки не
первую точку массива, а текущее положение
пера, после её выполнения текущее
положение указателя перемещается в
последнюю из точек массива. Функция PolyPolyline
предназаначена для рисования нескольких
не связанных между собой ломаных. Чтобы
нарисовать замкнутый многоугольник,
используйте Polyline, при этом в массиве
точек первая должна совпадать с
последней.
дуги
Для рисования дуги эллипса существуют функция Arc:
function Arc(hDC: HDC; xLeft, yTop, xRight, yBottom, startX, startY, endX, endY: Integer): BOOL;
Дуга рисуется так, как показано на рисунке:
Координаты xLeft, yTop, xRight, yBottom определяют прямоугольник, ограничивающий эддипс, дуга которого строится. startX, startY – это координаты точки, определяющей начальную точку дуги, GDI начинает дугу с точки пересечения эллипса (вписанного в данный прямоугольник) и прямой, проходящей через центр эллипса и точку (startX, startY). Соответственно endX, endY определяют конечную точку дуги аналогичным образом. Дуга всегда рисуется в направлении против часовой стрелки. Для рисования используется текущее перо. Вот пример того, как рисуется дуга (скачать здесь).
dc := BeginPaint(wnd, ps); //создаем перо коричневого цвета, толщиной 2 pen := CreatePen(PS_SOLID, 2, RGB($a1, $70, $1b)); oldPen := SelectObject(dc, pen); //(105, 55) - центр эллипса //(150,120) - начальная точка //(125,10) - конечная точка Arc(dc, 10, 10, 200,100, 150, 120, 250, 10); //дуга SelectObject(dc, oldPen); //строим линии к точкам, чтобы показать как строится дуга MoveToEx(dc, 105, 55, nil); LineTo(dc, 150, 120); MoveToEx(dc, 105, 55, nil); LineTo(dc, 250, 10); EndPaint(wnd, ps); |
Для рисования окружности тоже
используется функция Arc, нужно только
указать одинаковую начальную и стартовую
точки.
сплайны Безье
Для построения более сложных линий, чем дуги и прямые используются так называемые сплайны Безье. Они предназаначены для аппроксимации самых разных кривых с достаточной степенью точности. Формулы, по которым строятся сплайны, вы можете найти в любом учебнике по компьютерной графике. Плоский сплайн Безбе определяется четырьмя точками (две – начальная и конечная, и две контрольные), неформально две контрольные точки можно представить магнитами, которые оттягивают на себя прямую, соединяющую начальную и конечную точки.
Для построения сплайнов в Windows есть функция PolyBezier:
function PolyBezier(DC: HDC; const Points; Count: DWORD): BOOL;
Здесь параметр Points – это массив переменных типа TPoint, Count количество кривых, которые нужно построить. Первая точка в массиве Points определяет точку, из которой начнется построение первой кривой, следующие две точки – контрольные, четвёртая – конечная точка первой кривой. Далее для каждой последующей кривой нужно только три точки, первой точкой следующей кривой является последняя точка предыдущей. Линия строится текущим пером. Вот пример (скачать здесь)
//(10,10) - начальная точка //(250,250) - конечная точка //(170,35) и (5,250) - контрольные точки var apt : array[0..3] of TPoint = ((x:10;y:10), (x:170;y:35), (x:5;y:250), (x:250;y:250)); ... WM_PAINT: begin dc := BeginPaint(wnd, ps); PolyBezier(dc, apt, 4); //рисуем один сплайн //рисуем узловые (контрольные) точки красным цветом SetPixel(dc, apt[1].x, apt[1].y, RGB(255, 0, 0)); SetPixel(dc, apt[2].x, apt[2].y, RGB(255, 0, 0)); EndPaint(wnd, ps); result := 0; end; |
Функции рисования закрашенных фигур
эллипс
Я не буду вдаваться здесь в подробности из аналитической геометрии, скажу только, что эллипс, это геометрическое место точек, сумма расстояний от которых до двух фиксированных точек остается постоянной. В Windows для построения закрашенного эллипса используется функция Ellipse:
function Ellipse(DC: HDC; X1, Y1, X2, Y2: Integer): BOOL;
Эллипс строится по
ограничивающему его прямоугольнику, то
есть вы можете построить только эллипс, с
осями, параллельными осям координат.
Ограничивающий прямоугольник – это
минимальный прямоугольник полностью
окружающий в данном случае эллипс.
Граница эллипса рисуется текущим пером,
внутренность заполняется текущей кистью.
сегмент (chord)
Сегмент – это часть эллипса, отсеченная прямой, называемой секущей. Для рисования сегмента существует функция Chord:
function Chord(DC: HDC; xLeft, yTop, xRight, yBottom, startX, startY, endX, endY: Integer): BOOL;
Здесь xLeft, yTop, xRight, yBottom –
координаты ограничивающего
прямоугольника, котрый определяет эллипс,
из которого, в свою очередь, вырезается
сегмент. startX, startY, endX, endY определяют
дугу, которая ограничивает сегмент.
Делается это точно так же, как и в функции Arc.
Граница сегмента рисуется текущим пером,
внутренность заполняется текущей кистью.
сектор (pie)
Сектор – это часть эллипса, отсеченная двумя его радиусами. Для рисования сектора существует функция Pie:
function Pie(DC: HDC; xLeft, yTop, xRight, yBottom, startX, startY, endX, endY: Integer): BOOL;
Здесь xLeft, yTop, xRight, yBottom –
координаты ограничивающего
прямоугольника, котрый определяет эллипс,
из которого, в свою очередь, вырезается
сектор. startX, startY, endX, endY определяют
дугу, которая ограничивает сектор.
Делается это точно так же, как и в функции Arc.
Граница сектора рисуется текущим пером,
внутренность заполняется текущей кистью.
многоугольник
Многоугольник – это замкнутая фигура, граница которой состоит из прямых. Для рисования многоугольников существует функция Polygon:
function Polygon(DC: HDC; var Points; Count: Integer): BOOL;
Здесь Points массив состоящий из переменных типа TPoint, и определяющий вершины многоугольника; Count – число вершин многоугольника, должно быть больше либо равно двум. Последняя построенная вершина автоматически соединяется с первой. Граница многоугольника рисуется текущим пером, внутренность заполняется текущей кистью. Если в многоугольнике нет самопересечений, то он просто заполняется текущей кистью; если же самопересечения есть, то режим закрашивания определяется при помощи функции SetPolyFillMode:
function SetPolyFillMode(DC: HDC; PolyFillMode: Integer): Integer;
Здесь PolyFillMode – режим закрашивания многоугольников. Может принимать два значения:
Вот пример использования этих функций (скачать можно здесь) и скриншот программы:
WM_PAINT: begin dc := BeginPaint(wnd, ps); Polygon(dc, apt, 6);//рисуем многоугольник SetPolyFillMode(dc, WINDING);//меняем режим закраски for i:=0 to 5 do inc(apt[i].x, 300);//сдвигаем все точки на 300 пикселов Polygon(dc, apt, 6);//еще раз рисуем for i:=0 to 5 do dec(apt[i].x, 300); //сдвигаем назад EndPaint(wnd, ps); end; |
Для рисования прямоугольника существует функция Rectangle:
function Rectangle(DC: HDC; X1, Y1, X2, Y2: Integer): BOOL;
Здесь X1,Y1 – верхний левый угол прямоугольника; X2,Y2 – нижний правый. Граница рисуется текущим пером, внутренность заполняется текущей кистью.
Для рисования прямоугольника со скругленными углами используется функция RoundRect:
function RoundRect(DC: HDC; X1, Y1, X2, Y2, Width, Height: Integer): BOOL;
Здесь X1,Y1 – верхний левый угол прямоугольника; X2,Y2 – нижний правый, размер скругления определяется параметрами Width и Height: