Я уже говорил, что в Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT. Каким образом осуществляется перерисовка?
Обычно используют один из трех методов:
- Рабочая область может быть восстановлена, если ее содержимое формируется с помощью
каких-либо вычислений;
- последовательность событий, формирующих рабочую область, может быть сохранена, а
затем "проиграна" сколь угодно раз;
- можно создавать виртуальное окно и направлять весь вывод в виртуальное окно, а при
получении основным окном сообщения WM_PAINT копировать содержимое виртуального окна в основное.
Думаю, что читатель догадался, что в качестве виртуального окна используется контекст в памяти.
Как его копировать, мы уже знаем. Но как рисовать на нем?
Для установки текущей позиции используется функция MoveToEx(). В файле заголовков wingdi.h эта функция описывается следующим образом:
WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);Первый аргумент - это контекст устройства, на котором мы будем рисовать, второй и третий - координаты точки, в которую мы устанавливаем текущую графическую позицию. Последний аргумент - указатель на структуру типа POINT, в которую функция запишет координаты старой текущей позиции. Структура типа POINT выглядит следующим образом:
POINT struc ptX dd ? ptY dd ? POINT ends
Прорисовать один пиксель в определенной позиции мы можем с помощью вызова функции SetPixel(), описанной в wingdi.h:
WINGDIAPI COLORREF WINAPI SetPixel(HDC, int, int, COLORREF);Первый три аргумента очевидны - контекст устройства вывода и координаты прорисовываемого пикселя. Но что такое COLORREF?
Здесь следует пояснить, что каждый пиксель на экране состоит из трех микроточек - красной, зеленой и синей. Каждая из этих микроточек может светится с интенсивностью от 0 (микроточка не светится) до 255 (максимальная яркость). Например, если светится только красная составляющая, то получаются цвета от темно-бордового (почти черного) до ярко красного. Комбинируя микроточки и их интенсивность, мы можем определить почти 17 миллионов цветов (будут ли они все поддерживаться на компьютере читателя, определяется видеоподсистемой компьютера). Обычно в таких случаях говорят об RGB-значениях цвета (red, green, blue - красный, зеленый, голубой).
Вернемся к COLORREF. Опять обратимся к заголовочному файлу, но на сей раз не к wingdi.h, а к windef.h:
typedef DWORD COLORREF;Понятно, что COLORREF - это двойное слово. Оно кодируется следующим образом:
0x00bbggrrт.е. младший байт определяет интенсивность красного, второй - зеленого, третий - синего цвета.
При нормальном завершении функция возвращает предыдущее значение цвета пикселя. Если возвращаемое
значение равно -1, то это говорит либо о возникновении ошибки, либо о том, что координаты пикселя
вышли за пределы рабочей области окна.
Рисование графических примитивов производится с помощью перьев. В Windows'95 есть три предопределенных пера - черное (BLACK_PEN), белое (WHITE_PEN) и прозрачное (NULL_PEN). При создании окна по умолчанию ему присваивается черное перо. Хэндл каждого из них может быть получен с помощью функции GetStockObject(). Естественно, что программиста не может удовлетворитьь столь малое число перьев, поэтому для прорисовки линий можно воспользоваться пером, созданным в программе посредством вызова функции CreatePen(). Как всегда, обращаемся к файлам заголовков, в данном случае - к файлу wingdi.h:
WINGDIAPI HPEN WINAPI CreatePen(int, int, COLORREF);Первый аргумент определяет стиль кисти. В wingdi.h эти стили описаны достаточно образно. Для того чтобы сохранить стиль этого описания (не путать со стилем кисти) я включил его третьим столбцом в таблице:
Возможные стили кисти
Стиль пера | Значение | Описание | Эффект |
PS_SOLID | 0 | ______ | Сплошная линия |
PS_DASH | 1 | ------ | Пунктирная линия |
PS_DOT | 2 | ...... | Линия из точек |
PS_DASHDOT | 3 | _._._._ | Штрих-пунктирная линия (тире-точка) |
PS_DASHDOTDOT | 4 | _.._.._ | Штрих-пунктирная линия (тире-точка-точка) |
PS_NULL | 5 | Прозрачное перо | |
PS_INSIDEFRAME | 6 | При рисовании замкнутой фигуры граница фигуры будет определятся по внешнему краю, а не по середине линии (если толщина пера более 1 пикселя) |
Второй аргумент функции CreatePen() - толщина пера в логических единицах. Если этот аргумент равен 0, то толщина пера делается равной одному пикселю.
Третий аргумент - цвет чернил. Теперь для того, чтобы мы могли использовать наше перо, необходимо
сделать его текущим в контексте устройства. Делается это уже давно знакомой нам функцией
SelectObject(). После того, как мы отработаем с пером, необходимо удалить его, вызвав функцию
DeleteObject().
Нарисовать линию можно с помощью функции LineTo(). Она описана в файле wingdi.h:
WINGDIAPI BOOL WINAPI LineTo(HDC, int, int);Первый аргумент - контекст устройства. Второй и третий аргументы - координаты точки, ДО КОТОРОЙ ОТ ТЕКУЩЕЙ ПОЗИЦИИ будет проведена линия. При успешной завершении функция возвращает TRUE.
Но здесь же возникает вопрос: где будет находиться текущая позиция после успешного выполнения
функции? А будет она находиться там, где закончилась линия. Это сделано для того, чтобы легко
можно было рисовать ломаные линии. В таком случае не нужно многократно вызывать функцию
MoveToEx() для установления новой текущей позиции.
Прямоугольник можно нарисовать, обратившись к функции Rectangle(). Ее описание содержится в файле wingdi.h:
WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);Аргумент первый понятен без объяснений - хэндл контекста устройства. Остальные аргументы - координаты верхнего левого и нижнего правого углов прямоугольника. TRUE возвращает при нормальном завершении операции. Прямоугольник автоматически заполняется цветом и способом, определяемым текущей кистью.
Для рисования эллипса необходимо вызвать функцию Ellipse(), которая в wingdi.h описывается следующим образом:
WINGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);Первый аргумент - это, как всегда, контекст устройства. Для того чтобы понять, как определяется эллипс, предлагаю читателю обратиться к рисунку:
Как видно из рисунка, эллипс ограничен прямоугольником. Именно через координаты этого прямоугольника и определяется прорисовываемый эллипс. Второй и третий аргументы - координаты левого верхнего угла прямоугольника (на рисунке обозначены как UpX, UpY), четвертый и пятый аргументы - координаты нижнего правого угла (на рисунке обозначены как LowX, LowY).
Окружность является частным случаем эллипса. И в данном случае, если мы определим прямоугольник, у которого ширина равна высоте, т.е. квадрат, вместо эллипса получим окружность.
Как эллипс, так и окружность после прорисовки заполняются цветом и атрибутами
текущей кисти.
Прямоугольник с закругленными краями рисуется с помощью функции RoundRect(). Из файла wingdi.h добываем ее описание.
WINGDIAPI BOOL WINAPI RoundRect(HDC, int, int, int, int, int, int);Первые пять аргументов полностью идентичны аргументам функции Rectangle(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги. После прорисовки прямоугольник закрашивается текущей кистью. В случае успешного завершения функция возвращает TRUE.
Возьмем из файла wingdi.h описание функции Arc(), которая используется для рисования дуги:
WINGDIAPI BOOL WINAPI Arc(HDC, int, int, int, int, int, int, int, int);Первые пять аргументов полностью аналогичны аргументам функции Ellipse(). Непосредственно дуга определяется еще двумя точками. Первая - начало дуги - находится на пересечении эллипса, частью которого является дуга, и прямой, проходящей через центр прямоугольника и точку начала дуги. На рисунке начало дуги обозначено StartX, StartY. Вторая - конец дуги - определяется аналогично. Конец дуги обозначен EndX, EndY. Таким образом, для прорисовки дуги необходимо сначала определить точки StartX, StartY и EndX, EndY, после чего прорисовывать дугу. Дуга прорисовывается против часовой стрелки.
У функции Pie(), которая применяется для рисования сектора эллипса, набор
аргументов и их обозначения абсолютно идентичны функции Arc().
Как читатель уже знает, заполнение замкнутых графических объектов происходит с помощью текущей кисти. Программист может использовать предопределенную кисть, а может создать свою собственную, после чего сделать ее текущей с помощью функции SelectObject().
Простейшим видом кисти является так называемая сплошная кисть, которая создается с помощью функции CreateSolidBrush():
WINGDIAPI HBRUSH WINAPI CreateSolidBrush(COLORREF);Единственный аргумент этой функции - цвет кисти (может, лучше сказать не кисти, а краски?).
Штриховая кисть создается с помощью функции CreateHatchBrush():
WINGDIAPI HBRUSH WINAPI CreateHatchBrush(int, COLORREF);Первый аргумент этой функции - стиль штриховки. Возможные стили приведены в таблице.
Стили штриховки
Стиль штриховки | Значение | Описание | Эффект |
HS_HORIZONTAL | 0 | ---- | Горизонтальная штриховка |
HS_VERTICAL | 1 | ||||| | Вертикальная штриховка |
HS_FDIAGONAL | 2 | \\ | Наклонная слева направо штриховка |
HS_BDIAGONAL | 3 | ///// | Наклонная справа налево штриховка |
HS_CROSS | 4 | +++++ | Штриховка крестиком |
HS_DIAGCROSS | 5 | xxxxx | Штриховка косым крестиком |
Второй аргумент указывает цвет штриховки.
И наконец, с помощью функции CreatePatternBrush() мы можем создать кисть, которая при заполнении будет использовать bitmap. В wingdi.h она описана следующим образом:
WINGDIAPI HBRUSH WINAPI CreatePatternBrush(HBITMAP);Уже по типу аргумента видно, что единственным аргументом этой функции является хэндл bitmap'а.
Эти три функции при успешном завершении возвращают хэндл созданной кисти. В том случае, если
произошла какая-то ошибка, возвращаемое значение равно NULL.
Текст программы:
.386 .model flat, stdcall include win32.inc extrn BeginPaint:PROC extrn BitBlt:PROC extrn CreateCompatibleBitmap:PROC extrn CreateCompatibleDC:PROC extrn CreateHatchBrush:PROC extrn CreatePen:PROC extrn CreateWindowExA:PROC extrn DefWindowProcA:PROC extrn DeleteDC:PROC extrn DeleteObject:PROC extrn DispatchMessageA:PROC extrn Ellipse:PROC extrn EndPaint:PROC extrn ExitProcess:PROC extrn GetClientRect:PROC extrn GetMessageA:PROC extrn GetModuleHandleA:PROC extrn GetStockObject:PROC extrn LineTo:PROC extrn LoadCursorA:PROC extrn LoadIconA:PROC extrn MoveToEx:PROC extrn PatBlt:PROC extrn PostQuitMessage:PROC extrn Rectangle:PROC extrn RegisterClassA:PROC extrn SelectObject:PROC extrn SetPixel:PROC extrn ShowWindow:PROC extrn TranslateMessage:PROC extrn UpdateWindow:PROC BITMAP struct bmType dd ? bmWidth dd ? bmHeight dd ? bmWidthBytes dd ? bmPlanes dw ? bmBitsPixel dw ? bmBits dd ? BITMAP ends SRCCOPY = 00CC0020h PATCOPY = 00F00021h TRUE = 1 FALSE = 0 .data newhwnd dd 0 msg MSGSTRUCT wc WNDCLASS Rect RECT hDC dd ? hCompatibleDC dd ? PaintStruct PAINTSTRUCT hCompatibleBitmap dd ? hBitmap dd ? hOldBitmap dd ? hOldPen dd ? hOldBrush dd ? Pens dd 2 dup(?) Brushes dd 2 dup(?) hInstance dd 0 szTitleName db 'GraphDemo', 0 szClassName db 'ASMCLASS32',0 x dd ? y dd ? p dd ? .code ;----------------------------------------------------------------------------- start: call GetModuleHandleA, 0 mov [hInstance], eax ; initialize the WndClass structure mov [wc.clsStyle], CS_HREDRAW + CS_VREDRAW mov [wc.clsLpfnWndProc], offset GraphDemoWndProc mov [wc.clsCbClsExtra], 0 mov [wc.clsCbWndExtra], 0 mov eax, [hInstance] mov [wc.clsHInstance], eax call LoadIconA, 0, IDI_APPLICATION mov [wc.clsHIcon], eax call LoadCursorA, 0 ,IDC_ARROW mov [wc.clsHCursor], eax call GetStockObject, WHITE_BRUSH mov [wc.clsHbrBackground], eax mov [wc.clsLpszMenuName], 0 mov [wc.clsLpszClassName], offset szClassName call RegisterClassA, offset wc call CreateWindowExA, 0,offset szClassName,offset szTitleName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0, [hInstance], 0 mov [newhwnd], eax call ShowWindow, [newhwnd], SW_SHOWNORMAL call UpdateWindow, [newhwnd] msg_loop: call GetMessageA, offset msg, 0, 0, 0 .if eax != 0 call TranslateMessage, offset msg call DispatchMessageA, offset msg jmp msg_loop .endif call ExitProcess, [msg.msWPARAM] ;----------------------------------------------------------------------------- GraphDemoWndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD cmp [wmsg], WM_PAINT je wmpaint cmp [wmsg], WM_DESTROY je wmdestroy call DefWindowProcA, [hwnd],[wmsg],[wparam],[lparam] jmp finish wmpaint: ;--------------------------------------- call CreatePen, 1,0,0FF0000h mov [dword ptr Pens], eax call CreatePen, 3,0,000000h mov [dword ptr Pens+4], eax call CreateHatchBrush, 1,00000FFh mov [dword ptr Brushes], eax call CreateHatchBrush, 2,0FF0080h mov [dword ptr Brushes+4], eax ;--------------------------------------- call GetClientRect, [hwnd], offset Rect call BeginPaint, [hwnd], offset PaintStruct mov [hDC], eax call CreateCompatibleDC, [hDC] mov [hCompatibleDC], eax call GetClientRect, [hwnd], offset Rect call CreateCompatibleBitmap, [hDC],[Rect.rcRight],[Rect.rcBottom] mov [hCompatibleBitmap], eax call SelectObject, [hCompatibleDC],[hCompatibleBitmap] mov [hOldBitmap], eax call PatBlt, [hCompatibleDC],0,0,[Rect.rcRight], [Rect.rcBottom], PATCOPY ;--------------------------------------- mov [x], 0 mov [y], 0 mov [p], 0 @@lp2: call SetPixel, [hCompatibleDC],[x],[y],00FF0000h .if eax == -1 mov [x], 0 add [y], 10h .endif add [x], 10h .if eax == -1 cmp [p], -1 jz @@lp3 .endif mov [p], eax jmp @@lp2 @@lp3: ;--------------------------------------- call SelectObject, [hCompatibleDC],[dword ptr Pens] mov [hOldPen], eax call MoveToEx, [hCompatibleDC],[Rect.rcLeft],[Rect.rcTop],0 call LineTo, [hCompatibleDC],[Rect.rcRight],[Rect.rcBottom] call SelectObject, [hCompatibleDC],[dword ptr Pens+4] call MoveToEx, [hCompatibleDC],[Rect.rcRight],[Rect.rcTop],0 call LineTo, [hCompatibleDC],[Rect.rcLeft],[Rect.rcBottom] call SelectObject, [hCompatibleDC],[hOldPen] ;======================================= call SelectObject, [hCompatibleDC],[dword ptr Brushes] mov [hOldBrush], eax call Rectangle, [hCompatibleDC],100,75,450,200 call SelectObject, [hCompatibleDC],[dword ptr Brushes+4] call Ellipse, [hCompatibleDC],50,50,200,100 call SelectObject, [hCompatibleDC],[hOldBrush] ;======================================= call BitBlt, [hDC],[PaintStruct.PSrcPaint.rcLeft], [PaintStruct.PSrcPaint.rcTop], [PaintStruct.PSrcPaint.rcRight], [PaintStruct.PSrcPaint.rcBottom], [hCompatibleDC], [PaintStruct.PSrcPaint.rcLeft], [PaintStruct.PSrcPaint.rcTop], SRCCOPY call DeleteObject, [dword ptr Pens] call DeleteObject, [dword ptr Pens+4] call DeleteObject, [dword ptr Brushes] call DeleteObject, [dword ptr Brushes+4] call SelectObject, [hCompatibleDC], [hOldBitmap] call DeleteObject, [hCompatibleBitmap] call DeleteDC, [hCompatibleDC] call EndPaint, [hwnd],offset PaintStruct mov eax, 0 jmp finish wmdestroy: call DeleteObject, [hBitmap] call PostQuitMessage, 0 mov eax, 0 finish: ret GraphDemoWndProc endp ends end start