Курсоры

Дата публикации 16 июн 2002

Курсоры — Архив WASM.RU

Построение рисунка курсора

  При работе в текстовых или графических режимах IBM драйвер мыши самостоятельно определяет установленный видеорежим и в зависимости от этого выбирает способ построения или удаления рисунка курсора, задача только разрешает или запрещает драйверу выполнять эти действия. Поэтому при работе в среде DOS, после установки режимов VESA строить и перемещать рисунок курсора должна задача. Операционные системы семейства Windows и OS/2, в отличие от DOS, поддерживают управление курсором, что упрощает действия прикладных задач..

  Напомним, что код текущего режима хранится в байте, расположенном в области данных BIOS, по адресу 0000:0449. Трехзначные коды режимов VESA не помещаются в байте, поэтому их заменяют кодами OEM, которые уникальны для каждой модели видеокарты. Именно отсутствие стандартов на коды OEM не позволяет разрабатывать драйверы, выполняющие построение рисунка курсора во всех без исключения видеорежимах.

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

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

  Курсоры для Windows

  Наиболее доступными являются файлы, содержащие рисунки курсоров, подготовленные в стандарте Windows. Операционные системы Windows используют курсоры различной формы: стрелка, вертикальная черта, рука, песочные часы и пр. Конкретный рисунок курсора зависит от выполняемых действий и выбирается системой автоматически. Windows 9X позволяет изменять рисунки курсора при выборе "темы рабочего стола".

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

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

 Структура файлов Icon. Семейство Windows использует один общий стандарт Icon для хранения файлов с заготовками рисунков курсоров и пиктограмм (значков). Спецификации файлов имеют тип (расширение) cur для курсоров и ico для пиктограмм.

  К сожалению автор не встречал точного описания стандарта хранения таких файлов, даже в справочнике Борна [4] содержатся явные неточности. Если вам попадется описание стандарта Icon для Windows, то ему не следует слепо доверять. Обязательно распечатайте дамп одного из доступных вам файлов и сравните распечатку с вариантом описания. В качестве эталона можно взять файл nc.ico, входящий в комплект Norton Commander. Для версии NC 5.0 он содержит заготовку рисунка капитанской фуражки с красными цифрами 5.0.

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

  Первые четыре слова заголовка содержат следующие данные. Слово с адресом (смещением) 0 всегда очищено (пустое). В слове с адресом 2 указывается вид рисунка: 1–пиктограмма, 2–курсор. Слово с адресом 4 содержит количество хранящихся в файле рисунков (обычно 1). Слово с адресом 6 рассматривается как два независимых байта, первый (смещение 6) содержит количество точек в строке, а второй (смещение 7) – количество строк. Стандартное содержание обоих байтов 32 (20h).

  Из других полей заголовка важное значение имеет слово с адресом 36 (24h), содержащее количество бит которые занимает одна точка рисунка. Оно равно 1 для черно-белых рисунков и 4 для цветных. Эта величина указывает способ распаковки рисунка и размер палитры.

  Палитра используемых цветов располагается в файле начиная с адреса 3Eh. Она содержит 2 или 16 строк в которых хранятся коды цветов в формате b, g, r, 0. Напомним, что в таком формате хранится палитра в BMP файлах для Windows (см. приложение "А"). В зависимости от количества цветов палитра занимает 8 (2 цвета) или 64 (16 цветов) байта.

  Сразу после палитры размещается образ рисунка. Адрес его начала зависит от размера палитры и равен 46h для черно-белых рисунков или 7Eh для 16-ти цветных. Количество точек в рисунке фиксировано и составляет 32*32=1024 точки. Черно-белые рисунки упакованы по 8 точек в байте, а цветные – по 2 точки в байте. Соответственно, образ рисунка занимает в файле 128 или 512 байт.

  После образа рисунка располагается маска. Адрес ее начала C6h для черно-белых рисунков или 27Eh для цветных. Маска хранится как черно-белый рисунок, упакованный по 8 точек в байте и занимает 128 байтов. Адрес ее начала отстоит от конца файла 128 байтов.

  Образ рисунка и маска хранятся в перевернутом виде: первой записана последняя строка, второй – предпоследняя и т.д., последней в файле хранится первая строка рисунка или маски. Такой способ хранения данных используется в файлах формата BMP (см. приложение "А").

 Дамп файла с рисунком курсора. В примере 1 приведена распечатка (дамп) файла left_00.cur, входящего в комплект MouseWarp. Он содержит рисунок стрелки наклоненной вправо (обычно стрелка наклонена влево). Распечатка приведена в общепринятой шестнадцатиричной форме, каждой строке предшествует адрес ее начала в файле..

  Заголовок файла

Код (Text):
  1.  
  2. 000 00 00 02 00 01 00 20 20 00 00 0E 00 04 00 30 01
  3. 010 00 00 16 00 00 00 28 00 00 00 20 00 00 00 40 00
  4. 020 00 00 01 00 01 00 00 00 00 00 00 01 00 00 00 00
  5. 030 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  6.  

  Палитра, содержащая описание черного и белого цветов

Код (Text):
  1.  
  2. 03E 00 00 00 00 FF FF FF 00
  3.  

  Рисунок курсора, упакованный по 8 точек в байте

Код (Text):
  1.  
  2. 046 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  3. 056 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  4. 066 06 00 00 00 06 00 00 00 03 00 00 00 03 00 00 00
  5. 076 01 80 00 00 01 84 00 00 00 CC 00 00 00 DC 00 00
  6. 086 00 FC 00 00 07 FC 00 00 03 FC 00 00 01 FC 00 00
  7. 096 00 FC 00 00 00 7C 00 00 00 3C 00 00 00 1C 00 00
  8. 0A6 00 0C 00 00 00 04 00 00 00 00 00 00 00 00 00 00
  9. 0B6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  10.  

  Маска курсора, упакованная по 8 точек в байте

Код (Text):
  1.  
  2. 0C6 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
  3. 0D6 FF FF FF FF FF FF FF FF FF FF FF FF F9 FF FF FF
  4. 0E6 F0 FF FF FF F0 FF FF FF F8 7F FF FF F8 7D FF FF
  5. 0F6 FC 39 FF FF FC 31 FF FF fE 01 FF FF FE 01 FF FF
  6. 106 E0 01 FF FF F0 01 FF FF F8 01 FF FF FC 01 FF FF
  7. 116 FE 01 FF FF FF 01 FF FF FF 81 FF FF FF C1 FF FF
  8. 126 FF E1 FF FF FF F1 FF FF FF F9 FF FF FF FD FF FF
  9. 136 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
  10.  

  Пример 1. Распечатка (dump) файла Left_00.cur.

  Для того чтобы лучше понять как хранятся и кодируются рисунок курсора и его маска, советуем вам нарисовать их на бумаге в клетку. Рисунок и маска упакованы одинаково, по 8 точек в байте. Единица в разряде означает наличие точки (заштрихованная клетка на бумаге), а нуль – отсутствие точки (пустая клетка на бумаге). После построения вы увидите что маска похожа на негативное изображение рисунка, но если их совместить, то окажется, что рисунок стрелки в маске оконтурен белой линией.

  Предварительная подготовка рисунка.

  В исходном виде рисунок курсора и маска не удобны для многократного использования. Их надо распаковать, перевернуть, по возможности сократить и хранить в оперативной памяти до конца выполнения задачи.

  Перед построением изображения курсора, как и любого рисунка, должна быть установлена палитра используемых цветов. В данном случае палитра хранится в формате BMP для Windows. Напомним, что в зависимости от способа установки палитры могут изменяться коды точек рисунка курсора (но не маски).

 Поворот рисунка и маски. Рисунок на экране проще строить в естественном порядке, т.е в направлении слева направо и сверху вниз. В исходном виде рисунок и маска хранятся "вверх ногами", поэтому перед распаковкой их надо повернуть. При повороте переставляют 16 пар строк: первую строку с последней, вторую – с предпоследней и т.д.

  В примере 2 показано как можно программно переставить строки маски или черно-белого рисунка. Перед выполнением примера исходный файл должен быть прочитан в буфер, сегмент которого указывается в регистре fs, смещение (адрес) рисунка или маски в буфере помещается в регистр di. Перевернутое изображение записывается на место исходного.

Код (Text):
  1.  
  2.       mov  si, di        ; копируем адрес первой строки
  3.       add  si, 124       ; получаем адрес последней строки
  4.       mov  cx, 16        ; количество пар строк
  5. turn: mov  eax, fs:[di]  ; eax = строка 1
  6.       xchg eax, fs:[si]  ; eax = строка 2; fs:[si] = строка 1
  7.       mov  fs:[di], eax  ; fs:[di] = строка 2
  8.       add  di, 04        ; адрес следующей строки
  9.       sub  si, 04        ; адрес предыдущей строки
  10.       loop turn          ; управление повторами цикла
  11.  

Пример 2. Поворот черно-белого рисунка или маски.

  Выполнение примера 2 начинается с подготовки адреса последней строки и задания количества переставляемых пар строк. Перестановку пар строк выполняет цикл, его первая команда имеет метку turn. Она копирует в eax первую строку пары.

  Следующая команда переставляет содержимое eax и второй строки пары. Третья команда копирует содержимое eax в первую строку. В результате переставлена пара строк. Затем корректируются адреса строк и команда loop повторяет выполнение цикла 16 раз.

  При перестановке расположение байтов в строке не изменяется, поскольку пересылку выполняет одна команда.

  Для переворота цветного рисунка этот пример не подходит, в таком случае для перестановки 16 байтов пары строк надо организовать внутренний цикл, а во внешнем подготавливать адреса переставляемой пары.

 Распаковка рисунка и маски. Повернутые рисунок и маску надо распаковать и сохранить в оперативной памяти. Для их хранения выделяется два массива, размером по 1024 байта (один байт на точку). В дальнейшем мы будем называть эти массивы pntimage и pntmask, первый содержит распакованный рисунок курсора, а второй – маску.

  При распаковке черно-белого рисунка содержимое каждого байта, обрабатывается начиная со старшего разряда и значение каждого бита (0 или 1) помещается в соответствующий байт массива pntimage.

  При распаковке 16-ти цветного рисунка в байты массива pntimage записываются сначала старшая, а затем младшая тетрада каждого байта упакованного рисунка. Коды распакованных точек изменяются от 0 до Fh.

  Подпрограммы распаковки строк 16-ти и 2-х цветных рисунков приведены в примерах 3.17 и 3.18, но они записывают результат в видеопамять. Применительно к данному случаю их надо изменить так, чтобы результат записывался в оперативную память и организовать цикл для распаковки всего рисунка или маски.

  После распаковки рисунка будут получены коды цветов, являющиеся адресами строк прилагаемой палитры. Маловероятно, чтобы они совпали с кодами цветов системной палитры, с которой работает задача. Едва ли в ней код белого цвета будет равен 1 или 0Fh, как в прилагаемой к рисунку палитре.

  Поэтому у вас есть две возможности: либо преобразовать распакованные коды рисунка так, чтобы они соответствовали системной палитре, либо в системной палитре зарезервировать 2 или 16 первых регистров цвета для работы с курсором. Второй способ используется в Windows при работе в режимах PPG.

  Последовательность действий при распаковке маски та же, что и при распаковке черно-белого рисунка. Но, если текущий бит маски содержит 0, то соответствующий байт массива pntmask очищается, а если текущий бит маски содержит 1, то устанавливаются все разряды соответствующего байта массива pntmask (в него записывается код FFh). Это объясняется специфическим назначением маски – при ее наложении байты видеопамяти либо полностью очищаются, либо остаются без изменения.

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

  Для этого, в частности, можно исключить из исходного рисунка не используемые (пустые) строки и столбцы.

  Как правило, размеры рисунка меньше стандартного поля 32х32 точки. Например, изображение стрелки, хранящейся в файле Left_00.cur (пример 1) помещается в прямоугольнике шириной в 14 и высотой в 21 точку. Следовательно для его хранения в памяти достаточно выделить не 1024, а всего 294 байта. Очевидно, что при сокращении рисунка не только уменьшается занимаемое им пространство оперативной памяти, но и ускоряется процесс его вывода на экран. Рисунок и маска взаимосвязанны, поэтому при исключении строки или столбца рисунка надо исключить соответствующую строку или столбец маски.

 Пример описания рисунка и маски. В примере 3 заготовка рисунка и маска описаны на языке ассемблера.

  Это распакованный файл из примера 1, в котором переставлены не только строки, но и столбцы, для того чтобы стрелка курсора была наклонена влево а не вправо.

Код (Text):
  1.  
  2. pntimage db 00,00,00,00,00,00,00,00,00,00,00,00,00,00
  3.      db 00,00,00,00,00,00,00,00,00,00,00,00,00,00
  4.      db 00,00,FF,00,00,00,00,00,00,00,00,00,00,00
  5.      db 00,00,FF,FF,00,00,00,00,00,00,00,00,00,00
  6.      db 00,00,FF,FF,FF,00,00,00,00,00,00,00,00,00
  7.      db 00,00,FF,FF,FF,FF,00,00,00,00,00,00,00,00
  8.      db 00,00,FF,FF,FF,FF,FF,00,00,00,00,00,00,00
  9.      db 00,00,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00
  10.      db 00,00,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,00
  11.      db 00,00,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00
  12.      db 00,00,FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00
  13.      db 00,00,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00
  14.      db 00,00,FF,FF,FF,00,FF,FF,00,00,00,00,00,00
  15.      db 00,00,FF,FF,00,00,FF,FF,00,00,00,00,00,00
  16.      db 00,00,FF,00,00,00,00,FF,FF,00,00,00,00,00
  17.      db 00,00,00,00,00,00,00,FF,FF,00,00,00,00,00
  18.      db 00,00,00,00,00,00,00,00,FF,FF,00,00,00,00
  19.      db 00,00,00,00,00,00,00,00,FF,FF,00,00,00,00
  20.      db 00,00,00,00,00,00,00,00,00,FF,FF,00,00,00
  21.      db 00,00,00,00,00,00,00,00,00,FF,FF,00,00,00
  22.      db 00,00,00,00,00,00,00,00,00,00,00,00,00,00
  23. pntmask  db FF,00,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF
  24.      db FF,00,00,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF
  25.      db FF,00,00,00,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF
  26.      db FF,00,00,00,00,FF,FF,FF,FF,FF,FF,FF,FF,FF
  27.      db FF,00,00,00,00,00,FF,FF,FF,FF,FF,FF,FF,FF
  28.      db FF,00,00,00,00,00,00,FF,FF,FF,FF,FF,FF,FF
  29.      db FF,00,00,00,00,00,00,00,FF,FF,FF,FF,FF,FF
  30.      db FF,00,00,00,00,00,00,00,00,FF,FF,FF,FF,FF
  31.      db FF,00,00,00,00,00,00,00,00,00,FF,FF,FF,FF
  32.      db FF,00,00,00,00,00,00,00,00,00,00,FF,FF,FF
  33.      db FF,00,00,00,00,00,00,00,00,00,00,00,FF,FF
  34.      db FF,00,00,00,00,00,00,00,00,00,00,00,00,FF
  35.      db FF,00,00,00,00,00,00,00,00,FF,FF,FF,FF,FF
  36.      db FF,00,00,00,00,00,00,00,00,FF,FF,FF,FF,FF
  37.      db FF,00,00,00,FF,FF,00,00,00,00,FF,FF,FF,FF
  38.      db FF,00,00,FF,FF,FF,00,00,00,00,FF,FF,FF,FF
  39.      db FF,00,FF,FF,FF,FF,FF,00,00,00,00,FF,FF,FF
  40.      db FF,FF,FF,FF,FF,FF,FF,00,00,00,00,FF,FF,FF
  41.      db FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,FF,FF
  42.      db FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,FF,FF
  43.      db FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,FF,FF,FF
  44.      

 Пример 3. Описание рисунка и маски курсора.

  В примере 3 метки pntimage и pntmask предшествуют директиве db, поэтому двоеточие после них не ставится. Если вы будете включать текст примера в свою программу, то все коды FF надо заменить на 0FFh или на десятичное число "-1". Здесь это не сделано только из соображений наглядности, чтобы можно было увидеть образованный из цифр рисунок. Текст примера лучше всего включить в сегмент данных вашей программы. Для того чтобы рисунок курсора был черно-белым нулевой регистр цвета видеокарты должен быть очищен, а в последнем (255-ом) регистре должен находиться код белого цвета (3F,3F,3F).

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

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

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

 Немаскируемый курсор

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

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

  Но запись кодов точек в видеопамять, по прежнему, будет выполнять логическая операция XOR, вычисляющая функцию "исключающее или" (exclusive OR).

 Предварительные замечания. Образ рисунка курсора можно хранить в любом сегменте оперативной памяти, но учитывая его небольшой размер (294 байта) мы будем считать, что он расположен в сегменте данных (пример 3) и имеет имя pntimage. Маска при построении не используется, поэтому массив pntmask нас в данном случае не интересует.

  Учитывая, что размеры рисунка не фиксированы и зависят от его формы, в разделе данных задачи надо описать две следующие переменные:

Код (Text):
  1.  
  2. pntXsize    dw  14  ; количество точек в строке рисунка курсора
  3. pntYsize    dw  21  ; количество строк в рисунке курсора
  4.  

  В приведенном описании значения переменных соответствуют размерам рисунка, показанного в примере 3.

  Курсор является особым рисунком, его координаты в видеопамяти могут использоваться в различных целях. Поэтому они хранятся в специальных переменных, значение которых может изменяться только при перемещении манипулятора "мышь". В примере 8 описано несколько переменных, используемых при работе с курсором. Здесь нас интересуют только две из них. Переменная Winpnt содержит текущее окно видеопамяти, а Offspnt – адрес (смещение) точки левого верхнего угла рисунка курсора в этом окне.

 Подпрограмма Tglpntr. Текст подпрограммы, изменяющей состояние курсора на противоположное приведен в примере 4. При каждом нечетном вызове Tglpntr рисунок курсора появится на экране, а при четном на его месте восстановится исходный фон. Явно задаваемые входные параметры отсутствуют. Регистр es должен содержать код видеосегмента.

Код (Text):
  1.  
  2. Tglpntr:    pusha       ; сохранение содержимого регистров
  3.     push    Cur_win ; сохранение исходного окна
  4.     mov ax, Winpnt  ; ax = окно с рисунком курсора
  5.     mov Cur_win, ax ; Cur_win = Winpnt
  6.     call    setwin  ; установка исходного окна
  7.     lea     si, pntimage    ; si = адрес массива pntimage
  8.     mov di, Offspnt ; di = адрес в сегменте видеопамяти
  9.     mov cx, pntYsize    ; cx = кол-во повторов внешнего цикла
  10.     mov bx, horsize ; вычисляем константу для
  11.     sub bx, pntXsize    ; коррекции адресов строк
  12. Displ_1:    push    cx  ; сохраняем счетчик строк
  13.     mov cx, pntXsize    ; cx = количество точек в строке рисунка
  14. Displ_2:    lodsb       ; !! al = код очередной точки рисунка
  15.     xor es:[di], al ; !! корректируем байт видеопамяти
  16.     inc di  ; !! увеличение адреса видеопамяти
  17.     jnz @F  ; -> адрес в пределах текущего сегмента
  18.     call    nxtwin  ; конец сегмента, смена окна
  19. @@: loop    Displ_2 ; управление повторами цикла
  20.     pop cx  ; восстанавливаем счетчик строк
  21.     add di, bx  ; адрес начала следующей строки
  22.     jnc @F  ; -> адрес в пределах текущего сегмента
  23.     call    nxtwin  ; конец сегмента, смена окна
  24. @@: loop    Displ_1 ; управление повторами цикла
  25.     pop Cur_win ; восстановление Cur_win
  26.     popa        ; восстановление содержимого регистров
  27.     call    setwin  ; восстановление исходного окна
  28.     ret     ; возврат из подпрограммы
  29.    

 Пример 4. Подпрограмма переключения состояния курсора.

  В подпрограмме примера 4 используется только 5 регистров - ax, bx, cx, si и di, но для сокращения ее текста первая команда pusha сохраняет в стеке содержимое всех регистров. Затем в стеке сохраняется исходное значение переменной Cur_win, а ей присваивается новое значение и устанавливается соответствующее окно видеопамяти. В регистры si, di помещаются адреса оперативной и видеопамяти, а в cx – количество строк в рисунке курсора. В конце подготовки в регистре bx формируется разность horsize - pntXsize, используемая в цикле построения для коррекции адресов строк видеопамяти.

  Построение рисунка выполняют два вложенных цикла.

  Внешний цикл имеет метку Displ_1. Он начинается с сохранения в стеке и изменения содержимого регистра cx, после чего выполняется внутренний цикл.

  Внутренний цикл имеет метку Displ_2. Его первая команда lodsb считывает в регистр al байт, адрес которого находится в регистрах ds:si и увеличивает содержимое si на 1. Затем логическая операция XOR записывает содержимое регистра al в видеопамять. Регистр-посредник al нужен потому, что у команды xor (как и у команды mov) оба операнда не могут находиться в памяти.

  После вывода очередной точки адрес видеопамяти увеличивается на 1 и если его значение осталось в пределах сегмента, то команда jnz @F обходит call nxtwin. В противном случае call nxtwin выполняется и устанавливает следующее окно. Последняя команда (loop Disp_2) повторяет выполнение цикла до тех пор, пока не будет нарисована вся строка.

  После выхода из внутреннего цикла из стека выталкивается содержимое счетчика повторов и вычисляется адрес начала в видеопамяти следующей строки рисунка.

  Если при этом происходит переполнение, то устанавливается следующее окно видеопамяти. Команда loop Disp_1 повторяет выполнение внешнего цикла до тех пор, пока не будет построен весь рисунок курсора.

  Перед возвратом из подпрограммы из стека выталкивается содержимое переменной Cur_win и всех сохраненных регистров, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.

 Недостатки не маскируемого курсора. Очевидными преимущества работы с не маскируемым курсором следующие:

  • для построения и удаления курсора нужна одна подпрограмма;
  • подпрограмма выполняется сравнительно быстро;
  • в оперативной памяти хранится только образ рисунка.

  Но такой способ построения имеет один существенный недостаток, сводящий на нет перечисленные преимущества.

  Идея использования не маскируемого курсора основана на том, что при определенных значениях операнда источника команда xor инвертирует код операнда приемника или не изменяет его. При выполнении описанной подпрограммы источником являются точки заготовки рисунка, а приемником точки видеопамяти. Образ рисунка является черно-белым, коды его точек имеют значения либо 00, либо FFh. Поэтому при построении рисунка цвета точек экрана, расположенных под стрелкой инвертируются, а окружающих стрелку не изменятся. Таким образом, цвет не маскируемого рисунка курсора на экране зависит от исходного цвета точек в том месте экрана, на котором он создается.

  Вспомним таблицу 4.1 из главы 4. При ее описании говорилось что два цвета являются дополнительными, если при их наложении получается белый цвет. В частности дополнением к черному цвету является белый, к синему – желтый, к красному – циан, к зеленому – мажента. Исходя из этого можно представить как изменяется цвет курсора в зависимости от исходного цвета точек экрана. Если же цвет экрана не однороден, т.е. на там находится какая-то картинка, то и изображение курсора будет неоднородным. На пестром фоне оно может "потеряться" – стать трудно различимым для глаза.

  При работе в режимах PPG описанная подпрограмма инвертирует не код цвета, а номер регистра видеокарты. Полученный при инверсии цвет будет зависеть от установленной (системной) палитры. Эта особенность успешно использовалась, например, в ранних версиях Windows – системная палитра подбиралась так, чтобы можно было использовать не маскируемый курсор.

  В заключение заметим, что после описания построения маскируемого курсора, в разделе 6.1.5 мы продолжим обсуждение некоторых вопросов, связанных с построением изображения курсора.

 Маскируемый курсор

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

 Как производится маскировка. В предыдущем разделе мы использовали то, что при определенных условиях команда xor инвертирует значение операнда приемника. Но у нее есть еще одно полезное свойство. Вспомним таблицу истинности логической функции "исключающее или". Из нее, в частности следует, что если один из двух операндов очищен, то результат выполнения команды xor будет равен значению другого операнда. Следовательно, при наложении двух цветов с помощью операции xor черный цвет становится прозрачным.

  При построении маскируемого курсора та часть экрана, которую займет его изображение, предварительно окрашивается в черный цвет (очищается). С помощью команды xor (или or) на чистом месте можно построить рисунок любого цвета.

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

  Для закрашивания в черный цвет на место расположения выводимого рисунка накладывается маска. Как говорилось, байты маски могут содержать только два значения кодов – 00 или FFh. Маска является черно-белым рисунком, у которого черные точки соответствуют основной части маскируемого рисунка, а белые – черным точкам маскируемого рисунка, дополняющим его заготовку до прямоугольника. Посмотрите на пример 3 и вы увидите, что маска соответствует основному рисунку.

  Фактически маска как рисунок не используется. Она записывается в видеопамять с помощью логической операции "И" (конъюнкция), которую вычисляет команда and. При выполнении этой команды операнд приемника будет очищен, если очищен операнд источника и не изменится, если у операнда источника установлены все разряды.

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

  Если вы внимательно проанализируете пример 3, то обнаружите что находящаяся в массиве pntmask маска очищает несколько большую часть экрана чем нужно для размещения стрелки, хранящейся в массиве pntimage. Это сделано для того, чтобы белая стрелка не потерялась на белом фоне. В результате наложения такой маски рисунок курсора на экране окажется окруженным черной окантовкой и белая стрелка будет видна на любом фоне.

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

  Сохранение исходного фона необходимо потому, что восстановить его после построения рисунка курсора невозможно. Для размещения сохраняемого фона в оперативной памяти надо зарезервировать массив pntbuff. Его размер соответствует размеру рисунка курсора в байтах, т.е. совпадает с размером массивов pntimage и pntmask. Каждый из этих трех массивов занимает в памяти pntXsize*pntYsize байтов. Pntbuff надо расположить в том же сегменте, в котором находятся pntimage и pntmask. Мы будем считать, что они размещены в разделе данных задачи и доступ к ним происходит через сегментный регистр ds. При этом буфер описывается так:

  pntbuff db 294 dup (?) ; резервирование 294 байтов в разделе данных

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

 Подпрограмма Showpnt. Описанные действия выполняются в одном цикле, который повторяется для каждой точки прямоугольной области, в которой располагается рисунок курсора. Текст подпрограммы приведен в примере 5. Регистр es должен содержать код видеосегмента (хранящийся в Vbuff).

Код (Text):
  1.  
  2.     Showpnt:pusha       ; сохранение содержимого регистров
  3.     push    Cur_win ; сохранение исходного окна
  4.     mov ax, Winpnt  ; ax = окно с рисунком курсора
  5.     mov Cur_win, ax ; Cur_win = Winpnt
  6.     call    setwin  ; установка исходного окна
  7.     xor si, si  ; очистка регистра si
  8.     mov di, Offspnt ; di = адрес в сегменте видеопамяти
  9.     mov cx, pntYsize    ; cx = кол-во повторов внешнего цикла
  10.     mov bx, horsize ; вычисляем константу для
  11.     sub bx, pntXsize    ; коррекции адресов строк
  12. Sh_1:   push    cx  ; сохраняем значение счетчика строк
  13.     mov cx, pntXsize    ; cx = количество точек в строке рисунка
  14. Sh_2:   mov al, es:[di] ; !! al = код точки исходного фона
  15.     mov pntbuff[si], al ; !! сохpаняем его в pntbuff
  16.     and al, pntmask[si] ; !! накладываем маску
  17.     xor al, pntimage[si]    ; !! формируем новый код точки
  18.     stosb       ; !! и записываем его в видеопамять
  19.     inc si  ; !! коррекция адреса рисунка
  20.     or  di, di  ; адрес в пределах текущего окна ?
  21.     jnz @F  ; -> да, обходим следующую команду
  22.     call    nxtwin  ; установка  следующего окна
  23. @@: loop    sh_2    ; управление повторами цикла
  24.     pop cx  ; восстанавливаем счетчик строк
  25.     add di, bx  ; корректируем адрес видеопамяти
  26.     jnc @F  ; -> адрес в пределах текущего окна
  27.     call    nxtwin  ; установка следующего окна
  28. @@: loop    sh_1    ; управление внутренним циклом
  29.     pop Cur_win ; восстановление значения Cur_win
  30.     popa        ; восстановление всех регистров
  31.     call    setwin  ; восстановление исходного окна
  32.     ret     ; возврат из подпрограммы
  33.    

 Пример 5. Подпрограмма построения рисунка маскируемого курсора.

  Начало примера 5 отличается от примера 4 только одной командой. Вместо записи в регистр si адреса массива pntimage, он просто очищается. Это сделано потому, что регистр si используется для доступа к трем массивам, а не к одному как это было в примере 4.

  Принципиальное различие между примерами в основных действиях, выполняемых во внутреннем цикле, который в данном случае имеет метку Sh_2. Первая команда внутреннего цикла считывает код очередной точки из видеопамяти в регистр al, а вторая сохраняет его в очередном байте массива pntbuff. После этого в регистр al помещается результат вычисления логической функции "И" от его исходного значения и значения очередного байта маски. В зависимости от кода байта маски регистр al либо будет очищен, либо его исходное значение не изменится – третьего не дано. Четвертая команда завершает формирование нового кода точки, она вычисляет логическую функцию "исключающее или" от содержимого регистра al и очередного байта массива pntimage. Остается записать новый код точки в видеопамять, что и делает команда stosb, одновременно она увеличивает содержимое регистра di на 1.

  Основные действия выполнены, шестая команда увеличивает на 1 адрес, хранящийся в регистре si. Адрес, хранящийся в регистре di, увеличила команда stosb, но надо проверить остался ли он в пределах текущего видеосегмента. Признаком выхода за пределы сегмента является нуль в регистре di, при подпрограмма nxtwin установит следующее окно видеопамяти. Повторами внутреннего цикла управляет команда loop Sh_2.

  После построения строки из стека восстанавливается значение счетчика повторов, вычисляется адрес начала следующей строки в видеопамяти и команда loop Sh_1 повторяет выполнение внешнего цикла до тех пор пока на экран не будут выведены все строки изображения курсора.

  В заключение из стека восстанавливаются исходные значения переменной Cur_win и всех регистров, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.

 Массивы в другом регистре. Текст примера 5 составлен из расчета на то, что массивы pntimage, pntmask и pntbuff расположены в разделе данных и для доступа к ним используется регистр ds, имя которого не указывается перед операндами. Если вы предпочитаете расположить указанные массивы в другом сегменте, то в трех командах примера 5 перед именами массивов надо явно указать имя выбранного вами сегментного регистра. Например,mov fs:pntbuff[si], al если для доступа к массиву pntbuff используется сегментный регистр fs. Еще раз напоминаем, что все три массива должны располагаться в одном сегменте.

 Действия при удаление курсора. Перед перемещением курсора его старое изображение удаляется с экрана. Кроме того, изображение курсора удаляется перед выводом на экран новых рисунков.

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

 Подпрограмма Hidepnt. Текст подпрограммы, выполняющей удаление изображения курсора приведен в примере 6. В нем использованы те же переменные, что и в примерах 4 и 5, а из трех массивов нужен только pntbuff, содержащий ранее сохраненный фон.

Код (Text):
  1.  
  2.     pusha       ; сохранение содержимого регистров
  3.     push    Cur_win ; сохранение исходного окна
  4.     mov ax, Winpnt  ; ax = окно с рисунком курсора
  5.     mov Cur_win, ax ; Cur_win = Winpnt
  6.     call    setwin  ; установка исходного окна
  7.     lea si, pntbuff ; si = адрес буфера с сохраненным фоном
  8.     mov di, Offspnt ; di = адрес в сегменте видеопамяти
  9.     mov cx, pntYsize    ; cx = кол-во повторов внешнего цикла
  10.     mov bx, horsize ; вычисляем константу для
  11.     sub bx, pntXsize    ; коррекции адресов строк
  12. hid_1:  push    cx  ; сохраняем значение счетчика строк
  13.     mov cx, pntXsize    ; cx = количество точек в строке рисунка
  14. hid_2:  movsb       ; !! копируем байт из pntbuff в видеопамять
  15.     or  di, di  ; конец сегмента видеопамяти ?
  16.     jnz @F  ; -> нет, обходим следующую команду
  17.     call    nxtwin  ; устанавливаем следующее окно
  18. @@: loop    hid_2   ; управление повторами цикла
  19.     pop cx  ; восстанавливаем счетчик строк
  20.     add di, bx  ; корректируем адрес видеопамяти
  21.     jnc @F  ; -> адрес в пределах текущего окна
  22.     call    nxtwin  ; установка следующего окна
  23. @@: loop    hid_1   ; управление повторами цикла
  24.     pop Cur_win ; восстановление значения Cur_win
  25.     popa        ; восстановление всех регистров
  26.     call    setwin  ; восстановление исходного окна
  27.     ret     ; возврат из подпрограммы
  28.    

 Пример 6. Восстановление исходного фона на месте рисунка курсора.

  В примере 6 выполняются действия, которые уже неоднократно обсуждались, поэтому мы опустим его подробное описание. Автор надеется, что читатель разберется в том, что делают конкретные команды.

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

  Объем оперативной памяти, необходимый для хранения рабочих массивов, увеличился в три раза. При описанном способе построения изображения курсора сократить его невозможно. Попробуйте самостоятельно ответить на вопрос почему нельзя временно сохранять исходный фон в массиве pntimage и восстанавливать его содержимое при восстановлении исходного фона.

  При построении не маскируемого курсора обработку кода каждой точки выполняли 2 команды внутреннего цикла. В примере 5 таких команд стало 5 и сократить их количество невозможно.

  Наконец, вместо одной подпрограммы Tglpntr при использовании маски нужны две – Showpnt и Hidepnt. Объединить их в одну подпрограмму невозможно. Включение и выключение курсора это два независимых процесса, которые не могут выполняться одновременно.

  Такова реальная плата за улучшение качества изображения и возможность работы с цветным рисунком курсора. Выбор маскировки или отказ от нее зависит от вас.

 Замечания к описанным подпрограммам

  В двух предыдущих разделах описаны действия, которые надо выполнить для построения или удаления изображения курсора. Здесь мы рассмотрим как изменяются команды выполняющие эти действия в зависимости от тех или иных дополнительных условий. Нас будут интересовать способы ускорения работы с курсором и построения изображения в режимах direct color, когда цвет указывается непосредственно в коде точки.

 Ускорение работы с курсором. Курсор является основным средством управления процессом выполнения графических задач. Чем меньше времени затрачивается на перемещение его рисунка, тем больше времени остается на выполнение основных действий. Поэтому ускорение манипуляций с курсором представляет определенный практический интерес.

  Для ускорения работы трех описанных подпрограмм надо сократить количество действий, выполняемых в их внутренних циклах. В примере 6 основное действие выполняет одна команда movsb, поэтому в этом случае применимы способы ускорения построения строк, описанные в разделе 3.3.1. В частности, вместо внутреннего цикла можно использовать подпрограмму примера 3.16, внеся в нее незначительные изменения.

  К подпрограммам Tglpntr (пример 4) и Showpnt (пример 5) описанные способы ускорения не применимы, поскольку в них основные действия выполняют несколько (2 или 5) команд.

 На первый взгляд достаточно просто изменить основные команды так, чтобы они оперировали не с байтами, а со словами или двойными словами, т.е. обрабатывали коды двух или четырех точек. В первом случае количество повторов внутреннего цикла сократится в 2, а во втором – в 4 раза, поэтому перед входом в цикл содержимое регистра cx (pntXsize) надо уменьшить, соответственно, в 2 или в 4 раза.

  Такая замена дает нужный результат, но необходимы дополнительные меры защиты от возможной аварийной ситуации. Давайте разберемся в причине ее возникновения.

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

  Для исключения аварийной ситуации можно, например, сделать так, чтобы при работе с курсором адрес его начала в видеопамяти всегда был четным. Изображение курсора всегда следует за манипулятором "мышь". Если при опросе состояния последнего окажется что он находится в столбце с нечетным номером, то этот номер принудительно уменьшается или увеличивается на 1. Такой трюк уменьшает точность позиционирования курсора на экране, поэтому вам придется выбирать из двух зол наименьшее. К вопросу о точности позиционирования мы вернемся при описании программирования работы с мышью.

  Таким образом, при некотором ограничении точности позиционирования выполнение подпрограмм Tglpntr и Showpnt можно ускорить в 2 раза. Для этого в них надо внести описанные выше изменения. Более существенное ускорение связано со значительным увеличением размеров текстов подпрограмм и едва ли целесообразно.

 Изменения для режимов direct color. Подробному описанию особенностей программирования для видеорежимов с указанием цвета в коде точки посвящена глава 7. Здесь мы только покажем те изменения, которые надо внести в описанные подпрограммы для их использования при работе в режимах direct color. Это сделано для того чтобы в дальнейшем не повторять описание способов построения курсора. Вы можете пропустить эту часть раздела и вернуться к ней после прочтения главы 7.

  В режимах PPG код точки является номером регистра цвета видеокарты, а в режимах direct color он является кодом конкретного цвета и занимает 16 разрядов в режиме Hi–Color и 32 разряда в режиме True Color.

  Прежде всего вам придется изменить заготовку рисунка и маску, хранящиеся в массивах pntimage и pntmask (пример 3).

  Проще всего изменить описание маски и черно-белого рисунка курсора. В пояснениях к примеру 3 мы советовали при его использовании в конкретной программе заменить все коды FF десятичным числом -1. Если вы это сделали, то остается только заменить все директивы db на dw для Hi–Color или на dd для True Color. Макроассемблер зарезервирует требуемое пространство памяти и заменит число -1 кодами FFFFh или FFFFFFFFh, в зависимости от указанной директивы (dw или dd).

  Изменить описание цветного рисунка курсора сложнее, в этом случае недостаточно простой замены директив db на dw или dd. У заготовок цветных рисунков распакованный код точки является порядковым номером строки палитры, хранящейся вместе с рисунком. По коду точки надо выбрать из палитры код цвета, преобразовать его в нужную форму и поместить в описание рисунка. Такое преобразование делается программно, а не вручную. В главе 8 описаны способы преобразования рисунков из формата PPG в форматы direct color, их и можно использовать. Но на первое время лучше ограничиться черно-белым курсором, а к работе с цветным перейти позже, по мере накопления опыта программирования графики.

  В режимах direct color размеры массивов pntimage и pntmask увеличиваются в 2 или в 4 раза, во столько же раз надо увеличить размер массива pntbuff. Это можно сделать одним из двух способов: заменить в его описании директиву db на dw или dd. либо оставить директиву db, а количество резервируемых байтов умножить на 2 или на 4.

  Изменения в текстах подпрограмм примеров 4, 5 и 6 связаны только с увеличением размера кода точки в 2 (режим Hi–Color) или 4 (режим True Color) раза. Прежде всего, нужно увеличить значение константы, которая используется для коррекции адресов строк. Во всех примерах ее вычисляют две следующие команды:

Код (Text):
  1.  
  2.     mov bx, horsize ; вычисляем константу для
  3.     sub bx, pntXsize    ; коррекции адресов строк
  4.  

  После них надо записать третью команду, сдвигающую содержимое регистра bx на один (shl bx, 1) или на 2 (shl bx, 2) разряда влево. Значение константы увеличится, соответственно, в 2 или в 4 раза.

  Остальные изменяемые команды расположены во внутренних циклах, комментарий к ним начинается с двух восклицательных знаков. Изменения этих команд делятся на следующие три категории.

  • у строковых команд lodsb, stosb и movsb последняя буква (b) заменяется буквами w (Hi–Color) или d (True Color);
  • один из операндов команды находится в регистре al, имя регистра надо заменить на ax (Hi–Color) или на eax (True Color);
  • команды inc di и inc si увеличивают значение адреса на 1. Они заменяются командой сложения (add), которая прибавляет к регистру число 2 (Hi–Color) или 4 (True Color).

  Перечисленные изменения делают возможным применение описанных подпрограмм при работе в видеорежимах с указанием цвета в коде точки.

 Итоги. При программировании конкретной задачи важно не только составить нужную подпрограмму, но и корректно ее использовать. Применительно к нашему случаю это означает следующее.

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

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


0 1.510
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532