Devotes - Предисловие
Уважаеме посетители, Сайт переезжает на другой хост http://www.devote.ru/
   Поиск по сайту:  
» Главная
Книги
Музыка
Download
Форум
[Назад] [Далее]

4.6. Работа с мышью

Все общение с мышью в DOS выполняется через прерывание 33h, обработчик которого устанавливает драйвер мыши, загружаемый обычно при запуске системы. Современные драйверы поддерживают около 60 функций, позволяющих настраивать разрешение мыши, профили ускорений, виртуальные координаты, настраивать дополнительные обработчики событий и т.п. Большинство этих функций требуются редко, сейчас рассмотрим основные:


INT 33h, AX = 0 — Инициализация мыши

Ввод: AX = 0000h
Вывод: АХ = 0000h, если мышь или драйвер мыши не установлены
АХ = FFFFh, если драйвер и мышь установлены
ВХ = число кнопок:
    0002 или FFFF — две
    0003 — три
    0000 — другое количество

Выполняется аппаратный и программный сброс мыши и драйвера.


INT 33h, AX = 1 — Показать курсор

Ввод: AX = 0001h

INT 33h, AX = 2 — Спрятать курсор

Ввод: AX = 0002h

Драйвер мыши поддерживает внутренний счетчик, управляющий видимостью курсора мыши. Функция 2 уменьшает значение счетчика на единицу, а функция 1 увеличивает его, но только до значения 0. Если значение счетчика — отрицательное число, он спрятан, если ноль — показан. Это позволяет процедурам, использующим прямой вывод в видеопамять, вызывать функцию 2 в самом начале и 1 — в самом конце, не заботясь о том, в каком состоянии был курсор мыши у вызвавшей эту процедуру программы.


INT 33h, AX = 3 — Определить состояние мыши

Ввод: AX = 0003h
Вывод: ВХ = состояние кнопок:
    бит 0 — нажата левая кнопка
    бит 1 — нажата правая кнопка
    бит 2 — нажата средняя кнопка
СХ = Х-координата
DX = Y-координата

Возвращаемые координаты совпадают с пиксельными координатами соответствующей точки на экране в большинстве графических режимов, кроме 04, 05, 0Dh, 13h, в которых Х-координату мыши нужно разделить на 2, чтобы получить номер столбца соответствующей точки на экране. В текстовых режимах обе координаты надо разделить на 8, чтобы получить номер строки и столбца соответственно.

В большинстве случаев эта функция не используется в программах, так как для того, чтобы реагировать на нажатие кнопки или перемещение мыши в заданную область, требуется вызывать это прерывание постоянно, что приводит к трате процессорного времени. Функции 5 (определить положение курсора при последнем нажатии кнопки), 6 (определить положение курсора при последнем отпускании кнопки) и 0Bh (определить расстояние, пройденное мышью) могут помочь оптимизировать работу программы, самостоятельно следящей за всеми передвижениями мыши, но гораздо эффективнее указать драйверу самому следить за ее передвижениями (чем он, собственно, и занимается постоянно) и передавать управление в программу, как только выполнится заранее определенное условие, например пользователь нажмет на левую кнопку мыши. Такой сервис обеспечивает функция 0Ch — установить обработчик событий.


INT 33h, AX = 0Ch — Установить обработчик событий

Ввод: AX = 000Ch
ES:DX = адрес обработчика
СХ = условие вызова
    бит 0 — любое перемещение мыши
    бит 1 — нажатие левой кнопки
    бит 2 — отпускание левой кнопки
    бит 3 — нажатие правой кнопки
    бит 4 — отпускание правой кнопки
    бит 5 — нажатие средней кнопки
    бит 6 — отпускание средней кнопки
СХ = 0000h — отменить обработчик

Обработчик событий должен быть оформлен, как дальняя процедура (то есть завершаться командой RETF). На входе в процедуру обработчика АХ содержит условие вызова, ВХ — состояние кнопок, СХ, DX — X- и Y-координаты курсора, SI, DI — счетчики последнего перемещения по горизонтали и вертикали (единицы измерения для этих счетчиков — мики, 1/200 дюйма), DS — сегмент данных драйвера мыши. Перед завершением программы установленный обработчик событий должен быть обязательно удален (вызов функции 0Ch с СХ = 0), так как иначе при первом же выполнении условия управление будет передано по адресу в памяти, с которого начинался обработчик.

Функция 0Ch используется так часто, что у нее появилось несколько модификаций — функция 14h, позволяющая установить одновременно три обработчика с разными условиями, и функция 18h, также позволяющая установить три обработчика и включающая в условие вызова состояние клавиш Shift, Ctrl и Alt. Воспользуемся обычной функцией 0Ch, чтобы написать простую программу для рисования.

; mousedr.asm
; Рисует на экране прямые линии с концами в позициях, указываемых мышью
;
        .model      tiny
        .code
        org         100h         ; СОМ-файл
        .186                     ; для команды shr cx,3
start:
        mov         ax,12h
        int         10h          ; видеорежим 640x480
        mov         ax,0         ; инициализировать мышь
        int         33h
        mov         ax,1         ; показать курсор мыши
        int         33h
        mov         ax,000Ch     ; установить обработчик событий мыши
        mov         cx,0002h     ; событие - нажатие левой кнопки
        mov         dx,offset handler ; ES:DX - адрес обработчика
        int         33h
        mov         ah,0         ; ожидание нажатия любой клавиши
        int         16h
        mov         ax,000Ch
        mov         cx,0000h     ; удалить обработчик событий мыши
        int         33h
        mov         ax,3         ; текстовый режим
        int         10h
        ret                      ; конец программы

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

handler:
        push        0A000h
        pop         es           ; ES - начало видеопамяти
        push        cs
        pop         ds           ; DS - сегмент кода и данных этой программы
        push        сх           ; СХ (Х-координата) и
        push        dx           ; DX (Y-координата) потребуются в конце

        mov         ax, 2        ; спрятать курсор мыши перед выводом на экран
        int         33h
        cmp         word ptr previous_X,-1  ; если это первый вызов,
        je          first_point             ; только вывести точку,

        call        line_bresenham          ; иначе - провести прямую
exit_handler:
        pop         dx                      ; восстановить СХ и DX
        pop         сх
        mov         previous_X,cx           ; и запомнить их как предыдущие
        mov         previous_Y,dx           ; координаты

        mov         ax,1         ; показать курсор мыши
        int         33h
        retf                     ; выход из обработчика - команда RETF

first_point:
        call        putpixel1b   ; вывод одной точки (при первом вызове)
        jmp         short exit_handler

; Процедура рисования прямой линии с использованием алгоритма Брезенхама
; Ввод: СХ, DX - X, Y конечной точки
; previous_X,previous_Y - X, Y начальной точки

line_bresenham:
        mov         ax, сх
        sub         ax,previous_X             ; AX = длина проекции прямой на ось X
        jns         dx_pos                    ; если АХ отрицательный -
        neg         ax                        ; сменить его знак, причем
        mov         word ptr X_increment,1    ; координата X при выводе
        jmp         short dx_neg              ; прямой будет расти,
dx_pos: mov         word ptr X_increment,-1   ; а иначе - уменьшаться

dx_neg: mov         bx,dx
        sub         bx,previous_Y             ; BX = длина проекции прямой на ось Y
        jns         dy_pos                    ; если ВХ отрицательный -
        neg         bx                        ; сменить его знак, причем
        mov         word ptr Y_increment,1    ; координата Y при выводе
        jmp         short dy_neg              ; прямой будет расти,
dy_pos: mov         word ptr Y_increment,-1   ; а иначе - уменьшаться

dy_neg: shl         ax,1            ; удвоить значения проекций,
        shl         bx,1            ; чтобы избежать работы с полуцелыми числами

        call        putpixel1b      ; вывести первую точку (прямая рисуется от
                                    ; CX,DX к previous_X,previous_Y)
        cmp         ax,bx           ; если проекция на ось X больше, чем на Y:
        jna         dx_le_dy
        mov         di,ax           ; DI будет указывать, в какую сторону мы
        shr         di,1            ; отклонились от идеальной прямой
        neg         di              ; оптимальное начальное значение DI:
        add         di,bx           ; DI = 2 * dy - dx
cycle:
        cmp         ex,word ptr previous_X    ; основной цикл выполняется,
        je          exit_bres                 ; пока Х не станет равно previous_X
        cmp         di,0                      ; если DI > 0,
        jl          fractlt0
        add         dx,word ptr Y_increment   ; перейти к следующему Y
        sub         di,ax                     ; и уменьшить DI на 2 * dx
fractlt0:
        add         cx,word ptr X_increment   ; следующий Х (на каждом шаге)
        add         di,bx                     ; увеличить DI на 2 * dy
        call        putpixel1b                ; вывести точку
        jmp         short cycle               ; продолжить цикл
dx_le_dy:                                ; если проекция на ось Y больше, чем на X
        mov         di,bx
        shr         di,1
        neg         di                        ; оптимальное начальное значение DI:
        add         di,ax                     ; DI = 2 * dx - dy
cycle2:
        cmp         dx,word ptr previous_Y    ; основной цикл выполняется,
        je          exit_bres                 ; пока Y не станет равным previous_Y,
        cmp         di,0                      ; если DI > 0,
        jl          fractlt02
        add         cx,word ptr X_increment   ; перейти к следующему X
        sub         di,bx                     ; и уменьшить DI на 2 * dy
fractlt02:
        add         dx,word ptr Y_increment   ; следующий Y (на каждом шаге)
        add         di,ax                     ; увеличить DI на 2 * dy
        call        putpixel1b                ; вывести точку
        jmp         short cycle2              ; продолжить цикл
exit_bres:
        ret                                   ; конец процедуры

; Процедура вывода точки на экран в режиме, использующем один бит для
; хранения одного пикселя.
; DХ = строка, СХ = столбец
; Все регистры сохраняются

putpixel1b:
        pusha                            ; сохранить регистры
        xor        bx,bx
        mov        ax,dx                 ; AX = номер строки
        imul       ах,ах,80              ; АХ = номер строки * число байт в строке
        push       cx
        shr        сх,3                  ; СХ = номер байта в строке
        add        ах,сх                 ; АХ = номер байта в видеопамяти
        mov        di,ax                 ; поместить его в SI и DI для команд
        mov        si,di                 ; строковой обработки

        pop        cx                    ; СХ снова содержит номер столбца
        mov        bx,0080h
        and        cx,07h                ; последние три бита СХ =
; остаток от деления на 8 = номер бита в байте, считая справа налево
        shr        bx,cl                    ; теперь в BL установлен в 1 нужный бит
        lods       es:byte ptr some_label   ; AL = байт из видеопамяти
        or         ax,bx                    ; установить выводимый бит в 1,
; чтобы стереть пиксель с экрана, эту команду OR можно заменить на 
; not bx
; and ax,bx
; или лучше инициализировать ВХ не числом 0080h, а числом FF7Fh и использовать
; только and
        stosb                           ; и вернуть байт на место
        рора                            ; восстановить регистры
        ret                             ; конец

previous_X         dw       -1          ; предыдущая Х-координата
previous_Y         dw       -1          ; предыдущая Y-координата
Y_increment        dw       -1          ; направление изменения Y
X_increment        dw       -1          ; направление изменения X
some_label:                             ; метка, используемая для переопределения
                                        ; сегмента-источника для lods с DS на ES
        end        start

Алгоритм Брезенхама, использованный в этой программе, является самым распространенным алгоритмом рисования прямой. Существуют, конечно, и более эффективные алгоритмы, например алгоритм Цаолинь By, работающий на принципе конечного автомата, но алгоритм Брезенхама стал стандартом де-факто.




Приведенную реализацию этого алгоритма можно значительно ускорить, использовав самомодифицирующийся код, то есть после проверки на направление прямой в начале алгоритма вписать прямо в дальнейший текст программы команды INС СХ, DEC CX, INС DX и DEC DX вместо команд сложения этих регистров с переменными X_increment и Y_increment. Самомодифицирующийся код часто применяется при программировании для DOS, но во многих многозадачных системах текст программы загружается в область памяти, защищенную от записи, так что в последнее время область применения этого приема становится ограниченной.



Hosted by uCoz