Шаг 22.
Графические образы

    На этом шаге мы рассмотрим общие принципы работы с графикой.

    Этот шаг посвящается графике. Впрочем, основы графики в Windows, в принципе, достаточно тривиальны, поэтому мы рассмотрим один простой пример - на вывод графических образов. Но в начале мы изложим некоторые опорные моменты:

  1. Система координат для вывода графических образов такая же, как и для ввода текстовой информации. Координаты измеряются в логических единицах, которые по умолчанию совпадают с пикселями. При желании эту пропорцию можно изменить.

  2. Цвет рисования образуется тремя способами. При использовании функции SetPixel задается цвет данной точки. Для линий необходимо задать цвет пера. Для задания цвета замкнутых графических объектов следует задать цвет кисти.

  3. Перо создается при помощи функции CreatePen, кисть - при помощи CreateSolidBrush (мы ее уже использовали). Для создания разноцветной картинки можно заранее создать несколько кистей и перьев, а затем в нужный момент выбирать при помощи функции SelectObject (мы также уже использовали эту функцию).

  4. Для рисования можно использовать следующие функции API:
    • SetPixel - установить заданный цвет пикселя;
    • LineTo - провести линию от текущей точки до точки с заданными координатами, которая, в свою очередь, становится текущей;
    • MoveToEx - сменить текущую точку;
    • Arc - рисование дуги;
    • Rectangle - нарисовать прямоугольник;
    • RoundRect - нарисовать прямоугольник с округленными углами;
    • Ellipse, Pie - рисование эллипсов и секторов эллипсов.


  5. Если при рисовании замкнутой фигуры был установлен цвет кисти, отличный от цвета основного фона, то замкнутая фигура окрашивается этим цветом.

  6. Для установки соотношения между логическими единицами и пикселями используется функция SetMapMode.

  7. Можно установить область вывода при помощи функции SetViewportExtEx. С помощью функции SetViewportOrgEx можно задать начало области ввода.

    После всего сказанного пора продемонстрировать программу. Программа достаточно проста, но в ней заложены основы работы с графикой. По щелчку левой кнопки мыши сначала появляется горизонтальная линия, по второму щелчку - наклонная линия, по третьему щелчку - заполненный прямоугольник. Результат работы программы приведен на рисунке 1.


Рис.1. Результат работы приложения


    Заголовочный файл для приложения, содержащий определения констант, внешних процедур и структур (его имя pr22_1.asm):
;Константы.
;Сообщение приходит при  закрытии окна.
WM_DESTROY	equ 2
;Сообщение приходит при создании окна.
WM_CREATE       equ 1 
;Сообщение при щелчке левой кнопкой мыши в области окна.
WM_LBUTTONDOWN  equ 201h
;Сообщение приходит при  перерисовке окна.
WM_PAINT        equ 0Fh
;Свойства окна.
CS_VREDRAW	    equ 1h
CS_HREDRAW	    equ 2h
CS_GLOBALCLASS      equ 4000h
WS_OVERLAPPEDWINDOW equ 000CF0000h
stylcl              equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
DX0                 equ 600
DY0                 equ 400
;Компоненты цветов.
RGBW                equ (50 or (50 shl 8)) or (255 shl 16);Цвет окна.
RGBR                equ 150 ;Цвет региона.
RGBL                equ 0   ;Цвет линии.
RGBP                equ 255 or (100 shl 8);Цвет точки.
;Идентификатор стандартной пиктограммы.
IDI_APPLICATION   equ  32512
;Идентификатор курсора.
IDC_CROSS         equ  32515
;Режим показа окна - нормальный.
SW_SHOWNORMAL	  equ  1

;Прототипы внешних процедур.
EXTERN  CreateWindowExA@48:NEAR
EXTERN  DefWindowProcA@16:NEAR
EXTERN  DispatchMessageA@4:NEAR
EXTERN  ExitProcess@4:NEAR
EXTERN  GetMessageA@16:NEAR
EXTERN  GetModuleHandleA@4:NEAR
EXTERN  LoadCursorA@8:NEAR
EXTERN  LoadIconA@8:NEAR
EXTERN  PostQuitMessage@4:NEAR 
EXTERN  RegisterClassA@4:NEAR
EXTERN  ShowWindow@8:NEAR
EXTERN  TranslateMessage@4:NEAR
EXTERN  UpdateWindow@4:NEAR
EXTERN  BeginPaint@8:NEAR
EXTERN  EndPaint@8:NEAR
EXTERN  GetStockObject@4:NEAR
EXTERN  CreateSolidBrush@4:NEAR
EXTERN  GetSystemMetrics@4:NEAR
EXTERN  GetDC@4:NEAR
EXTERN  CreateCompatibleDC@4:NEAR
EXTERN  SelectObject@8:NEAR
EXTERN  CreateCompatibleBitmap@12:NEAR
EXTERN  PatBlt@24:NEAR
EXTERN  BitBlt@36:NEAR
EXTERN  ReleaseDC@8:NEAR
EXTERN  DeleteObject@4:NEAR
EXTERN  InvalidateRect@12:NEAR
EXTERN  DeleteDC@4:NEAR
EXTERN  CreatePen@12:NEAR
EXTERN  SetPixel@16:NEAR
EXTERN  LineTo@12:NEAR
EXTERN  MoveToEx@16:NEAR
EXTERN  Rectangle@20:NEAR
;Структуры
;Структура сообщения.
MSGSTRUCT  STRUC	
           MSHWND     DD ? ;Идентификатор окна, получающего сообщение.
           MSMESSAGE  DD ? ;Идентификатор сообщения.
           MSWPARAM   DD ? ;Доп. информация о сообщении.
           MSLPARAM   DD ? ;Доп. информация о сообщении.
           MSTIME     DD ? ;Время посылки сообщения.
           MSPT       DD ? ;Положение курсора во время посылки сообщения.
MSGSTRUCT ENDS	
;-----------------------
WNDCLASS  STRUC	
           CLSSTYLE     DD ? ;Стиль окна.
           CLWNDPROC    DD ? ;Указатель на процедуру окна.
           CLSCBCLSEX   DD ? ;Информация о доп. байтах для данной структуры.
           CLSCBWNDEX   DD ? ;Информация о доп. байтах для окна.
           CLSHINST     DD ? ;Дескриптор приложения.
           CLSHICON     DD ? ;Идентификатор иконки окна.
           CLSHCURSOR   DD ? ;Идентификатор курсора окна.
           CLBKGROUND   DD ? ;Идентификатор кисти окна.
           CLMENNAME    DD ? ;Имя-идентификатор меню.
           CLNAME       DD ? ;Специфицирует имя класса окон.
WNDCLASS  ENDS 
;-------------------------------- 
PAINTSTR  STRUC
    hdc     DWORD 0 
    fErase  DWORD 0
    left    DWORD 0
    top     DWORD 0
    right   DWORD 0
    bottom  DWORD 0
    fRes    DWORD 0
    fIncUp  DWORD 0
    Reserv DB 32 dup(0)
PAINTSTR  ENDS
;--------------------------
RECT STRUC 
        L   DWORD ?  ;X - левого верхнего угла.
        T   DWORD ?  ;Y - левого верхнего угла.
        R   DWORD ?  ;Х - правого нижнего угла.
        B   DWORD ?  ;Y - правого нижнего угла.
RECT ENDS
Текст этого модуля можно взять здесь.


    Основной файл приложения содержит подключение файла pr22_1.asm (его имя pr22_2.asm):
.386P
;Плоская модель.
.MODEL FLAT, STDCALL
include pr22_1.asm
;Директивы компоновщику для подключения библиотек.
includelib c:\masm32\lib\user32.lib 
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\gdi32.lib
;------------------------------------------------
;Сегмент данных. 
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
     NEWHWND     DD 0
     MSG         MSGSTRUCT <?>
     WC          WNDCLASS <?>
     PNT         PAINTSTR <?>
     HINST       DD 0 ;Здесь хранится дескриптор приложения.
     TITLENAME   BYTE 'Графика в окне',0
     NAM         BYTE 'CLASS32',0
     XT          DWORD 30
     YT          DWORD 30
     XM          DWORD ?
     YM          DWORD ?
     HDC         DWORD ?
     MEMDC       DWORD ?
     HPEN        DWORD ?
     HBRUSH      DWORD ?
     P           DWORD 0 ;Признак вывода.
     XP          DWORD ?
     YP          DWORD ?
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
;Получить дескриптор приложения.
     PUSH  0
     CALL  GetModuleHandleA@4
     MOV   [HINST], EAX
REG_CLASS:
;Заполнить структуру окна.
;Стиль.
     MOV [WC.CLSSTYLE],stylcl 
;Процедура обработки сообщений.
     MOV   [WC.CLWNDPROC], OFFSET WNDPROC
     MOV   [WC.CLSCBCLSEX], 0
     MOV   [WC.CLSCBWNDEX], 0
     MOV   EAX, [HINST]
     MOV   [WC.CLSHINST], EAX
 
;------------ пиктограмма окна
    PUSH   IDI_APPLICATION
    PUSH   0
    CALL   LoadIconA@8
    MOV    [WC.CLSHICON], EAX
;------------ курсор окна
    PUSH   IDC_CROSS
    PUSH   0
    CALL   LoadCursorA@8
    MOV    [WC.CLSHCURSOR], EAX
;------------
    PUSH   RGBW ;Цвет кисти.
    CALL   CreateSolidBrush@4;Создать кисть.
    MOV    [WC.CLBKGROUND],EAX 
    MOV    DWORD PTR [WC.CLMENNAME],0
    MOV    DWORD PTR [WC.CLNAME], OFFSET  NAM
    PUSH   OFFSET WC
    CALL   RegisterClassA@4
;Создать окно зарегистрированного класса.
    PUSH   0
    PUSH   [HINST]
    PUSH   0
    PUSH   0
    PUSH   DY0      ; DY0 - высота окна.
    PUSH   DX0      ; DX0 - ширина окна.
    PUSH   100      ; Координата Y.
    PUSH   100      ; Координата X.
    PUSH   WS_OVERLAPPEDWINDOW
    PUSH   OFFSET TITLENAME  ;Имя окна.
    PUSH   OFFSET NAM        ;Имя класса.
    PUSH   0
    CALL   CreateWindowExA@48
;Проверка на ошибку.
    CMP    EAX,0
    JZ    _ERR
    MOV   [NEWHWND], EAX  ;Дескриптор окна.
;------------------------------------
    PUSH  SW_SHOWNORMAL
    PUSH  [NEWHWND]
    CALL  ShowWindow@8    ;Показать созданное окно.
;------------------------------------
    PUSH  [NEWHWND]
    CALL UpdateWindow@4   ;Команда перерисовать видимую
                          ;часть окна, сообщение WM_PAINT.
;Цикл обработки сообщений 
MSG_LOOP:
    PUSH 0
    PUSH 0
    PUSH 0
    PUSH OFFSET MSG
    CALL GetMessageA@16
    CMP  EAX, 0
    JE   END_LOOP
    PUSH OFFSET MSG
    CALL TranslateMessage@4
    PUSH OFFSET MSG
    CALL DispatchMessageA@4
    JMP  MSG_LOOP 
END_LOOP: 
;Выход из программы (закрыть процесс).
    PUSH [MSG.MSWPARAM]
    CALL ExitProcess@4
_ERR:
    JMP  END_LOOP
;-----------------------------------------
;Процедура окна.
;Расположение параметров в стеке: 
;[ЕВР+14Н] LPARAM 
;[ЕВР+10Н] WPARAM 
;[ЕВР+0СН] MES 
;[ЕВР+08H] HWND 
WNDPROC PROC
        PUSH EBP
        MOV  EBP, ESP
        PUSH EBX
        PUSH ESI
        PUSH EDI
        CMP  DWORD PTR [EBP+0CH], WM_DESTROY
        JE   WMDESTROY
        CMP  DWORD PTR [EBP+0CH], WM_CREATE
        JE   WMCREATE
        CMP  DWORD PTR [EBP+0CH], WM_PAINT
        JE   WMPAINT
        CMP  DWORD PTR [EBP+0CH], WM_LBUTTONDOWN
        JE   LBUTTON
        JMP  DEFWNDPROC
LBUTTON:
        CMP  P,0 ;Какой щелчок левой кнопки мыши?
        JNE  F1
;Линия точками (горизонтальная).
        MOV  YP,50   ;Y 
        MOV  XP,10   ;X 
        MOV  ECX,200 ;Длина линии.
LL:
        PUSH ECX 
        PUSH RGBP 
        PUSH YP 
        PUSH XP 
        PUSH MEMDC 
        CALL SetPixel@16 
        INC  XP 
        POP  ECX 
        LOOP LL
        INC  P 
        JMP  F3 
F1:
        CMP  P,1 
        JNE  F2
;Ввначале установим текущие координаты на конец предыдущей линии.
        PUSH 0 
        PUSH YP 
        PUSH XP 
        PUSH MEMDC 
        CALL MoveToEx@16 
;Линия пером.
        PUSH 300 ;Конечные координаты.
        PUSH 550
        PUSH MEMDC
        CALL LineTo@12
        INC  P
        JMP  F3
F2:
        CMP  P,2
        JNE FIN
;Замкнутая фигура - прямоугольник. 
;Вначале выбрать кисть для заполнения области.
        PUSH HBRUSH
        PUSH MEMDC
        CALL SelectObject@8
;Теперь рисуем заполненный прямоугольник.
;Если не выбирать кисть, то будет нарисован незаполненный прямоугольник.
        PUSH 350
        PUSH 400
        PUSH 200
        PUSH 200
        PUSH MEMDC
        CALL Rectangle@20
        INC  P 
F3: 
;Дать команду перерисовать окно.
        PUSH 0
        PUSH OFFSET RECT
        PUSH DWORD PTR [EBP+08H]
        CALL InvalidateRect@12 
FIN:
        MOV EAX, 0
        JMP FINISH  
WMPAINT:
;--------------------------
        PUSH OFFSET PNT
        PUSH DWORD PTR [EBP+08H]
        CALL BeginPaint@8
        MOV  HDC,EAX ;Сохранить контекст (дескриптор)
;Скопировать виртуальное окно на реальное.
        PUSH 0CC0020h ;SRCCOPY=Изображение как есть
        PUSH 0        ;у источника.
        PUSH 0        ;х источника.
        PUSH MEMDC    ;Контекст источника.
        PUSH YM       ;Высота куда.
        PUSH XM       ;Ширина куда.
        PUSH 0        ;у куда.
        PUSH 0        ;х куда.
        PUSH HDC      ;Контекст куда.
        CALL BitBlt@36
;---------------------- закрыть контекст окна
        PUSH OFFSET PNT 
        PUSH DWORD PTR [EBP+08H] 
        CALL EndPaint@8 
        MOV  EAX, 0 
        JMP  FINISH

WMCREATE:
;Размеры экрана.
        PUSH 0 ;X
        CALL GetSystemMetrics@4 
        MOV  XM,EAX 
        PUSH 1 ;Y
        CALL GetSystemMetrics@4 
        MOV  YM,EAX 
;Открыть контекст окна.
        PUSH DWORD PTR [EBP+08H] 
        CALL GetDC@4 
        MOV  HDC,EAX
;Создать совместимый с данным окном контекст. 
        PUSH EAX
        CALL CreateCompatibleDC@4 
        MOV  MEMDC,EAX
;Создать в памяти растровое изображение, совместимое с hdc. 
        PUSH YM 
        PUSH XM 
        PUSH HDC
        CALL CreateCompatibleBitmap@12
;Выбрать растровое изображение в данном контексте. 
        PUSH EAX 
        PUSH MEMDC 
        CALL SelectObject@8
;Цвет кисти
        PUSH RGBW
        CALL CreateSolidBrush@4 ;Создать кисть. 
;Выбрать кисть в данном контексте.
        PUSH EAX
        PUSH MEMDC
        CALL SelectObject@8 
;Заполнить данную прямоугольную область.
        PUSH 0F00021h ;PATCOPY=заполнить данным цветом.
        PUSH YM
        PUSH XM
        PUSH 0
        PUSH 0
        PUSH MEMDC
        CALL PatBlt@24 
;Создать кисть и перо для рисования.
        PUSH RGBR
        CALL CreateSolidBrush@4 ;Создать кисть.
        MOV  HBRUSH,EAX 
;Задать перо.
        PUSH RGBR    ;Цвет.
        PUSH 0       ;Толщина=1
        PUSH 0       ;Сплошная линия.
        CALL CreatePen@12
        MOV HPEN,EAX 
;Удалить контекст.
        PUSH HDC
        PUSH DWORD PTR [EBP+08H]
        CALL ReleaseDC@8
        MOV  EAX, 0
        JMP FINISH  
DEFWNDPROC:
        PUSH DWORD PTR [EBP+14H]
        PUSH DWORD PTR [EBP+10H]
        PUSH DWORD PTR [EBP+0CH]
        PUSH DWORD PTR [EBP+08H]
        CALL DefWindowProcA@16
        JMP  FINISH
WMDESTROY:	
;Удалить перо.
        PUSH HPEN 
        CALL DeleteDC@4 
;Удалить кисть.
        PUSH HBRUSH 
        CALL DeleteDC@4 
;Удалить виртуальное окно.
        PUSH MEMDC 
        CALL DeleteDC@4 
;Выход.
        PUSH 0                   ;MB_OK
        CALL PostQuitMessage@4   ;Сообщение WM_QUIT.
        MOV  EAX, 0
FINISH:	
        POP  EDI
        POP  ESI
        POP  EBX
        POP  EBP
        RET  16
WNDPROC ENDP
_TEXT   ENDS 
        END START
Текст этой программы можно взять здесь.

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

  1. Загрузить растровое изображение и запомнить его дескриптор.
  2. Получить контекст устройства для области памяти, где будет храниться изображение.
  3. Выбрать изображение в данном контексте.
  4. Скопировать изображение на экран (BitBlt).

    С растровыми изображениями удобно работать при помощи ресурсов, но их можно создавать непосредственно в программе или считывать из файла. Но об этом позднее.


    Замечание. Специалисту в Windows-программировании, возможно, покажется странным, что мы пишем свои собственные строковые функции, вместо того чтобы воспользоваться существующими в Windows соответствующими API-функциями. Причина пренебрежения этими функциями проста. Во-первых, мы рассчитывем не только на "продвинутых" читателей, но и на людей, обучающихся программированию на ассемблере. Во-вторых, мы не всегда будем решать задачи только средствами API-функций. Однако, понимая всю важность строковых API-функций, в свое время мы приведем примеры их использования.

    Со следующего шага мы начнем рассматривать консольные приложения.




Предыдущий шаг Содержание Следующий шаг