Blog. Just Blog

Окна нестандартной формы на Ассемблере. Часть 2

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
В первой части статьи я рассказал как создавать окна нестандартной формы при помощи регионов. У этого способа есть один большой недостаток: создаваемые окна так или иначе состоят из четких геометрических форм. Но высший пилотаж - это окна в форме картинок, и сейчас я расскажу как они делаются. Сперва немного теоретических выкладок. В графическом файле формата BMP информация о картинке хранится в растровом виде, то есть каждый пиксел описан определенным цветом. Рекомендую внимательно прочитать документацию о формате BMP-файла, так как есть несколько важных моментов. Создание окна нестандартной формы на основе растровой картинки заключается в наложении изображения на диалоговое окно и удалении всех его регионов, в которых находятся точки определенного цвета. Этот цвет мы будем считать "прозрачным", потому что настоящую прозрачность обычный формат BMP не поддерживает. А удалять отдельные регионы мы уже умеем.

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

Картинка для окна
Картинка для окна

Картинку надо сохранить в формате BMP, с глубиной цвета 8 бит. Это очень важно, так как в этом случае количество цветов в палитре не превышает 256, а каждая точка описывается ровно одним байтом. Поскольку картинка будет накладываться на диалоговое окно, то и хранить ее надо будет в ресурсах. Тут есть важная особенность: в ресурсах картинка хранится без 14-байтного заголовка BITMAPFILEHEADER и все смещения считаются сразу же от начала блока BITMAPINFOHEADER.

Нам потребуется вспомогательная функция для получения цвета точки. В ней учтены особенности формата BMP, а именно то, что пиксели хранятся построчно, снизу вверх, а каждая строка изображения дополняется нулями до длины, кратной четырем байтам.
  1. ;----------------------------------------------------------
  2. ; Процедура получения цвета точки с координатами X,Y
  3. ;----------------------------------------------------------
  4. ; dXCoord    - X-координата запрашиваемой точки
  5. ; dYCoord    - Y-координата запрашиваемой точки
  6. ; bmpX       - ширина картинки
  7. ; bmpY       - высота картинки
  8. ; bitmapData - указатель на начало массива картинки
  9. ; palData    - указатель на начало массива палитры
  10. ; На выходе: EAX - RGB цвет точки из палитры
  11. ;----------------------------------------------------------
  12. proc GetPixelColorAt dXCoord:dword, dYCoord:dword, bmpX:dword,\
  13.                      bmpY:dword, bitmapData:dword, palData:dword
  14.  
  15.         ; Сохранить изменяемые регистры
  16.         push    esi ecx edx
  17.  
  18.         ; Проверка валидноости координат
  19.         xor     eax,eax
  20.         mov     esi,[dXCoord]
  21.         cmp     esi,[bmpX]
  22.         ja      @f
  23.         mov     esi,[dYCoord]
  24.         cmp     esi,[bmpY]
  25.         ja      @f
  26.  
  27.         ; Вычислить указатель на точку в картинке
  28.         mov     eax,[bmpY]
  29.         dec     eax
  30.         sub     eax,[dYCoord]
  31.         mov     ecx,[bmpX]
  32.  
  33.         ; Каждая строка изображения дополняется нулями до длины,
  34.         ; кратной четырём байтам. Поэтому если ширина картинки не кратна
  35.         ; 4, то вычислить поправку для нахождения правильного смещения
  36.         ; точки в данных
  37.  
  38.         test    ecx,3
  39.         jz      loc_no_fix
  40.         and     ecx,0FFFFFFFCh
  41.         add     ecx,4
  42. loc_no_fix:
  43.         imul    eax,ecx
  44.         add     eax,[dXCoord]
  45.         mov     esi,[bitmapData]
  46.         ; Получить код цвета точки в палитре картинки
  47.         movzx   eax,byte [esi+eax]
  48.         shl     eax,2
  49.  
  50.         ; Получить значение цвета из палитры
  51.         mov     esi,[palData]
  52.         mov     eax,[esi+eax]
  53. @@:
  54.         ; Восстановить измененные регистры
  55.         pop     edx ecx esi
  56.  
  57.         ret
  58. endp
Переходим к основной функции, которая применяет маску из растровой картинки к диалоговому окну. Функция последовательно выполняет следующие действия: загружает ресурс с картинкой в память, самостоятельно анализирует внутренний формат картинки согласно спецификации, получает ее размер и указатели на палитру и графические данные. Определяет цвет левой верхней точки картинки и принимает его значение в качестве прозрачного цвета. Далее в цикле перебираются все точки изображения, и если цвет точки равен принятому прозрачному цвету, то соответствующий пиксел диалогового окна вырезается.
  1. ;----------------------------------------------------------
  2. ; Процедура установки скина на диалоговое окно
  3. ; Copyright (C) ManHunter / PCL
  4. ; http://www.manhunter.ru
  5. ;----------------------------------------------------------
  6. ; hwnddlg   - хэндл окна, к которому будет применена
  7. ;             битовая маска
  8. ; hInstance - хэндл приложения, в ресурсах которого
  9. ;             хранится картинка
  10. ; BitmapID  - идентификатор картинки в ресурсах
  11. ;----------------------------------------------------------
  12. proc SetBMPSkin hwnddlg:dword, hInstance:dword, BitmapID:dword
  13.         local   bitmapData:DWORD    ; Указатель на начало данных картинки
  14.         local   palData:DWORD       ; Указатель на начало палитры
  15.         local   bmpX:DWORD          ; Размеры картинки
  16.         local   bmpY:DWORD
  17.         local   TransColor:DWORD    ; Прозрачный цвет
  18.  
  19.         local   hRMain:DWORD        ; Хэндл главного региона окна
  20.         local   workX:DWORD         ; Текущая X-координата
  21.         local   workY:DWORD         ; Текущая Y-координата
  22.         local   regFlag:BYTE        ; Флаг прозрачности
  23.         local   regStart:DWORD      ; Начало прозрачной области в строке
  24.  
  25.         ; Сохранить все регистры
  26.         pusha
  27.  
  28.         ; Найти в ресурсах картинку с маской
  29.         invoke  FindResource,[hInstance],[BitmapID],RT_BITMAP
  30.         ; Загрузить картинку с маской
  31.         invoke  LoadResource,[hInstance],eax
  32.         ; Получить указатель на память с картинкой
  33.         invoke  LockResource,eax
  34.         ; Сохранить указатель
  35.         mov     edi,eax
  36.  
  37.         ; Получить все необходимые данные напрямую из файла
  38.         mov     ecx,[eax+00h]
  39.         mov     esi,ecx                 ; Размер заголовка
  40.  
  41.         add     ecx,edi
  42.         mov     [palData],ecx           ; Указатель на начало палитры
  43.  
  44.         mov     ecx,[eax+04h]           ; Ширина картинки
  45.         mov     [bmpX],ecx
  46.         mov     ecx,[eax+08h]           ; Высота картинки
  47.         mov     [bmpY],ecx
  48.  
  49.         mov     ecx,[eax+20h]           ; Вычислить размер палитры
  50.         or      ecx,ecx
  51.         jnz     @f
  52.  
  53.         ; Если количество цветов = 0, значит используется максимальное
  54.         ; доступное количество цветов для 8 бит, то есть 256 цветов
  55.  
  56.         mov     ecx,256
  57. @@:
  58.         shl     ecx,2
  59.         add     ecx,esi
  60.         add     ecx,edi
  61.         mov     [bitmapData],ecx        ; Указатель на начало данных
  62.  
  63.         ; Получить прозрачный цвет картинки. Подразумевается,
  64.         ; что самый левый верхний пиксел прозрачный. Если это не так,
  65.         ; то надо записать нужное значение в переменную TransColor
  66.         stdcall GetPixelColorAt,0,0,[bmpX],[bmpY],[bitmapData],[palData]
  67.         mov     [TransColor],eax
  68.  
  69.         ; Cоздать главный регион для окна
  70.         invoke  CreateRectRgn,0,0,[bmpX],[bmpY]
  71.         mov     [hRMain],eax
  72.  
  73.         ; Установить начальные координаты
  74.         mov     [workY],0
  75. .loop_y:
  76.         mov     [workX],0
  77.         mov     [regStart],0
  78.         mov     [regFlag],0
  79. .loop_x:
  80.         ; Получить цвет текущей точки
  81.         stdcall GetPixelColorAt,[workX],[workY],[bmpX],[bmpY],\
  82.                 [bitmapData],[palData]
  83.         cmp     eax,[TransColor]
  84.         jne     .chk_pixel
  85.  
  86.         ; Точка прозрачная
  87.         cmp     [regFlag],1
  88.         je      .next_pixel
  89.         mov     eax,[workX]
  90.         mov     [regStart],eax
  91.         mov     [regFlag],1
  92.         jmp     .next_pixel
  93.  
  94. .chk_pixel:
  95.         ; Если точка непрозрачная и флаг не установлен
  96.         cmp     [regFlag],1
  97.         jne     .next_pixel
  98.  
  99.         mov     eax,[workY]
  100.         inc     eax
  101.  
  102.         ; Вырезать прозрачный блок из главного региона
  103.         invoke  CreateRectRgn,[regStart],eax,[workX],[workY]
  104.         push    eax
  105.         invoke  CombineRgn,[hRMain],[hRMain],eax,RGN_DIFF
  106.         ; Освободить память региона
  107.         invoke  DeleteObject
  108.  
  109.         ; Сбросить флаг и длину региона
  110.         mov     [regFlag],0
  111.  
  112. .next_pixel:
  113.         inc     [workX]
  114.         mov     eax,[workX]
  115.         cmp     eax,[bmpX]
  116.         jb      .loop_x
  117.  
  118.         cmp     [regFlag],1
  119.         jne     @f
  120.  
  121.         mov     eax,[workY]
  122.         inc     eax
  123.  
  124.         ; Вырезать прозрачный блок из главного региона
  125.         invoke  CreateRectRgn,[regStart],eax,[workX],[workY]
  126.         push    eax
  127.         invoke  CombineRgn,[hRMain],[hRMain],eax,RGN_DIFF
  128.         ; Освободить память региона
  129.         invoke  DeleteObject
  130. @@:
  131.         inc     [workY]
  132.         mov     eax,[workY]
  133.         cmp     eax,[bmpY]
  134.         jb      .loop_y
  135.  
  136.         ; Установить регион для окна с его перерисовкой
  137.         invoke  SetWindowRgn,[hwnddlg],[hRMain],TRUE
  138.         ; Освободить память региона
  139.         invoke  DeleteObject,[hRMain]
  140.  
  141.         ; Восстановить все регистры
  142.         popa
  143.         ret
  144. endp
Функция получилась универсальная и самодостаточная, размер картинки определяется автоматически, никакие дополнительные данные не требуются, но проверка на наличие и доступность ресурсов не производится. Напоминаю, что прозрачным цветом фона считается цвет точки в левом верхнем углу картинки, имейте это в виду при создании исходного растрового изображения. Вызывается функция в обработчике инициализации окна WM_INITDIALOG:
  1. wminitdialog:
  2.         ; Установить скин для диалогового окна
  3.         stdcall SetBMPSkin,[hwnddlg],[hInstance],BITMAP_ID
  4.         jmp     processed
В результате получается вот такое симпатичное окно:

Окно нестандартной формы
Окно нестандартной формы

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

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

Примеры программ с исходными текстами (FASM)Примеры программ с исходными текстами (FASM)

Window.BMP.Skin.Demo.zip (136,680 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 7622 | Комментариев: 11

Метки: Assembler, окна
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Лёха (06.02.2017 в 13:34):
Нашел MASM-ский исходник на http://www.allasm.ru/distr.php В нем есть различные способы реализации есть в том числе кнопки реализация регионом.Пытаюсь переделать на FASM.
ManHunter (01.02.2017 в 15:28):
Можно, но только картинкой.
Лёха (01.02.2017 в 14:28):
А можно ли сделать кнопку нестандартной формы?
brute (23.08.2014 в 12:21):
Господи! Почти 300 строк Фasma! "получить цвет точки", "вычислить указатель точки", "проверить вылидность координат"...пока это держал в голове, уже забыл что хотел накодить.. Теперь прежде чем что-то кодить, надо изучить форматы bmp,jpg,gif,mp3,avi,итд итп? Наверное, это очень круто, но я тупоават и ленив для этого. Помню, как люди радовались, когда появились winAPI: сами следят за событиями, работают со строками, выводят целое окно на экран! Сейчас же писать на winapi считается пыткой, тратой времени в борьбе с багами. Быть может люди обленились? Но как заставить себя разбираться в этом, когда есть готовые кросплатформенные и кроссразрядные библиотеки? (которые иногда эффективнее аналогов на api, т.к. используют новые регистры или DirectX) Почему микрософт сама не понаделала удобных api для работы со строками, памятью, изображениями? В моем примере exe'шник 8,5кб: как PB это сделал - не знаю, но рисунка в ресурсах нет, возможно он сжат.
https://cloud.mail.ru/public/3...w.Region.rar
ManHunter (13.10.2012 в 02:50):
Может сперва пару-тройку месяцев (а лучше лет) самостоятельно поизучать теорию? Это элементарные вопросы, самые азы, а здесь не бесплатные курсы с готовыми ответами на все вопросы.
Александр (12.10.2012 в 22:23):
А можно еще вопрос, я хочу отлавливать нажатия других клавиш как это сделать в этом примере?
ManHunter (10.10.2012 в 20:34):
Чо тут непонятного? Для обработчика в старшем слове дворда [wparam] должно быть значение BN_CLICKED, в младшем слове ID кнопки, то есть  IDCANCEL. Через shl переводят константу BN_CLICKED из младшего слова в старшее и складывают с ID нажатой кнопки.
Александр (10.10.2012 в 20:01):
Здравствуйте. Я не могу понять данный фрагмент.
Можете подробно объяснить.

wmcommand:
        cmp     [wparam],BN_CLICKED shl 16 + IDCANCEL
        je      wmclose

Заранее благодарю.
Alexey (25.03.2012 в 21:12):
Молодца
ManHunter (31.08.2009 в 16:11):
Для PNG примеров нет, там другой принцип наложения маски на окно.
==DJ==[ZLO] (31.08.2009 в 15:50):
Доброго времени суток ManHunter!
Интересная статья, а нет ли у Вас примера с форматом PNG для асма ?
Заранее спасибо.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0038 сек.) / Память: 4.5 Mb
Наверх