Code archives/File Utilities/GIFLoad Module for B3D/B+

This code has been declared by its author to be Public Domain code.

Download source code

GIFLoad Module for B3D/B+ by markcw
(Posted 19 years ago)
This page used to have a LoadImageGIF function which could load most single-frame gif files. Then I decided to work on animated gifs and discovered that the LoadImageGIF function didn't follow the gif spec properly.

I have been over the gif89a spec several times and written and rewritten the code and have finally got some solid code to load animated gifs. I am sharing it so that anyone who would like to use gifs for their game can now do so without having to use a dll or a workaround with OpenMovie.

To have the GIFLoad functions highlighted, create a file in your userlibs folder called "gifload.decls" and copy/paste this code into it.
.lib " "

;GIFLoad Module Decls
GIFLoadImage%(filename$,firstframe,numframes)
GIFDelayTimes%(filename$,firstframe,numframes)
GIFFrames%(filename$,firstframe,numframes)
GIFLoops%(filename$)
GIFHeight%(filename$)
GIFWidth%(filename$)
GIFLoad%(filename$,GIFCLASS*)
GIFOutLine%(Buffer,Width,Height,GIFCLASS*)
GIFNextCode%(file,GIFCLASS*)

Save this code entry as "gifload.bb" and then include it in your main program. You can find example usage below.
;GIFLoad Module for B3D/B+
;Author: markcw, edited 26 Feb 2008
;LZW decoding based on C code by John Findlay for ImageShop32
;Create a file in your userlibs folder called "gifload.decls"

;.lib " "
;
;;GIFLoad Module Decls
;GIFLoadImage%(filename$,firstframe,numframes)
;GIFDelayTimes%(filename$,firstframe,numframes)
;GIFFrames%(filename$,firstframe,numframes)
;GIFLoops%(filename$)
;GIFHeight%(filename$)
;GIFWidth%(filename$)
;GIFLoad%(filename$,GIFCLASS*)
;GIFOutLine%(Buffer,Width,Height,GIFCLASS*)
;GIFNextCode%(file,GIFCLASS*)

Type GIFCLASS ;This is instead of using globals
 Field pCharBuff ;Pointer to next byte in block
 Field iPass ;First pass for interlaced images in GIFOutLine
 Field iLine ;Offset for addressing the bits in GIFOutLine
 Field pBits ;Scanline for bits
 Field Pitch ;Bytes are rounded up for image lines
 Field CurrCodeSize ;The current code size
 Field BitsLeft ;Used in GIFNextCode
 Field BytesLeft ;Used in GIFNextCode
 Field CurrByte ;Current byte
 Field GlobalBpp ;Global bit depth
 Field isInterlaced ;Is the image interlaced
 Field LocalBpp ;Local bit depth
 Field CodeMask[16] ;Masks for LZW compression algorithm
 Field CharBuff[279] ;Current block
 Field DIB ;DIB bank handle
 Field NextFilePos ;Used to jump to the next block
 Field isLocalTable ;Local Color Table Flag
 Field bBackIndex ;Background Color Index
 Field Disposal ;Disposal Method, 0..3
 Field isTransparent ;Transparent Color Flag
 Field wLeft ;Image Left Position
 Field wTop ;Image Top Position
 Field bTransIndex ;Transparent Color Index
End Type

Function GIFLoadImage(filename$,firstframe=1,numframes=0)
 ;Creates and returns an image from a gif, top-level function
 ;firstframe -> The frame to start drawing from, 1=first frame
 ;numframes -> The number of frames to draw, 0=all frames
 ;Uses GIFFrames, GIFWidth, GIFHeight and GIFLoad

 Local cl.GIFCLASS=New GIFCLASS ;Gif type
 Local frames,width,height,image,graphic,buffer,dib,bpp
 Local bits,wWidth,wHeight,pitch,src,dest,ix,iy,offset
 Local index,pixel,blue,green,red,rgb

 ;Set some initial variables
 frames=GIFFrames(filename$) ;Number of frames
 If firstframe>0 Then firstframe=firstframe-1 Else firstframe=0
 If firstframe>frames-1 Then firstframe=frames-1 ;Limit firstframe
 If numframes<=0 Then numframes=frames-firstframe ;Limit numframes
 If numframes>frames-firstframe Then numframes=frames-firstframe
 frames=firstframe+numframes ;Limit number of frames
 width=GIFWidth(filename$) ;Screen width
 height=GIFHeight(filename$) ;Screen height
 image=CreateImage(width,height,numframes)
 graphic=CreateImage(width,height,3) ;0=previous, 1=this, 2=empty

 For buffer=0 To frames-1 ;Loop through the frames

  dib=GIFLoad(filename$,cl) ;Load the next frame
  If dib=0 ;Avoid errors
   Delete cl ;Delete the class
   FreeImage graphic ;Free the graphic
   Return image ;Return the image
  EndIf
  bpp=PeekShort(dib,14) ;biBitCount
  bits=40+(PeekInt(dib,32)*4) ;biSize+(biClrUsed*4)
  wWidth=PeekInt(dib,4) ;biWidth
  wHeight=PeekInt(dib,8) ;biHeight
  pitch=((wWidth*bpp+31)/32)*4 ;DWORD-aligned

  If cl\Disposal=3 ;Restore to previous, store before drawing
   src=ImageBuffer(graphic,1) ;Copy this graphic to previous graphic
   dest=ImageBuffer(graphic,0)
   CopyRect cl\wLeft,cl\wTop,wWidth,wHeight,cl\wLeft,cl\wTop,src,dest
  EndIf

  ;Draw this graphic from the DIB
  LockBuffer ImageBuffer(graphic,1)
  For iy=0 To wHeight-1
   offset=bits+(pitch*(wHeight-1-iy)) ;Next scanline
   For ix=0 To wWidth-1
    If ix+cl\wLeft<width And iy+cl\wTop<height ;Pixel in bounds
     If bpp=1
      index=PeekByte(dib,offset+(ix Shr 3)) ;Get bit
      pixel=7-(ix Mod 8)
      index=(index And (1 Shl pixel)) Shr pixel
     ElseIf bpp=4
      index=PeekByte(dib,offset+(ix Shr 1)) ;Get nibble
      pixel=(1-(ix Mod 2)) Shl 2
      index=(index And (15 Shl pixel)) Shr pixel
     ElseIf bpp=8
      index=PeekByte(dib,offset+ix) ;Get byte
     EndIf
     If cl\isTransparent And cl\bTransIndex=index ;Transparent pixel
     Else ;Normal pixel
      index=40+(index Shl 2) ;Get palette index
      blue=PeekByte(dib,index)


      green=PeekByte(dib,index+1)
      red=PeekByte(dib,index+2)
      rgb=blue Or (green Shl 8) Or (red Shl 16)
      If rgb=0 Then rgb=$080808 ;Avoid transparent pixels
      WritePixelFast ix+cl\wLeft,iy+cl\wTop,rgb,ImageBuffer(graphic,1)
     EndIf
    EndIf
   Next
  Next
  UnlockBuffer ImageBuffer(graphic,1)
  FreeBank dib ;Free the DIB

  If buffer-firstframe>=0 ;If this frame is valid
   src=ImageBuffer(graphic,1) ;Copy this graphic to this frame
   dest=ImageBuffer(image,buffer-firstframe)
   CopyRect 0,0,width,height,0,0,src,dest
  EndIf

  ;Decide how to dispose of this graphic
  If cl\Disposal<2 ;0=Not specified, 1=Do not dispose, both do nothing
  ElseIf cl\Disposal=2 ;Restore to background
   src=ImageBuffer(graphic,2) ;Copy empty graphic to this graphic
   dest=ImageBuffer(graphic,1)
   CopyRect cl\wLeft,cl\wTop,wWidth,wHeight,cl\wLeft,cl\wTop,src,dest
  ElseIf cl\Disposal=3 ;Restore to previous
   src=ImageBuffer(graphic,0) ;Copy previous graphic to this graphic
   dest=ImageBuffer(graphic,1)
   CopyRect cl\wLeft,cl\wTop,wWidth,wHeight,cl\wLeft,cl\wTop,src,dest
  EndIf

 Next

 Delete cl ;Delete the class
 FreeImage graphic ;Free the graphic
 Return image ;Return the image

End Function

Function GIFDelayTimes(filename$,firstframe=1,numframes=0)
 ;Creates and returns a bank with delay times for a gif
 ;Bank values are in integers, the number of frames is at 0
 ;and the delay time for frame 1 is at 1*4, etc
 ;firstframe -> The frame to start reading from, 1=first frame
 ;numframes -> The number of frames to read, 0=all frames

 Local file,count,Sig$,bank,bPacked,isColorTable,blocksize
 Local byte,blocklabel,wDelayTime,frames

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  Return 0 ;Not a valid gif
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version

 bank=CreateBank(4) ;Create the delay times bank

 ;Logical Screen Descriptor block
 SeekFile(file,FilePos(file)+4) ;Skip Screen Width/Height
 bPacked=ReadByte(file) ;bPacked
 SeekFile(file,FilePos(file)+2) ;Skip BackgroundColor/AspectRatio

 ;Global Color Table block
 isColorTable=(bPacked And 128) Shr 7 ;Global Color Table Flag, bit 7
 If isColorTable
  blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
  SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
 EndIf

 ;Parse the blocks
 While Not Eof(file)

  byte=ReadByte(file)

  If byte=$21 ;Extension block (89a)
   blocklabel=ReadByte(file)

   If blocklabel=$01 ;Plain Text Extension block
    blocksize=ReadByte(file) ;Should be 12
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$F9 ;Graphic Control Extension block
    blocksize=ReadByte(file) ;Should be 4
    SeekFile(file,FilePos(file)+1) ;Skip bPacked
    wDelayTime=ReadShort(file) ;100ths of a second (1/100)
    SeekFile(file,FilePos(file)+1) ;Skip Transparent Color Index
    blocksize=ReadByte(file) ;Should be 0
    ResizeBank(bank,8+(frames Shl 2))
    PokeInt bank,4+(frames Shl 2),wDelayTime*10 ;Convert to millisecs

   ElseIf blocklabel=$FE ;Comment Extension block
    blocksize=ReadByte(file) ;Should be 1..255
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$FF ;Application Extension block
    blocksize=ReadByte(file) ;Should be 11
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   EndIf
  EndIf

  If byte=$2C ;Image Descriptor block (87a)
   blocklabel=byte
   SeekFile(file,FilePos(file)+8) ;Skip Image Left/Top/Width/Height
   bPacked=ReadByte(file)
   isColorTable=(bPacked And 128) Shr 7 ;Local Color Table Flag, bit 7
   If isColorTable ;Local Color Table block
    blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
    SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
   EndIf
   frames=frames+1 ;Increment frames
  EndIf

  If byte>1 And byte<13 ;Image Data block (87a)
   blocklabel=byte ;LZW bit range is 2..12
   blocksize=ReadByte(file) ;1..255
   While blocksize>0 ;Skip sub-blocks
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file)
   Wend
  EndIf

 Wend

 ;Modify the bank according to optional parameters
 If firstframe>0 Then firstframe=firstframe-1 Else firstframe=0
 If firstframe>frames-1 Then firstframe=frames-1 ;Limit firstframe
 If numframes<=0 Then numframes=frames-firstframe ;Limit numframes
 If numframes>frames-firstframe Then numframes=frames-firstframe
 For count=1 To numframes ;Move the values one at a time
  PokeInt bank,count*4,PeekInt(bank,(count+firstframe)*4)
 Next
 ResizeBank(bank,4+(numframes*4))
 PokeInt bank,0,numframes ;Store the frames

 CloseFile file ;Close the file
 Return bank ;Return the delay times bank

End Function

Function GIFFrames(filename$,firstframe=1,numframes=0)
 ;Returns the number of frames in a gif
 ;For this we have to manually count them since gifs
 ;don't have a field to store the number of frames
 ;firstframe -> The frame to start counting from, 1=first frame
 ;numframes -> The number of frames to count, 0=all frames

 Local file,count,Sig$,bPacked,isColorTable,blocksize
 Local byte,blocklabel,frames

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  Return 0 ;Gif header not valid
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version

 ;Logical Screen Descriptor block
 SeekFile(file,FilePos(file)+4) ;Skip Screen Width/Height
 bPacked=ReadByte(file) ;bPacked
 SeekFile(file,FilePos(file)+2) ;Skip BackgroundColor/AspectRatio

 ;Global Color Table block
 isColorTable=(bPacked And 128) Shr 7 ;Global Color Table Flag, bit 7
 If isColorTable
  blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
  SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
 EndIf

 ;Parse the blocks
 While Not Eof(file)

  byte=ReadByte(file)

  If byte=$21 ;Extension block (89a)
   blocklabel=ReadByte(file)

   If blocklabel=$01 ;Plain Text Extension block
    blocksize=ReadByte(file) ;Should be 12
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$F9 ;Graphic Control Extension block
    blocksize=ReadByte(file) ;Should be 4
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file) ;Should be 0

   ElseIf blocklabel=$FE ;Comment Extension block
    blocksize=ReadByte(file) ;Should be 1..255
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$FF ;Application Extension block
    blocksize=ReadByte(file) ;Should be 11
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   EndIf
  EndIf

  If byte=$2C ;Image Descriptor block (87a)
   blocklabel=byte
   SeekFile(file,FilePos(file)+8) ;Skip Image Left/Top/Width/Height
   bPacked=ReadByte(file)
   isColorTable=(bPacked And 128) Shr 7 ;Local Color Table Flag, bit 7
   If isColorTable ;Local Color Table block
    blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
    SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
   EndIf
   frames=frames+1 ;Increment frames
  EndIf

  If byte>1 And byte<13 ;Image Data block (87a)
   blocklabel=byte ;LZW bit range is 2..12
   blocksize=ReadByte(file) ;1..255
   While blocksize>0 ;Skip sub-blocks
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file)
   Wend
  EndIf

 Wend

 ;Modify the return value according to optional parameters
 If firstframe>0 Then firstframe=firstframe-1 Else firstframe=0
 If firstframe>frames-1 Then firstframe=frames-1 ;Limit firstframe
 If numframes<=0 Then numframes=frames-firstframe ;Limit numframes
 If numframes>frames-firstframe Then numframes=frames-firstframe

 CloseFile file ;Close the file
 Return numframes ;Return the number of frames

End Function

Function GIFLoops(filename$)
 ;Returns the number of loops in a gif, 0=forever
 ;This is the number of times the animation is repeated

 Local file,count,Sig$,bPacked,isColorTable,blocksize
 Local byte,blocklabel,app$,loops,frames

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  Return 0 ;Gif header not valid
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version

 ;Logical Screen Descriptor block
 SeekFile(file,FilePos(file)+4) ;Skip Screen Width/Height
 bPacked=ReadByte(file) ;bPacked
 SeekFile(file,FilePos(file)+2) ;Skip BackgroundColor/AspectRatio

 ;Global Color Table block
 isColorTable=(bPacked And 128) Shr 7 ;Global Color Table Flag, bit 7
 If isColorTable
  blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
  SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
 EndIf

 ;Parse the blocks
 While Not Eof(file)

  byte=ReadByte(file)

  If byte=$21 ;Extension block (89a)
   blocklabel=ReadByte(file)

   If blocklabel=$01 ;Plain Text Extension block
    blocksize=ReadByte(file) ;Should be 12
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$F9 ;Graphic Control Extension block
    blocksize=ReadByte(file) ;Should be 4
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file) ;Should be 0

   ElseIf blocklabel=$FE ;Comment Extension block
    blocksize=ReadByte(file) ;Should be 1..255
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$FF ;Application Extension block
    blocksize=ReadByte(file) ;Should be 11
    app$=""
    For count=1 To blocksize ;Read the info
     app$=app$+Chr(ReadByte(file))
    Next
    If app$="NETSCAPE2.0" ;Netscape's looping extension
     SeekFile(file,FilePos(file)+2) ;Skip 2 bytes
     loops=ReadShort(file) ;Number of times to loop, 0=forever
     SeekFile(file,FilePos(file)-4) ;Return to blocksize
    EndIf
    blocksize=ReadByte(file)
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   EndIf
  EndIf

  If byte=$2C ;Image Descriptor block (87a)
   blocklabel=byte
   SeekFile(file,FilePos(file)+8) ;Skip Image Left/Top/Width/Height
   bPacked=ReadByte(file)
   isColorTable=(bPacked And 128) Shr 7 ;Local Color Table Flag, bit 7
   If isColorTable ;Local Color Table block
    blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Table size, bits 0..2
    SeekFile(file,FilePos(file)+blocksize) ;Skip table if present
   EndIf
   frames=frames+1 ;Increment frames
  EndIf

  If byte>1 And byte<13 ;Image Data block (87a)
   blocklabel=byte ;LZW bit range is 2..12
   blocksize=ReadByte(file) ;1..255
   While blocksize>0 ;Skip sub-blocks
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file)
   Wend
  EndIf

 Wend

 CloseFile file ;Close the file
 Return loops ;Return the number of loops

End Function

Function GIFWidth(filename$)
 ;Returns the screen width of a gif
 ;This is the actual width of the image

 Local file,count,Sig$,wScreenWidth,wScreenHeight

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  Return 0 ;Gif header not valid
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version

 ;Logical Screen Descriptor block
 wScreenWidth=ReadShort(file)
 wScreenHeight=ReadShort(file)

 CloseFile file ;Close the file
 Return wScreenWidth ;Return the width

End Function

Function GIFHeight(filename$)
 ;Returns the screen height of a gif
 ;This is the actual height of the image

 Local file,count,Sig$,wScreenWidth,wScreenHeight

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  Return 0 ;Gif header not valid
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version

 ;Logical Screen Descriptor block
 wScreenWidth=ReadShort(file)
 wScreenHeight=ReadShort(file)

 CloseFile file ;Close the file
 Return wScreenHeight ;Return the height

End Function

Function GIFLoad(filename$,cl.GIFCLASS)
 ;Creates and returns a bank with a DIB for a gif frame
 ;Uses GIFOutLine and GIFNextCode, used by GIFLoadImage

 Local Stack[4096] ;Stack for storing pixels, bytes
 Local Suffix[4096] ;Suffix table, max number of LZW codes, bytes
 Local Prefix[4096] ;Prefix linked list, integers
 Local GlobalCols[256]
 Local LocalCols[256]
 Local file,count,Sig$,bPacked,isGlobalTable,blocksize
 Local GlobalColors,byte,blocklabel,wWidth,wHeight,LocalColors
 Local red,green,blue,frames,LZWCodeSize,BitCount,ncolors,size
 Local dib,pal,TopSlot,ClearCode,EndingCode,NewCodes,Slot,cc
 Local TempOldCode,OldCode,pStack,pBuffer,BufCount,Buffer,Code

 file=ReadFile(filename$)
 If file=0
  RuntimeError "File could not be opened"
 EndIf

 ;Header block
 For count=0 To 2
  Sig$=Sig$+Chr(ReadByte(file))
 Next
 If Sig$<>"GIF"
  CloseFile file ;Close the file
  Return 0 ;Gif header not valid
 EndIf
 SeekFile(file,FilePos(file)+3) ;Skip Version, both are supported

 cl\CodeMask[0]=$0000 ;LZW Code Masks
 cl\CodeMask[1]=$0001 : cl\CodeMask[2]=$0003 : cl\CodeMask[3]=$0007
 cl\CodeMask[4]=$000F : cl\CodeMask[5]=$001F : cl\CodeMask[6]=$003F
 cl\CodeMask[7]=$007F : cl\CodeMask[8]=$00FF : cl\CodeMask[9]=$01FF
 cl\CodeMask[10]=$03FF : cl\CodeMask[11]=$07FF : cl\CodeMask[12]=$0FFF
 cl\CodeMask[13]=$1FFF : cl\CodeMask[14]=$3FFF : cl\CodeMask[15]=$7FFF

 ;Logical Screen Descriptor block
 SeekFile(file,FilePos(file)+4) ;Skip Screen Width/Height
 bPacked=ReadByte(file) ;Packed Fields
 cl\bBackIndex=ReadByte(file) ;Background Color Index, ignored
 isGlobalTable=(bPacked And 128) Shr 7 ;Global Color Table Flag, bit 7
 blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Global Table size, bits 0..2
 SeekFile(file,FilePos(file)+1) ;Skip Aspect Ratio

 GlobalColors=blocksize/3 ;Number of global colors
 If GlobalColors<=2 ;Use number of colors to set bit depth
  cl\GlobalBpp=1
 ElseIf GlobalColors<=16
  cl\GlobalBpp=4
 Else
  cl\GlobalBpp=8
 EndIf

 ;Global Color Table block
 If isGlobalTable
  For count=0 To GlobalColors-1 ;Store global colors
   red=ReadByte(file)
   green=ReadByte(file)
   blue=ReadByte(file)
   GlobalCols[count]=(red Shl 16) Or (green Shl 8) Or blue
  Next
 Else
  For count=0 To GlobalColors-1 ;Create a 2/16/256 greyscale palette
   red=(255*count)/(GlobalColors-1)
   green=(255*count)/(GlobalColors-1)
   blue=(255*count)/(GlobalColors-1)
   GlobalCols[count]=(red Shl 16) Or (green Shl 8) Or blue
  Next
 EndIf

 ;Return to where we left off reading last call
 If cl\NextFilePos>0 Then SeekFile(file,cl\NextFilePos)

 cl\isTransparent=0 ;Make sure these extension variables are zero
 cl\bTransIndex=0
 cl\Disposal=0

 ;Parse the blocks
 While Not Eof(file)

  byte=ReadByte(file)

  If byte=$21 ;Extension block (89a)
   blocklabel=ReadByte(file)

   If blocklabel=$01 ;Plain Text Extension block
    blocksize=ReadByte(file) ;Should be 12
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$F9 ;Graphic Control Extension block
    blocksize=ReadByte(file) ;Should be 4
    bPacked=ReadByte(file) ;Packed Fields
    cl\Disposal=(bPacked And (4+8+16)) Shr 2 ;Disposal Method, bits 2..4
    cl\isTransparent=bPacked And 1 ;Transparent Color Flag, bit 0
    SeekFile(file,FilePos(file)+2) ;Skip Delay Time
    cl\bTransIndex=ReadByte(file) ;Transparent Color Index
    blocksize=ReadByte(file) ;Block Terminator, always 0

   ElseIf blocklabel=$FE ;Comment Extension block
    blocksize=ReadByte(file) ;Should be 1..255
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   ElseIf blocklabel=$FF ;Application Extension block
    blocksize=ReadByte(file) ;Should be 11
    While blocksize>0 ;Skip sub-blocks
     SeekFile(file,FilePos(file)+blocksize)
     blocksize=ReadByte(file)
    Wend

   EndIf
  EndIf

  If byte=$2C ;Image Descriptor block (87a)
   blocklabel=byte
   cl\wLeft=ReadShort(file)
   cl\wTop=ReadShort(file)
   wWidth=ReadShort(file)
   wHeight=ReadShort(file)
   bPacked=ReadByte(file)
   cl\isLocalTable=(bPacked And 128) Shr 7 ;Local Color Table Flag, bit 7
   cl\isInterlaced=(bPacked And 64) Shr 6 ;Interlace Flag, bit 6
   blocksize=3*(1 Shl ((bPacked And 7)+1)) ;Local Table size, bits 0..2
   LocalColors=blocksize/3 ;Number of local colors
   If LocalColors<=2 ;Use number of colors to set bit depth
    cl\LocalBpp=1
   ElseIf LocalColors<=16
    cl\LocalBpp=4
   Else
    cl\LocalBpp=8
   EndIf
   If cl\isLocalTable ;Local Color Table block
    For count=0 To LocalColors-1 ;Store local colors
     red=ReadByte(file)
     green=ReadByte(file)
     blue=ReadByte(file)
     LocalCols[count]=(red Shl 16) Or (green Shl 8) Or blue
    Next
   EndIf
   frames=frames+1 ;Increment frames
  EndIf

  If byte>1 And byte<13 ;Image Data block (87a)
   LZWCodeSize=byte ;LZW bit range is 2..12

   count=FilePos(file) ;Store this block
   blocksize=ReadByte(file) ;1..255
   While blocksize>0 ;Skip sub-blocks
    SeekFile(file,FilePos(file)+blocksize)
    blocksize=ReadByte(file)
   Wend
   cl\NextFilePos=FilePos(file) ;Store the next block
   SeekFile(file,count) ;Return to this block

   ;Set the bit depth and number of colors
   If cl\isLocalTable
    BitCount=cl\LocalBpp
    ncolors=LocalColors
   Else
    BitCount=cl\GlobalBpp
    ncolors=GlobalColors
   EndIf
   If ncolors=0 Then ncolors=1 Shl BitCount ;If no palette

   ;Allocate memory for DIB
   cl\Pitch=(((BitCount*wWidth)+31) Shr 5) Shl 2 ;Bytes per line
   size=40+(ncolors*4)+(cl\Pitch*wHeight) ;Size of DIB
   dib=CreateBank(size)
   cl\DIB=dib ;cl\DIB used in GIFOutLine

   ;Fill in the DIB info header
   PokeInt dib,0,40 ;biSize, 40
   PokeInt dib,4,wWidth ;biWidth
   PokeInt dib,8,wHeight ;biHeight
   PokeShort dib,12,1 ;biPlanes, 1
   PokeShort dib,14,BitCount ;biBitCount, 1/4/8
   PokeInt dib,16,0 ;biCompression, #BI_RGB=0
   PokeInt dib,20,cl\Pitch*wHeight ;biSizeImage
   PokeInt dib,24,0 ;biXPelsPerMeter
   PokeInt dib,28,0 ;biYPelsPerMeter
   PokeInt dib,32,ncolors ;biClrUsed
   PokeInt dib,36,0 ;biClrImportant

   pal=40 ;Fill in the DIB palette
   If cl\isLocalTable
    For count=0 To ncolors-1
     PokeByte dib,pal,LocalCols[count] And $0000FF ;Blue
     PokeByte dib,pal+1,(LocalCols[count] And $00FF00) Shr 8 ;Green
     PokeByte dib,pal+2,(LocalCols[count] And $FF0000) Shr 16 ;Red
     pal=pal+4
    Next
   Else
    For count=0 To ncolors-1
     PokeByte dib,pal,GlobalCols[count] And $0000FF ;Blue
     PokeByte dib,pal+1,(GlobalCols[count] And $00FF00) Shr 8 ;Green
     PokeByte dib,pal+2,(GlobalCols[count] And $FF0000) Shr 16 ;Red
     pal=pal+4
    Next
   EndIf

   ;Init variables for the decoder for reading a new image
   cl\CurrCodeSize=LZWCodeSize+1
   TopSlot=1 Shl cl\CurrCodeSize ;Highest code for current size
   ClearCode=1 Shl LZWCodeSize ;Value for a clear code
   EndingCode=ClearCode+1 ;Value for an ending code
   NewCodes=ClearCode+2 ;First available code
   Slot=NewCodes ;Last read code
   cl\BitsLeft=0 ;Make sure these LZW variables are zero
   cl\BytesLeft=0
   pStack=0 : pBuffer=0 ;Init the stack and decode buffer pointers
   BufCount=wWidth ;Line counter (count for pixel line length)
   cl\iLine=0 : cl\iPass=0 ;Init line offset and interlace pass
   cl\pBits=40+(ncolors*4)+(cl\Pitch*(wHeight-1)) ;Pointer to bits

   ;Allocate space for the decode buffer
   Buffer=CreateBank(wWidth+16) ;+16 just in case

   ;This is the main loop. For each code we get we pass through the
   ;linked list of prefix codes, pushing the corresponding "character"
   ;for each code onto the stack. When the list reaches a single
   ;"character" we push that on the stack too, and then start
   ;unstacking each character for output in the correct order.
   ;Special handling is included for the clear code, and the whole
   ;thing ends when we get an ending code.
   While cc<>EndingCode

    cc=GIFNextCode(file,cl)
    If cc<0 Then Exit ;File error, exit without completing the decode

    ;If the code is a clear code, re-initialise all necessary items
    If cc=ClearCode

     cl\CurrCodeSize=LZWCodeSize+1
     Slot=NewCodes
     TopSlot=1 Shl cl\CurrCodeSize

     ;Continue reading codes until we get a non-clear code
     ;(another unlikely, but possible case...)
     While cc=ClearCode
      cc=GIFNextCode(file,cl)
     Wend

     ;If we get an ending code immediately after a clear code
     ;(yet another unlikely case), then break out of the loop
     If cc=EndingCode Then Exit ;end loop

     ;Finally, if the code is beyond the range of already set codes,
     ;(This one had better not happen, I have no idea what will
     ;result from this, but I doubt it will look good)
     ;then set it to color zero.
     If cc>=Slot Then cc=0
     OldCode=cc
     TempOldCode=OldCode

     ;And let us not forget to put the char into the buffer, and if,
     ;on the off chance, we were exactly one pixel from the end of
     ;the line, we have to send the buffer to the GIFOutLine routine.
     PokeByte Buffer,pBuffer,cc
     pBuffer=pBuffer+1
     BufCount=BufCount-1
     If BufCount=0
      GIFOutLine(Buffer,wWidth,wHeight,cl)
      pBuffer=0
      BufCount=wWidth
     EndIf

    Else

     ;In this case, it's not a clear code or an ending code, so it
     ;must be a code code. So we can now decode the code into a
     ;stack of character codes (Clear as mud, right?).
     Code=cc
     If Code=Slot
      Code=TempOldCode
      Stack[pStack]=OldCode
      pStack=pStack+1
     EndIf

     ;Here we scan back along the linked list of prefixes, pushing
     ;helpless characters (i.e. suffixes) onto the stack as we do so.
     While Code>=NewCodes
      Stack[pStack]=Suffix[Code]
      pStack=pStack+1
      Code=Prefix[Code]
     Wend

     ;Push the last character on the stack, and set up the new
     ;prefix and suffix, and if the required slot number is greater
     ;than that allowed by the current bit size, increase the bit
     ;size. (Note - if we are all full, we *don't* save the new
     ;suffix and prefix. I'm not certain if this is correct,
     ;it might be more proper to overwrite the last code.
     Stack[pStack]=Code
     pStack=pStack+1
     If Slot<TopSlot
      OldCode=Code
      Suffix[Slot]=OldCode
      Prefix[Slot]=TempOldCode
      Slot=Slot+1
      TempOldCode=cc
     EndIf
     If Slot>=TopSlot
      If cl\CurrCodeSize<12
       TopSlot=TopSlot Shl 1
       cl\CurrCodeSize=cl\CurrCodeSize+1
      EndIf
     EndIf

     ;Now that we've pushed the decoded string (in reverse order)
     ;onto the stack, lets pop it off and put it into our decode
     ;buffer, and when the decode buffer is full, write another line.
     While pStack>0
      pStack=pStack-1
      PokeByte Buffer,pBuffer,Stack[pStack]
      pBuffer=pBuffer+1
      BufCount=BufCount-1
      If BufCount=0
       GIFOutLine(Buffer,wWidth,wHeight,cl)
       pBuffer=0
       BufCount=wWidth
      EndIf
     Wend

    EndIf
   Wend

   ;If there are any left, output the bytes
   If BufCount<>wWidth
    GIFOutLine(Buffer,wWidth-BufCount-1,wHeight,cl)
   EndIf

   Exit ;End block parsing loop
  EndIf

 Wend

 CloseFile file ;Close the file
 FreeBank Buffer ;Free the buffer
 Return dib ;Return the DIB

End Function

Function GIFOutLine(Buffer,Width,Height,cl.GIFCLASS)
 ;Outputs the pixel color index data to the DIB
 ;Buffer -> Memory block that holds the color index value
 ;Width -> Length of the line of pixels, Height -> wHeight
 ;Gif images are 2, 16 or 256 colors, poking the values into memory
 ;requires a different method for each case. If gif is interlaced,
 ;that is dealt with here.
 ;Used by GIFLoad

 Local bits,bpp,count,pixel,byte,bitcount

 bits=cl\pBits-(cl\iLine*cl\Pitch) ;Pointer to bits

 If cl\iLine>=Height Then Return False ;Avoid poking out of range

 If cl\isLocalTable
  bpp=cl\LocalBpp
 Else
  bpp=cl\GlobalBpp
 EndIf

 Select bpp
  Case 1 ;1-bit DIB
   count=0
   For pixel=0 To Width-1 Step 8
    byte=0
    For bitcount=0 To 7
     If PeekByte(Buffer,bitcount+pixel)
      byte=byte Or (1 Shl (7-bitcount))
     EndIf
    Next
    PokeByte cl\DIB,bits+count,byte
    count=count+1
   Next
  Case 4 ;4-bit DIB
   count=0
   For pixel=0 To Width-1 Step 2
    byte=PeekByte(Buffer,pixel) Shl 4
    byte=byte Or PeekByte(Buffer,pixel+1)
    PokeByte cl\DIB,bits+count,byte
    count=count+1
   Next
  Case 8 ;8-bit DIB
   For pixel=0 To Width-1
    byte=PeekByte(Buffer,pixel)
    PokeByte cl\DIB,bits+pixel,byte
   Next
 End Select

 If cl\isInterlaced ;Set iLine for different passes when interlaced
  Select cl\iPass
   Case 0 ;Pass 1
    If cl\iLine<Height-8
     cl\iLine=cl\iLine+8
    Else
     cl\iLine=4 : cl\iPass=cl\iPass+1 ;For 2nd pass
    EndIf
   Case 1 ;Pass 2
    If cl\iLine<Height-8
     cl\iLine=cl\iLine+8
    Else
     cl\iLine=2 : cl\iPass=cl\iPass+1 ;For 3rd pass
    EndIf
   Case 2 ;Pass 3
    If cl\iLine<Height-4
     cl\iLine=cl\iLine+4
    Else
     cl\iLine=1 : cl\iPass=cl\iPass+1 ;For 4th pass
    EndIf
   Case 3 ;Pass 4
    If cl\iLine<Height-2
     cl\iLine=cl\iLine+2
    EndIf
  End Select
 Else ;When not interlaced increment iLine
  cl\iLine=cl\iLine+1
 EndIf

End Function

Function GIFNextCode(file,cl.GIFCLASS)
 ;Reads the next code from the data stream
 ;Returns the LZW code or error
 ;Used by GIFLoad

 Local count,char,ret

 If cl\BitsLeft=0 ;Any bits left in byte?

  If cl\BytesLeft<=0 ;If not get another block
   cl\pCharBuff=0 ;Reset byte pointer
   cl\BytesLeft=ReadByte(file) ;Block size
   If cl\BytesLeft=0 Then Return -2 ;Found block terminator
   If Eof(file)<>0 Then Return -1 ;Stream error or end of file
   For count=0 To cl\BytesLeft-1
    char=ReadByte(file)
    cl\CharBuff[count]=char ;Fill CharBuff with the new block
   Next
  EndIf

  cl\CurrByte=cl\CharBuff[cl\pCharBuff] ;Get a byte
  cl\pCharBuff=cl\pCharBuff+1 ;Increment byte pointer
  cl\BitsLeft=8 ;Set bits left in the byte
  cl\BytesLeft=cl\BytesLeft-1 ;Decrement BytesLeft counter

 EndIf

 ;Shift off any previously used bits
 ret=cl\CurrByte Shr (8-cl\BitsLeft)

 While cl\CurrCodeSize>cl\BitsLeft

  If cl\BytesLeft<=0 ;Out of bytes in current block
   cl\pCharBuff=0 ;Set byte pointer
   cl\BytesLeft=ReadByte(file) ;Block size
   If cl\BytesLeft=0 Then Return -2 ;Found block terminator
   If Eof(file)<>0 Then Return -1 ;Stream error or end of file
   For count=0 To cl\BytesLeft-1
    char=ReadByte(file)
    cl\CharBuff[count]=char ;Fill CharBuff with current block
   Next
  EndIf

  cl\CurrByte=cl\CharBuff[cl\pCharBuff] ;Get a byte
  cl\pCharBuff=cl\pCharBuff+1 ;Increment byte pointer
  ret=ret Or (cl\CurrByte Shl cl\BitsLeft) ;Add remaining bits to ret
  cl\BitsLeft=cl\BitsLeft+8 ;Set bit counter
  cl\BytesLeft=cl\BytesLeft-1 ;Decrement BytesLeft counter

 Wend

 ;Subtract the code size from BitsLeft
 cl\BitsLeft=cl\BitsLeft-cl\CurrCodeSize
 ;Mask off the right number of bits
 ret=ret And cl\CodeMask[cl\CurrCodeSize] 
 Return ret

End Function