65.9K
CodeProject is changing. Read more.
Home

AniGIF - a simple, animated GIF custom control

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.68/5 (18 votes)

Jan 2, 2007

CPOL

2 min read

viewsIcon

86552

downloadIcon

2114

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.