Разбиваем GIF на кадры...

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

GDI+ кодек отлично загружает прозрачные GIF. Чего конечно не скажешь о сохранении. Как я не пытался, но сохранить прозрачную анимашку GIF мне не удалось. Хотя непрозрачные кодек сохраняет. И через некоторое время я так же выложу пример сохранения.

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

Для примера я взял вот эту анимашку:

mushroom.gif

Платформа: Windows
Автор: Станислав Будинов

#INCLUDE "windows.bi"
#INCLUDE "win/gdiplus.bi"

Using GDIPLUS

Dim As Guid  FrameDimensionTime = Type(&h6AEDBD6D,&h3FB5,&h418A,{&h83, &ha6, &h7f, &h45, &h22, &h9d, &hc8, &h72})
Dim wc As WNDCLASSEX
Dim  msg As MSG
Dim GDIPLUSSTARTUPINPUT As GDIPLUSSTARTUPINPUT
Dim As ULONG_PTR gdiplusToken
Redim Shared As GPIMAGE Ptr page() ' массив с фреймами
Dim Shared As GPIMAGE Ptr Img ' хендл для загрузки изображения
Dim As GUID   pageGuid = FrameDimensionTime
Dim As GUID Ptr pDimensionIDs
Dim As CLSID  encoderClsid
Dim As UINT icount,_ ' кол-во размерностей (может быть для gif и не требуется)
iframeCount,_ ' кол-во фреймов в одной размерности
iframeCountAll,_  ' общее кол-во фреймов
iPos ' счетчик-смещение в массиве фреймов

' Инициализация GDI+
GDIPLUSSTARTUPINPUT.GdiplusVersion = 1
If (GdiplusStartup(@gdiplusToken, @GDIPLUSSTARTUPINPUT, NULL) <> 0) Then
    Print "FAIL"
Endif

' Загрузка фото
If (GdipLoadImageFromFile(Wstr("mushroom.gif"), @Img) <> 0) Then
    Print "FAIL"
    Sleep
    End
Endif

' Получение кол-ва размерностей
GdipImageGetFrameDimensionsCount(Img,@icount)
' Выделение памяти под Guid(ы)
pDimensionIDs = Allocate(Sizeof(GUID)*icount)
' Получение Guid(ов)
GdipImageGetFrameDimensionsList(Img,pDimensionIDs, icount)

For i As Integer = 1 To icount
    ' Получение кол-ва фремов в одном Guid
    GdipImageGetFrameCount(img, @pDimensionIDs[i-1],@iframecount)
    ' Складываем фреймы
    iframeCountAll = iframecount+iframeCountAll-1
    ' Пересоздаем массив
    Redim Preserve page(iframeCountAll)

    For j As Integer = 0 To iframecount-1
        ' Получаем текущий фрейм
        GdipImageSelectActiveFrame(img,@pageGuid, j)
        ' Копируем изображение в массив фреймов
        GdipCloneImage(img,@page(iPos))
        ' Увеличиваем счетчик
        iPos+=1

    Next

Next

' Проверяем, анимированный ли GIF
' Если нет, пишем что его нужно загружать
' с помощью обычных средств загрузки фото
If iframeCountAll = 0 Then
    Print "it is not animated gif"
    Print "Please load gif via GdipLoadImageFromFile"
    Sleep
    End
Endif

Dim item As PROPERTYITEM Ptr
Dim Shared iSleeptime As Integer ' время задержки между фреймами
Dim As Integer  size
' Получаем размер памяти под Property
GdipGetPropertyItemSize(page(0),PropertyTagFrameDelay,@size)
item = Allocate(size)
' Получаем время задержки первого фрейма
GdipGetPropertyItem(page(0),PropertyTagFrameDelay, size, Item)
iSleeptime = Peek(Item->value)*10
Deallocate (item)

' Рисуем как положено с двойной буферизацией
Sub OnPaint(hwnd As HWND, img As Any Ptr)
    Static iframe As Integer = 1
    Dim As PVOID GpGraphics,GpGraphics2 
    GdipGetImageGraphicsContext(img,@GpGraphics)
    GdipCreateFromHWND(hwnd,@GpGraphics2)
    GdipGraphicsClear(GpGraphics,&hffffff00)
    GdipDrawImage(GpGraphics,page(iframe),10,10)
    GdipDrawImage(GpGraphics2,img,0,0)
    GdipDeleteGraphics(GpGraphics)
    GdipDeleteGraphics(GpGraphics2)
    iframe+=1
    If iframe>Ubound(page) Then iframe = 0
End Sub

Function WndProc(hWnd As HWND,uMsg As UINT,wParam As WPARAM,lParam As LPARAM) As Integer
    Static As PVOID img 
    Select Case uMsg
        Case WM_CREATE
            GdipCreateBitmapFromScan0(125, 130 , NULL, PixelFormat32bppARGB, NULL, @img)
            SetTimer(hwnd,1,iSleeptime,0)
        Case WM_CLOSE
            GdipDisposeImage(img)
            PostQuitMessage(0)
        Case WM_TIMER
            OnPaint(hwnd, img)
        Case Else
            Return DefWindowProc(hWnd,uMsg,wParam,lParam)
    End Select
End Function

With wc
    .hInstance=GetModuleHandle(0)
    .cbSize=SizeOf(WNDCLASSEX)
    .style=CS_HREDRAW Or CS_VREDRAW
    .lpfnWndProc=@WndProc
    .lpszClassName=StrPtr("class")
    .hCursor=LoadCursor(NULL,IDC_ARROW)
End With

RegisterClassEx(@wc)
CreateWindowEx(0,wc.lpszClassName,"DrawGIF",WS_OVERLAPPEDWINDOW Or WS_VISIBLE,200,200,100,150,0,0,wc.hInstance,0)

While GetMessage(@msg,0,0,0)
    TranslateMessage(@msg)
    DispatchMessage(@msg)
Wend


Deallocate(pDimensionIDs)
For i As Integer = 0 To Ubound(page)
    GdipDisposeImage(page(i))
Next
GdipDisposeImage(img)
GdiplusShutdown gdiplusToken