65.9K
CodeProject 正在变化。 阅读更多。
Home

AniGIF - 一个简单的动画 GIF 自定义控件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.68/5 (18投票s)

2007年1月2日

CPOL

2分钟阅读

viewsIcon

87506

downloadIcon

2149

使用 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。屏幕上的渲染是容易的部分,可以从 AniGIFControlProcWM_PAINT 处理程序中研究。 欢迎提出任何问题或建议,以改进此项目。

您可以在 WinAsm Studio 网站上找到最新版本。

未使用。

Swagler 使用 AniGIF 控件实现了一个 C 语言的图形查看器。您可以在 这里 获取它。

© . All rights reserved.