Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASM

AniGIF - a simple, animated GIF custom control

3.68/5 (18 votes)
5 Apr 2007CPOL2 min read 1   2.1K  
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.

ASM
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.

ASM
.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:

ASM
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)