AniGIF - a simple, animated GIF custom control
Display simple, animated GIFs in your applications using the AniGIF custom control (packaged as a DLL and a static library).
Introduction
I decided to write a free and open source GIF static library/DLL, both for educational purposes and for my real needs to include some simple and animated GIFs in my applications.
Documentation
The Cover Sheet for the GIF89a Specification by CompuServe Incorporated is a comprehensive piece of information, and has been included in the attached source files.
Implementation
Based on the GIF89a Specification, the "brain" of the AniGIF custom control is the LoadGIF
procedure in the AniGIF.asm file from which I will present the most important parts:
Check if it is a valid GIF file, get its width and height, get packed fields, check whether there is a global color map, get the color resolution, the sort flag, the size of the global color table, and the background color index.
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
The next important thing to do is to keep moving forward in the file until we hit the Image Descriptor Block. Immediately following this block is the actual image data. We will copy from the start of the GIF until the end of the image data.
.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
At this stage, we are able to create the individual GIF frames and store them in memory so that we will be using them in sequence to display the animation:
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
Final Note
We've managed to decode simple, animated GIFs by parsing the files from start to end while storing all the necessary information in memory. The rendering on the screen is the easy part, and can be studied from the WM_PAINT
handler of the AniGIFControlProc
. Feel free to ask any questions or make any suggestions you think will improve this project.
You can find the latest version from the WinAsm Studio site.
Edit
Swagler has implemented a Graphic Viewer in C using the AniGIF control. You can get it here.