AniGIF - 一个简单的动画 GIF 自定义控件
使用 AniGIF 自定义控件(打包为 DLL 和静态库)在您的应用程序中显示简单的动画 GIF。
引言
我决定编写一个免费且开源的 GIF 静态库/DLL,既用于教育目的,也为了满足我在应用程序中包含一些简单动画 GIF 的实际需求。
文档
CompuServe Incorporated 的 GIF89a 规范封面 是一份全面的信息,已包含在附带的源代码文件中。
实现
基于 GIF89a 规范,AniGIF 自定义控件的“大脑”是 AniGIF.asm 文件中的 LoadGIF
过程,我将展示其中最重要的部分。
检查它是否是有效的 GIF 文件,获取其宽度和高度,获取打包字段,检查是否存在全局颜色映射,获取颜色分辨率、排序标志、全局颜色表的大小以及背景颜色索引。
MOV EDI,lpRawData
MOV EAX,EDI
ADD EAX,dwRawDataSize
SUB EAX,2 ;<---------------Look: Prevent crashes
MOV dwRawDataEnd,EAX
.If DWORD PTR [EDI]!='8FIG' || (WORD PTR [EDI+4]!='a7' && WORD PTR [EDI+4]!='a9')
Invoke MessageBox,hCtrl,Offset szErrorNotAValidSignature,Offset szControlName,MB_OK
JMP Done
.EndIf
ADD EDI,GIFSIGNATURELENGTH
XOR EAX,EAX
MOV AX,WORD PTR [EDI]
MOV [EBX].GIFDATA.Screen.right,EAX
ADD EDI,SCREENWIDTHLENGTH
MOV AX,WORD PTR [EDI]
MOV [EBX].GIFDATA.Screen.bottom,EAX
ADD EDI,SCREENHEIGHTLENGTH
MOV AL,BYTE PTR [EDI]
;No need to store it since we store individual properties
;MOV [EBX].GIFDATA.PackedFields,AL
MOV DL,AL
;Is there a Global Color Map?
;We need bit 7 (counting starts from 0)
AND DL,BIT8
SHR DL,7
MOV [EBX].GIFDATA.GlobalColorTableFlag,AL
MOV DL,AL
;we need bits 4,5,6 (counting starts from 0)
AND DL,(BIT5 OR BIT6 OR BIT7)
SHR DL,4
INC DL
;# bits of color resolution
MOV [EBX].GIFDATA.ColorResolution,DL
MOV DL,AL
;We need the the 3rd bit (counting starts from 0)
AND DL,BIT4
.If DL
;For GIF87a this should always be 0!
;Invoke MessageBox,hCtrl,Offset szErrorNotValidBit3OfByte5,Offset szControlName,MB_OK
;JMP Done
MOV [EBX].GIFDATA.GSortFlag,TRUE
.EndIf
MOV DL,AL
AND DL,(BIT1 OR BIT2 OR BIT3) ;we need the the last 3 bits
;Therefore 2^(DL+1) gives nr of colors
;MOV [EBX].GIFDATA.SizeOfGlobalColorTable,DL
INC EDI
MOV AL,BYTE PTR [EDI]
MOV [EBX].GIFDATA.BackgroundColorIndex,AL
接下来的重要步骤是在文件中继续前进,直到遇到图像描述符块。紧随此块之后是实际的图像数据。我们将从 GIF 的开头复制到图像数据的结尾。
.While BYTE PTR [EDI] != IMAGESEPARATOR && EDI<dwRawDataEnd
.If BYTE PTR [EDI] == EXTENSIONINTRODUCER
MOV AL,BYTE PTR[EDI+1]
.If AL == APPLICATIONEXTENSION
;This needs to check if it is the Netscape App Ext to read the value
;for the number of iterations the loop should be executed to display the GIF
;Jump to start of Application Data Sub Blocks
ADD EDI,14
XOR EAX,EAX
MOV AL,BYTE PTR [EDI]
ADD EDI,EAX
;If Next byte is 0 then this is the Block terminator
;Keep reading Comment Blocks until done
.While BYTE PTR [EDI+1]!=0
MOV AL,BYTE PTR [EDI+1]
ADD EDI,EAX
INC EDI
.EndW
.ElseIf AL==COMMENTEXTENSION
;Jump length of first Comment Data Sub Block
;First Byte of this Data block is always the Size
;not including this Byte!
ADD EDI,2
XOR EAX,EAX
MOV AL,BYTE PTR [EDI]
ADD EDI,EAX
;If Next byte is 0 then this is the Block terminator
;Keep reading Comment Blocks until done
.While BYTE PTR [EDI+1]!=0
MOV AL,BYTE PTR [EDI+1]
ADD EDI,EAX
INC EDI
.EndW
INC EDI
.ElseIf AL==GRAPHICCONTROLEXTENSION
;Here we can derive key props concerning the playback of the GIF.
MOV AL,BYTE PTR [EDI+2+1]
;Not really need to store PackedFields since we store info in individual properties
;MOV [ESI].FRAME.PackedFields,AL
MOV DL,AL
AND DL,BIT5
.If DL
MOV [ESI].FRAME.DisposalMethod,BIT3
.EndIf
MOV DL,AL
AND DL,BIT4
.If DL
OR [ESI].FRAME.DisposalMethod,BIT2
.EndIf
MOV DL,AL
AND DL,BIT3
.If DL
OR [ESI].FRAME.DisposalMethod,BIT1
.EndIf
MOV DL,AL
AND DL,BIT2
.If DL
MOV [ESI].FRAME.UserInputFlag,TRUE
.EndIf
MOV DL,AL
AND DL,BIT1
.If DL
MOV [ESI].FRAME.TransparentColorFlag,TRUE
.EndIf
MOV AX,WORD PTR [EDI+2+2]
MOV [ESI].FRAME.DelayTime,AX
MOV AL,BYTE PTR [EDI+2+4]
MOV [ESI].FRAME.TransparentColorIndex,AL
;PrintText "TransparentColorIndex"
;PrintDec AL
ADD EDI,GRAPHICCONTROLEXTENSIONLENGTH
.ElseIf AL==PLAINTEXTEXTENSION
;Jump to start of Plain Text Data Sub Blocks
XOR EAX,EAX
MOV AL,BYTE PTR [EDI+14]
ADD EDI,EAX
;if Next byte is 0 then this is the Block terminator
;Keep reading Comment Blocks until done
.While BYTE PTR [EDI+1]!=0
MOV AL,BYTE PTR [EDI+1]
ADD EDI,EAX
INC EDI
.EndW
.EndIf
.EndIf
;Check to make sure we are not at the end of the file
;.If BYTE PTR [EDI]==TRAILER
.If EDI>=dwRawDataEnd
JMP EndParse
.EndIf
;.EndIf
INC EDI
.EndW
在此阶段,我们能够创建单个 GIF 帧并将它们存储在内存中,以便按顺序使用它们来显示动画。
Invoke CoInitialize, NULL
Invoke GetDC, NULL ; screen DC
MOV compDC, EAX
Invoke CreateCompatibleDC, compDC
MOV tempDC, EAX
MOV rc.left,0
MOV rc.top,0
ADD dwRawDataSize,2 ;Restore length back
MOV ESI,lpFrames
MOV ECX,[EBX].GIFDATA.NumberOfFrames
MOV EAX,SizeOf FRAME
MUL ECX
;Now EAX is the number of bytes for ALL Frames
MOV EDI,EAX
ADD EDI,ESI
.While ESI<EDI
Invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,dwRawDataSize
MOV EBX,EAX
PUSH EBX
;Write data for this Frame up to the start of the Local Color Table
MOV ECX,lpFrames
MOV EDX,[ECX].FRAME.GifStart
SUB EDX,lpRawData
;DEC EDX
MOV dwBytesWritten,EDX
Invoke RtlMoveMemory,EBX,lpRawData,EDX
ADD EBX,dwBytesWritten
;Write out entire GIf Frame. This will start at our stored start point.
;The length will be our stored ending point minus the starting point.
MOV EDX,[ESI].FRAME.GifStart
MOV ECX,[ESI].FRAME.GifEnd
SUB ECX,EDX
ADD dwBytesWritten,ECX
PUSH ECX
Invoke RtlMoveMemory,EBX,EDX,ECX
POP ECX
ADD EBX,ECX
;Write Block Terminator - ZERO
ADD dwBytesWritten,1
Invoke RtlMoveMemory,EBX,Offset szZero,1
ADD EBX,1
;Write DUMMY Control Block. Some programs depend on seeing
;the next Control Block to know the previous data block is done.
ADD dwBytesWritten,2
Invoke RtlMoveMemory,EBX,Offset szDummyControlBlock,2
ADD EBX,2
;Write the Trailer Block
ADD dwBytesWritten,1
Invoke RtlMoveMemory,EBX,Offset szTrailerBlock,1
POP EBX
Invoke CoTaskMemAlloc, dwBytesWritten
MOV pGlobal, EAX
Invoke RtlMoveMemory,pGlobal,EBX,dwBytesWritten ; ;Copy picture into task memory
Invoke HeapFree,hHeap,0,EBX
;Create a stream for the picture object's creator
Invoke CreateStreamOnHGlobal, pGlobal, TRUE, ADDR pStream
Invoke OleLoadPicture, pStream, NULL,TRUE, ADDR IID_IPicture, ADDR pPicture
;read out the width and height of the IPicture object
;(IPicture)pPicture::get_Width(*hmWidth)
LEA EAX, hmWidth
PUSH EAX
MOV EAX, pPicture
PUSH EAX
MOV EAX, [EAX]
CALL [EAX].IPicture.get_Width
;(IPicture)pPicture::get_Height(*hmHeight)
LEA EAX, hmHeight
PUSH EAX
MOV EAX, pPicture
PUSH EAX
MOV EAX, [EAX]
CALL [EAX].IPicture.get_Height
;Convert himetric to pixels
Invoke GetDeviceCaps, compDC, LOGPIXELSX
Invoke MulDiv, hmWidth, EAX, HIMETRIC_INCH
MOV rc.right,EAX
Invoke GetDeviceCaps, compDC, LOGPIXELSY
Invoke MulDiv, hmHeight, EAX, HIMETRIC_INCH
MOV rc.bottom,EAX
XOR EAX, EAX
SUB EAX, hmHeight
MOV neghmHeight, EAX
Invoke CreateCompatibleBitmap, compDC, rc.right, rc.bottom
MOV [ESI].FRAME.hBitMap,EAX
Invoke SelectObject, tempDC, [ESI].FRAME.hBitMap
MOV OldBitmap, EAX
;OK, now we have our bitmap mounted onto our temporary DC
.If [ESI].FRAME.TransparentColorFlag
Invoke CreateSolidBrush,[ESI].FRAME.TransparentColor
PUSH EAX
Invoke FillRect,tempDC,ADDR rc,EAX
POP EAX
Invoke DeleteObject,EAX
.Else
;Invoke GetStockObject,WHITE_BRUSH
Invoke GetWindowLong,hCtrl,0
;LOOK V1.0.4.0 (we had crashes because EBX has already changed
Invoke CreateSolidBrush,[EAX].GIFDATA.BkColor
PUSH EAX
Invoke FillRect,tempDC,ADDR rc,EAX
CALL DeleteObject
.EndIf
;Let's blit
; (IPicture)pPicture::Render(hdc, x, y, cx, cy, \
; xpos_himetric, ypos_himetric, \
; xsize_himetric, ysize_himetric, *rectBounds)
PUSH NULL ; *rectBounds
PUSH neghmHeight
PUSH hmWidth
PUSH hmHeight
PUSH 0
PUSH rc.bottom
PUSH rc.right
PUSH 0
PUSH 0
PUSH tempDC
MOV EAX, pPicture
PUSH EAX
MOV EAX, [EAX]
CALL [EAX].IPicture.Render
; we now have the bitmap blitted, let's get it off the dc and clean up.
; we're not going to check for errors, cause we did our importaint thing
; and if these fail now, other things will fall apart anyway
Invoke SelectObject, tempDC, OldBitmap
;Release the stream
MOV EAX, pStream
PUSH EAX
MOV EAX, [EAX]
CALL [EAX].IPicture.Release
;Release the Picture object
MOV EAX, pPicture
PUSH EAX
MOV EAX, [EAX]
CALL [EAX].IPicture.Release
;Invoke CoTaskMemFree, pGlobal ; free task memory
ADD ESI,SizeOf FRAME
.EndW
Invoke DeleteDC, tempDC
Invoke ReleaseDC,NULL,compDC
Invoke CoUninitialize ;Done with COM
最后说明
我们已经通过从头到尾解析文件并将所有必要信息存储在内存中来解码简单的动画 GIF。屏幕上的渲染是容易的部分,可以从 AniGIFControlProc
的 WM_PAINT
处理程序中研究。 欢迎提出任何问题或建议,以改进此项目。
您可以在 WinAsm Studio 网站上找到最新版本。
未使用。
Swagler 使用 AniGIF 控件实现了一个 C 语言的图形查看器。您可以在 这里 获取它。