Blog. Just Blog

GIF-анимация на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
GIF-анимация на Ассемблере
GIF-анимация на Ассемблере

Выводить статическое изображение на форму мы уже научились, причем разными способами, теперь давайте попробуем вывести анимированный GIF-файл. Для этого даже не понадобится углубляться в дебри формата, будем работать с высокоуровневыми функциями GDI+.

Сперва надо инициализировать систему GDI+ и загрузить изображение. Это мы все уже делали не один раз, тут ничего сложного. Напомню только, что имя файла изображения должно быть в юникоде.
  1. ; Структура для работы с GDI+
  2. struct GdiplusStartupInput
  3.     GdiplusVersion           dd ?
  4.     DebugEventCallback       dd ?
  5.     SuppressBackgroundThread dd ?
  6.     SuppressExternalCodecs   dd ?
  7. ends
  1.         ; Инициализация GDI+
  2.         mov     [gdiplusSInput.GdiplusVersion],1
  3.         mov     [gdiplusSInput.DebugEventCallback],0
  4.         mov     [gdiplusSInput.SuppressBackgroundThread],0
  5.         mov     [gdiplusSInput.SuppressExternalCodecs],0
  6.         invoke  GdiplusStartup,gdiplusToken,gdiplusSInput,0
  7.  
  8.         ; Загрузить картинку из файла
  9.         invoke  GdipLoadImageFromFile,fname,hImage
Теперь, поскольку это анимированный GIF-файл, надо получить количество кадров и интервалы между ними.
  1. PropertyTagFrameDelay = 0x5100
  2.         ...
  3.  
  4.         ; Проверить количество пространств
  5.         invoke  GdipImageGetFrameDimensionsCount,[hImage],tmp
  6.         cmp     [tmp],0
  7.         je      loc_error
  8.         ; Получить идентификатор первого пространства
  9.         invoke  GdipImageGetFrameDimensionsList,[hImage],dimID,1
  10.  
  11.         ; Получить количество кадров в первом пространстве
  12.         invoke  GdipImageGetFrameCount,[hImage],dimID,frameCount
  13.  
  14.         ; Получить значение задержек между кадрами
  15.         invoke  GdipGetPropertyItemSize,[hImage],PropertyTagFrameDelay,tmp
  16.         mov     eax,[tmp]
  17.         shl     eax,2
  18.         invoke  GdipAlloc,eax
  19.         mov     [delayData],eax
  20.         invoke  GdipGetPropertyItem,[hImage],PropertyTagFrameDelay,\
  21.                 [tmp],[delayData]
Для получения интервалов между кадрами используется запрос метаданных при помощи пары уже известных вам функций GdipGetPropertyItemSize и GdipGetPropertyItem. Почему нельзя использовать какое-то фиксированное значение? Дело в том, что анимация GIF так устроена, что между отдельными кадрами интервал может быть разный, да и скорость анимации для разных картинок может быть разной. После выполнения этого кода в переменной frameCount у нас находится количество кадров анимации, а в delayData - массив DWORD'ов со значениями интервалов между каждым кадром.

Дальше дело техники. Надо вывести поочередно все кадры, учитывая время задержки между ними. Для этого взводится таймер, на каждый тик которого уменьшается интервал текущей задержки.
  1.         ; Данные для самого первого кадра и нулевая задержка для запуска
  2.         mov     [dDelay],0
  3.         mov     [dFrame],0
  4.         invoke  SetTimer,[hwnddlg],1,100,0
Как только задержка будет отработана, при помощи функции GdipImageSelectActiveFrame изображение переключается на следующий кадр и выводится на форму в точности как мы это делали для статичных изображений. Даже код масштабирования изображения остался без изменений.
  1.         ; Интервал между кадрами уже прошел?
  2.         cmp     [dDelay],0
  3.         je      @f
  4.         dec     [dDelay]
  5.         jmp     processed
  6. @@:
  7.         ; Установить следующий кадр
  8.         invoke  GdipImageSelectActiveFrame,[hImage],dimID,[dFrame]
  9.  
  10.         mov     [hBitmap],0
  11.  
  12.         ; Создать HBITMAP для установки на static
  13.         invoke  GdipCreateHBITMAPFromBitmap,[hImage],\
  14.                 hBitmap,0x00FFFFFF
  15.  
  16.         ; Получить размеры изображения
  17.         invoke  GetObject,[hBitmap],sizeof.BITMAP,bm
  18.         ; Создать виртуальный битмап для превьюшки
  19.         invoke  GetDC,NULL
  20.         invoke  CreateCompatibleBitmap,eax,IMG_X,IMG_Y
  21.         mov     [imgBitmap],eax
  22.         invoke  CreateCompatibleDC,NULL
  23.         mov     [hDC1],eax
  24.         invoke  CreateCompatibleDC,NULL
  25.         mov     [hDC2],eax
  26.         invoke  SelectObject,[hDC1],[imgBitmap]
  27.         mov     [hOld1],eax
  28.         invoke  SelectObject,[hDC2],[hBitmap]
  29.         mov     [hOld2],eax
  30.  
  31.         ; Размеры области заливки
  32.         mov     [rct.top],0
  33.         mov     [rct.left],0
  34.         mov     [rct.bottom],IMG_Y
  35.         mov     [rct.right],IMG_X
  36.  
  37.         ; Заливка фона виртуального битмапа
  38.         invoke  GetSysColorBrush,COLOR_BTNFACE
  39.         invoke  FillRect,[hDC1],rct,eax
  40.  
  41.         ; При необходимости подогнать большое изображение
  42.         ; под размер области превьюшки
  43.         mov     eax,[bm.bmWidth]
  44.         mov     [dImageWidth],eax
  45.         mov     eax,[bm.bmHeight]
  46.         mov     [dImageHeight],eax
  47.  
  48.         ; Размеры больше окна превьюшки?
  49.         cmp     [dImageWidth],IMG_X
  50.         ja      thumb_fix_size
  51.         cmp     [dImageHeight],IMG_Y
  52.         ja      thumb_fix_size
  53.  
  54.         ; Нарисовать как есть
  55.         jmp     thumb_draw
  56.  
  57. thumb_fix_size:
  58.         ; Пресчет размеров по горизонтали
  59.         mov     eax,[dImageWidth]
  60.         cmp     eax,[dImageHeight]
  61.         jb      @f
  62.  
  63.         mov     eax,[dImageHeight]
  64.         xor     edx,edx
  65.         mov     ecx,IMG_X
  66.         imul    ecx
  67.         xor     edx,edx
  68.         mov     ecx,[dImageWidth]
  69.         idiv    ecx
  70.         mov     [dImageHeight],eax
  71.         mov     [dImageWidth],IMG_X
  72.  
  73.         ; Пресчет размеров по вертикали
  74.         cmp     [dImageHeight],IMG_Y
  75.         jbe     thumb_draw
  76. @@:
  77.         mov     eax,[dImageWidth]
  78.         xor     edx,edx
  79.         mov     ecx,IMG_Y
  80.         imul    ecx
  81.         xor     edx,edx
  82.         mov     ecx,[dImageHeight]
  83.         idiv    ecx
  84.         mov     [dImageWidth],eax
  85.         mov     [dImageHeight],IMG_Y
  86.  
  87. thumb_draw:
  88.         ; Смещение по X
  89.         mov     eax,IMG_X
  90.         sub     eax,[dImageWidth]
  91.         shr     eax,1
  92.         mov     [ImageX],eax
  93.  
  94.         ; Смещение по Y
  95.         mov     eax,IMG_Y
  96.         sub     eax,[dImageHeight]
  97.         shr     eax,1
  98.         mov     [ImageY],eax
  99.  
  100.         ; Наложить изображение в центр виртуального битмапа
  101.         invoke  SetStretchBltMode,[hDC1],HALFTONE
  102.         invoke  StretchBlt,[hDC1],[ImageX],[ImageY],\
  103.                 [dImageWidth],[dImageHeight],[hDC2],0,0,\
  104.                 [bm.bmWidth],[bm.bmHeight],SRCAND
  105.  
  106.         invoke  SelectObject,[hDC1],[hOld1]
  107.         invoke  DeleteDC,[hDC1]
  108.         invoke  SelectObject,[hDC2],[hOld2]
  109.         invoke  DeleteDC,[hDC2]
  110.  
  111.         ; Установить виртуальный битмап на static
  112.         invoke  SendDlgItemMessage,[hwnddlg],ID_IMG,\
  113.                 STM_SETIMAGE,IMAGE_BITMAP,[imgBitmap]
  114.  
  115.         invoke  DeleteObject,[hBitmap]
Согласно документации, интервал задается в сотых долях секунды, поэтому для каждого очередного значения надо будет пересчитывать количества срабатываний таймера заново.
  1.         ; Высчитать интервал между следующим кадром
  2.         mov     eax,[dFrame]
  3.         shl     eax,2
  4.         add     eax,[delayData]
  5.         add     eax,16
  6.         mov     eax,[eax]
  7.         xor     edx,edx
  8.         mov     ecx,60
  9.         imul    ecx
  10.         mov     ecx,1000
  11.         idiv    ecx
  12.         mov     [dDelay],eax
  13.  
  14.         ; Следующий кадр
  15.         mov     eax,[dFrame]
  16.         inc     eax
  17.         cmp     eax,[frameCount]
  18.         jb      @f
  19.         xor     eax,eax
  20. @@:
  21.         mov     [dFrame],eax
При достижении последнего кадра анимация "перематывается" на начало. Это в нашем случае допустимо, хотя принципиально неправильно. В GIF-анимации в качестве одного из параметров может быть записано количество повторов, которые должны быть воспроизведены. Да, в подавляющем большинстве случаев это будет бесконечное зацикленное воспроизведение, но если уж делать все совсем правильно, то обязательно надо запрашивать это значение при помощи функции GdipGetPropertyItem с параметром PropertyTagLoopCount. Нулевое значение соответствует бесконечной анимации, ненулевое означает, что количество повторов должно быть ограничено этим значением. Также не забывайте об освобождении задействованных ресурсов при закрытии окна.

В приложении пример программы с исходным текстом, которая загружает с диска анимированный GIF-файл и выводит его на форму.

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

GIF.Animation.Demo.zip (22,765 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (26.05.2024 в 14:40):
Нет
gob (26.05.2024 в 14:35):
Кстати, не пробовали создать гифку на ассемблере? Выходит шляпа с задержками между кадрами.
ASMiral (25.02.2022 в 18:21):
ЦитатаОн же в аттаче есть в готовом скомпилированном виде.

Да я примерно-то в курсе, что у вас везде аттачи с готовыми бинарниками. Уж не первый год ваш сайт читаю. Но мне всегда интересно самому и код глянуть и скомпилировать.
А за отзывчивость - спасибо, а то не все же бывают в курсе где и что лежит. Я вон как-то, помню, не мог догадаться как цитаты у вас в блоге вставляются. Но слава богу Манхантер, добрая душа пояснил, для особо одаренных. ))
ManHunter (25.02.2022 в 18:10):
Он же в аттаче есть в готовом скомпилированном виде.
ASMiral (25.02.2022 в 18:09):
Цитата"То, что не удается запрограммировать на Ассемблере, приходится паять"

А мне иногда даже паяльника бывает мало. В таких случаях применяю еще и напиильНЕГ, молоток и зубило. ))

P.S. А, вообще, тема интересная. Нужно будет вникнуть, когда будет свободное время. Примерчик скомпилировать.

Спасибо.
ManHunter (25.02.2022 в 17:39):
"То, что не удается запрограммировать на Ассемблере, приходится паять"
NobootRecord (23.02.2022 в 18:06):
Очень интересная штука - не знал, что на Ассемблере можно такое вытворять.
Самое прикольное - эта статья написана в мой день рождения :)

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

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

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