mirror of
https://github.com/QB64Official/qb64.git
synced 2024-08-22 11:25:08 +00:00
421 lines
14 KiB
Markdown
421 lines
14 KiB
Markdown
**Animated GIF File Decoder**
|
|
|
|
GIF files can be one frame or animated images made up of many frames that are displayed at a set frame rate. The following program allows you to view either kind of image or use them in a program. [_LOADIMAGE](_LOADIMAGE) can only return one frame of an animated image.
|
|
|
|
```vb
|
|
|
|
'#######################################################################################
|
|
'# Animated GIF decoder v1.0 #
|
|
'# By Zom-B #
|
|
'#######################################################################################
|
|
|
|
DEFINT A-Z
|
|
'$DYNAMIC
|
|
|
|
DIM SHARED Dbg: Dbg = 0
|
|
DIM SHARED powerOf2&(11)
|
|
FOR a = 0 TO 11: powerOf2&(a) = 2 ^ a: NEXT a
|
|
|
|
TYPE GIFDATA
|
|
file AS INTEGER
|
|
sigver AS STRING * 6
|
|
width AS _UNSIGNED INTEGER
|
|
height AS _UNSIGNED INTEGER
|
|
bpp AS _UNSIGNED _BYTE
|
|
sortFlag AS _BYTE ' Unused
|
|
colorRes AS _UNSIGNED _BYTE
|
|
colorTableFlag AS _BYTE
|
|
bgColor AS _UNSIGNED _BYTE
|
|
aspect AS SINGLE ' Unused
|
|
numColors AS _UNSIGNED INTEGER
|
|
palette AS STRING * 768
|
|
END TYPE
|
|
|
|
TYPE FRAMEDATA
|
|
addr AS LONG
|
|
left AS _UNSIGNED INTEGER
|
|
top AS _UNSIGNED INTEGER
|
|
width AS _UNSIGNED INTEGER
|
|
height AS _UNSIGNED INTEGER
|
|
localColorTableFlag AS _BYTE
|
|
interlacedFlag AS _BYTE
|
|
sortFlag AS _BYTE ' Unused
|
|
palBPP AS _UNSIGNED _BYTE
|
|
minimumCodeSize AS _UNSIGNED _BYTE
|
|
transparentFlag AS _BYTE 'GIF89a-specific (animation) values
|
|
userInput AS _BYTE ' Unused
|
|
disposalMethod AS _UNSIGNED _BYTE
|
|
delay AS SINGLE
|
|
transColor AS _UNSIGNED _BYTE
|
|
END TYPE
|
|
|
|
SCREEN _NEWIMAGE(640, 480, 32)
|
|
|
|
' Open gif file. This reads the headers and palette but not the image data.
|
|
' The array will be redimentioned to fit the exact number of frames in the file.
|
|
|
|
DIM gifData AS GIFDATA, frameData(0 TO 0) AS FRAMEDATA
|
|
|
|
filename$ = "mygif.gif" '<<<<<<<<<<<< Enter a file name here!!!
|
|
|
|
IF LEN(filename$) = 0 THEN END
|
|
openGif filename$, gifData, frameData()
|
|
|
|
' Loop away.
|
|
frame = 0
|
|
DO
|
|
' Request a frame. If it has been requested before, it is re-used,
|
|
' otherwise it is read and decoded from the file.
|
|
_PUTIMAGE (0, 0), getGifFrame&(gifData, frameData(), frame)
|
|
_DELAY frameData(frame).delay
|
|
frame = (frame + 1) MOD (UBOUND(framedata) + 1)
|
|
LOOP UNTIL LEN(INKEY$)
|
|
|
|
'Close the file and free the allocated frames.
|
|
codeGif gifData, frameData()
|
|
END
|
|
|
|
'########################################################################################
|
|
|
|
SUB openGif (filename$, gifData AS GIFDATA, frameData() AS FRAMEDATA) STATIC
|
|
file = FREEFILE: gifData.file = file
|
|
OPEN "B", gifData.file, filename$
|
|
|
|
GET file, , gifData.sigver
|
|
GET file, , gifData.width
|
|
GET file, , gifData.height
|
|
GET file, , byte~%%
|
|
gifData.bpp = (byte~%% AND 7) + 1
|
|
gifData.sortFlag = (byte~%% AND 8) > 0
|
|
gifData.colorRes = (byte~%% \ 16 AND 7) + 1
|
|
gifData.colorTableFlag = (byte~%% AND 128) > 0
|
|
gifData.numColors = 2 ^ gifData.bpp
|
|
GET file, , gifData.bgColor
|
|
GET file, , byte~%%
|
|
IF byte~%% = 0 THEN gifData.aspect = 0 ELSE gifData.aspect = (byte~%% + 15) / 64
|
|
|
|
IF gifData.sigver <> "GIF87a" AND gifData.sigver <> "GIF89a" THEN _DEST 0: PRINT "Invalid version": END
|
|
IF NOT gifData.colorTableFlag THEN _DEST 0: PRINT "No Color Table": END
|
|
|
|
palette$ = SPACE$(3 * gifData.numColors)
|
|
GET file, , palette$
|
|
gifData.palette = palette$
|
|
IF Dbg AND 1 THEN
|
|
PRINT "sigver ="; gifData.sigver
|
|
PRINT "width ="; gifData.width
|
|
PRINT "height ="; gifData.height
|
|
PRINT "bpp ="; gifData.bpp
|
|
PRINT "sortFlag ="; gifData.sortFlag
|
|
PRINT "colorRes ="; gifData.colorRes
|
|
PRINT "colorTableFlag ="; gifData.colorTableFlag
|
|
PRINT "bgColor ="; gifData.bgColor
|
|
PRINT "aspect ="; gifData.aspect
|
|
PRINT "numColors ="; gifData.numColors
|
|
FOR i = 0 TO gifData.numColors - 1
|
|
PRINT USING "pal(###) = "; i;
|
|
PRINT HEX$(_RGB32(ASC(gifData.palette, i * 3 + 1), ASC(gifData.palette, i * 3 + 2), ASC(gifData.palette, i * 3 + 3)))
|
|
NEXT
|
|
END IF
|
|
DO
|
|
GET file, , byte~%%
|
|
IF Dbg AND 2 THEN PRINT "Chunk: "; HEX$(byte~%%)
|
|
SELECT CASE byte~%%
|
|
CASE &H2C ' Image Descriptor
|
|
IF frame > UBOUND(frameData) THEN
|
|
REDIM _PRESERVE frameData(0 TO frame * 2 - 1) AS FRAMEDATA
|
|
END IF
|
|
|
|
GET file, , frameData(frame).left
|
|
GET file, , frameData(frame).top
|
|
GET file, , frameData(frame).width
|
|
GET file, , frameData(frame).height
|
|
GET file, , byte~%%
|
|
frameData(frame).localColorTableFlag = (byte~%% AND 128) > 0
|
|
frameData(frame).interlacedFlag = (byte~%% AND 64) > 0
|
|
frameData(frame).sortFlag = (byte~%% AND 32) > 0
|
|
frameData(frame).palBPP = (byte~%% AND 7) + 1
|
|
frameData(frame).addr = LOC(file) + 1
|
|
|
|
IF frameData(frame).localColorTableFlag THEN
|
|
SEEK file, LOC(file) + 3 * 2 ^ frameData(frame).palBPP + 1
|
|
END IF
|
|
GET file, , frameData(frame).minimumCodeSize
|
|
IF Dbg AND 2 THEN
|
|
PRINT "addr ="; HEX$(frameData(frame).addr - 1)
|
|
PRINT "left ="; frameData(frame).left
|
|
PRINT "top ="; frameData(frame).top
|
|
PRINT "width ="; frameData(frame).width
|
|
PRINT "height ="; frameData(frame).height
|
|
PRINT "localColorTableFlag ="; frameData(frame).localColorTableFlag
|
|
PRINT "interlacedFlag ="; frameData(frame).interlacedFlag
|
|
PRINT "sortFlag ="; frameData(frame).sortFlag
|
|
PRINT "palBPP ="; frameData(frame).palBPP
|
|
PRINT "minimumCodeSize ="; frameData(frame).minimumCodeSize
|
|
END IF
|
|
IF localColors THEN _DEST 0: PRINT "Local color table": END
|
|
IF frameData(frame).disposalMethod > 2 THEN PRINT "Unsupported disposalMethod: "; frameData(frame).disposalMethod: END
|
|
skipBlocks file
|
|
|
|
frame = frame + 1
|
|
CASE &H3B ' Trailer
|
|
EXIT DO
|
|
CASE &H21 ' Extension Introducer
|
|
GET file, , byte~%% ' Extension Label
|
|
IF Dbg AND 2 THEN PRINT "Extension Introducer: "; HEX$(byte~%%)
|
|
SELECT CASE byte~%%
|
|
CASE &HFF, &HFE ' Application Extension, Comment Extension
|
|
skipBlocks file
|
|
CASE &HF9
|
|
IF frame > UBOUND(frameData) THEN
|
|
REDIM _PRESERVE frameData(0 TO frame * 2 - 1) AS FRAMEDATA
|
|
END IF
|
|
|
|
GET 1, , byte~%% ' Block Size (always 4)
|
|
GET 1, , byte~%%
|
|
frameData(frame).transparentFlag = (byte~%% AND 1) > 0
|
|
frameData(frame).userInput = (byte~%% AND 2) > 0
|
|
frameData(frame).disposalMethod = byte~%% \ 4 AND 7
|
|
GET 1, , delay~%
|
|
IF delay~% = 0 THEN frameData(frame).delay = 0.1 ELSE frameData(frame).delay = delay~% / 100
|
|
GET 1, , frameData(frame).transColor
|
|
IF Dbg AND 2 THEN
|
|
PRINT "frame ="; frame
|
|
PRINT "transparentFlag ="; frameData(frame).transparentFlag
|
|
PRINT "userInput ="; frameData(frame).userInput
|
|
PRINT "disposalMethod ="; frameData(frame).disposalMethod
|
|
PRINT "delay ="; frameData(frame).delay
|
|
PRINT "transColor ="; frameData(frame).transColor
|
|
END IF
|
|
skipBlocks file
|
|
CASE ELSE
|
|
PRINT "Unsupported extension Label: "; HEX$(byte~%%): END
|
|
END SELECT
|
|
CASE ELSE
|
|
PRINT "Unsupported chunk: "; HEX$(byte~%%): END
|
|
END SELECT
|
|
LOOP
|
|
|
|
REDIM _PRESERVE frameData(0 TO frame - 1) AS FRAMEDATA
|
|
END FUNCTION
|
|
|
|
SUB skipBlocks (file)
|
|
DO
|
|
GET file, , byte~%% ' Block Size
|
|
IF Dbg AND 2 THEN PRINT "block size ="; byte~%%
|
|
SEEK file, LOC(file) + byte~%% + 1
|
|
LOOP WHILE byte~%%
|
|
END SUB
|
|
|
|
FUNCTION getGifFrame& (gifData AS GIFDATA, frameData() AS FRAMEDATA, frame)
|
|
IF frameData(frame).addr > 0 THEN
|
|
IF Dbg AND 4 THEN
|
|
PRINT "addr ="; HEX$(frameData(frame).addr - 1)
|
|
PRINT "left ="; frameData(frame).left
|
|
PRINT "top ="; frameData(frame).top
|
|
PRINT "width ="; frameData(frame).width
|
|
PRINT "height ="; frameData(frame).height
|
|
PRINT "localColorTableFlag ="; frameData(frame).localColorTableFlag
|
|
PRINT "interlacedFlag ="; frameData(frame).interlacedFlag
|
|
PRINT "sortFlag ="; frameData(frame).sortFlag
|
|
PRINT "palBPP ="; frameData(frame).palBPP
|
|
PRINT "minimumCodeSize ="; frameData(frame).minimumCodeSize
|
|
PRINT "transparentFlag ="; frameData(frame).transparentFlag
|
|
PRINT "userInput ="; frameData(frame).userInput
|
|
PRINT "disposalMethod ="; frameData(frame).disposalMethod
|
|
PRINT "delay ="; frameData(frame).delay
|
|
PRINT "transColor ="; frameData(frame).transColor
|
|
END IF
|
|
w = frameData(frame).width
|
|
h = frameData(frame).height
|
|
img& = _NEWIMAGE(w, h, 256)
|
|
frame& = _NEWIMAGE(gifData.width, gifData.height, 256)
|
|
|
|
_DEST img&
|
|
decodeFrame gifData, frameData(frame)
|
|
|
|
_DEST frame&
|
|
IF frameData(frame).localColorTableFlag THEN
|
|
_COPYPALETTE img&
|
|
ELSE
|
|
FOR i = 0 TO gifData.numColors - 1
|
|
_PALETTECOLOR i, _RGB32(ASC(gifData.palette, i * 3 + 1), ASC(gifData.palette, i * 3 + 2), ASC(gifData.palette, i * 3 + 3))
|
|
NEXT
|
|
END IF
|
|
|
|
IF frame THEN
|
|
SELECT CASE frameData(frame - 1).disposalMethod
|
|
CASE 0, 1
|
|
_PUTIMAGE , frameData(frame - 1).addr
|
|
CASE 2
|
|
CLS , gifData.bgColor
|
|
_CLEARCOLOR gifData.bgColor
|
|
END SELECT
|
|
ELSE
|
|
CLS , gifData.bgColor
|
|
END IF
|
|
|
|
IF frameData(frame).transparentFlag THEN
|
|
_CLEARCOLOR frameData(frame).transColor, img&
|
|
END IF
|
|
_PUTIMAGE (frameData(frame).left, frameData(frame).top), img&
|
|
_FREEIMAGE img&
|
|
|
|
frameData(frame).addr = frame&
|
|
_DEST 0
|
|
END IF
|
|
|
|
getGifFrame& = frameData(frame).addr
|
|
END FUNCTION
|
|
|
|
|
|
'############################################################################################
|
|
|
|
SUB decodeFrame (gifdata AS GIFDATA, framedata AS FRAMEDATA)
|
|
DIM byte AS _UNSIGNED _BYTE
|
|
DIM prefix(4095), suffix(4095), colorStack(4095)
|
|
|
|
startCodeSize = gifdata.bpp + 1
|
|
clearCode = 2 ^ gifdata.bpp
|
|
endCode = clearCode + 1
|
|
minCode = endCode + 1
|
|
startMaxCode = clearCode * 2 - 1
|
|
nvc = minCode
|
|
codeSize = startCodeSize
|
|
maxCode = startMaxCode
|
|
|
|
IF framedata.interlacedFlag THEN interlacedPass = 0: interlacedStep = 8
|
|
bitPointer = 0
|
|
blockSize = 0
|
|
blockPointer = 0
|
|
x = 0
|
|
y = 0
|
|
|
|
file = gifdata.file
|
|
SEEK file, framedata.addr
|
|
|
|
IF framedata.localColorTableFlag THEN
|
|
palette$ = SPACE$(3 * 2 ^ framedata.palBPP)
|
|
GET 1, , palette$
|
|
|
|
FOR i = 0 TO gifdata.numColors - 1
|
|
c& = _RGB32(ASC(palette$, i * 3 + 1), ASC(palette$, i * 3 + 2), ASC(palette$, i * 3 + 3))
|
|
_PALETTECOLOR i, c&
|
|
NEXT
|
|
END IF
|
|
|
|
GET file, , byte ' minimumCodeSize
|
|
|
|
DO
|
|
GOSUB GetCode
|
|
stackPointer = 0
|
|
IF code = clearCode THEN 'Reset & Draw next color direct
|
|
nvc = minCode ' \
|
|
codeSize = startCodeSize ' Preset default codes
|
|
maxCode = startMaxCode ' /
|
|
|
|
GOSUB GetCode
|
|
currentCode = code
|
|
|
|
lastColor = code
|
|
colorStack(stackPointer) = lastColor
|
|
stackPointer = 1
|
|
ELSEIF code <> endCode THEN 'Draw direct color or colors from suffix
|
|
currentCode = code
|
|
IF currentCode = nvc THEN 'Take last color too
|
|
currentCode = oldCode
|
|
colorStack(stackPointer) = lastColor
|
|
stackPointer = stackPointer + 1
|
|
END IF
|
|
|
|
WHILE currentCode >= minCode 'Extract colors from suffix
|
|
colorStack(stackPointer) = suffix(currentCode)
|
|
stackPointer = stackPointer + 1
|
|
currentCode = prefix(currentCode) 'Next color from suffix is described in
|
|
WEND ' the prefix, else prefix is the last col.
|
|
|
|
lastColor = currentCode ' Last color is equal to the
|
|
colorStack(stackPointer) = lastColor ' last known code (direct, or from
|
|
stackPointer = stackPointer + 1 ' Prefix)
|
|
suffix(nvc) = lastColor 'Automatically, update suffix
|
|
prefix(nvc) = oldCode 'Code from the session before (for extracting from suffix)
|
|
nvc = nvc + 1
|
|
|
|
IF nvc > maxCode AND codeSize < 12 THEN
|
|
codeSize = codeSize + 1
|
|
maxCode = maxCode * 2 + 1
|
|
END IF
|
|
END IF
|
|
|
|
FOR i = stackPointer - 1 TO 0 STEP -1
|
|
PSET (x, y), colorStack(i)
|
|
x = x + 1
|
|
IF x = framedata.width THEN
|
|
x = 0
|
|
IF framedata.interlacedFlag THEN
|
|
y = y + interlacedStep
|
|
IF y >= framedata.height THEN
|
|
SELECT CASE interlacedPass
|
|
CASE 0: interlacedPass = 1: y = 4
|
|
CASE 1: interlacedPass = 2: y = 2
|
|
CASE 2: interlacedPass = 3: y = 1
|
|
END SELECT
|
|
interlacedStep = 2 * y
|
|
END IF
|
|
ELSE
|
|
y = y + 1
|
|
END IF
|
|
END IF
|
|
NEXT
|
|
|
|
oldCode = code
|
|
LOOP UNTIL code = endCode
|
|
|
|
GET file, , byte
|
|
EXIT SUB
|
|
|
|
GetCode:
|
|
IF bitPointer = 0 THEN GOSUB ReadByteFromBlock: bitPointer = 8
|
|
WorkCode& = LastChar \ powerOf2&(8 - bitPointer)
|
|
WHILE codeSize > bitPointer
|
|
GOSUB ReadByteFromBlock
|
|
|
|
WorkCode& = WorkCode& OR LastChar * powerOf2&(bitPointer)
|
|
bitPointer = bitPointer + 8
|
|
WEND
|
|
bitPointer = bitPointer - codeSize
|
|
code = WorkCode& AND maxCode
|
|
RETURN
|
|
|
|
ReadByteFromBlock:
|
|
IF blockPointer = blockSize THEN
|
|
GET file, , byte: blockSize = byte
|
|
a$ = SPACE$(blockSize): GET file, , a$
|
|
blockPointer = 0
|
|
END IF
|
|
blockPointer = blockPointer + 1
|
|
LastChar = ASC(MID$(a$, blockPointer, 1))
|
|
RETURN
|
|
END SUB
|
|
|
|
|
|
SUB codeGif (gifData AS GIFDATA, frameData() AS FRAMEDATA)
|
|
FOR i = 0 TO UBOUND(FRAMEDATA)
|
|
IF frameData(i).addr < 0 THEN _FREEIMAGE frameData(i).addr
|
|
NEXT
|
|
|
|
CLOSE gifData.file
|
|
END SUB
|
|
|
|
|
|
```
|
|
|
|
NOTE: This has been reported to only work using 256-color images, and you need to keep the code loading into a 32-bit image destination as the source?
|
|
|
|
## See Also
|
|
|
|
* [GIF Creation](GIF-Creation), [Bitmaps](Bitmaps)
|
|
* [Icons and Cursors](Icons-and-Cursors)
|
|
* [_LOADIMAGE](_LOADIMAGE), [_PUTIMAGE](_PUTIMAGE)
|
|
* [FILELIST$ (function)](FILELIST$-(function)) (member file search routine)
|
|
* [SaveIcon32](SaveIcon32) (create icons from any image)
|
|
* [$EXEICON]($EXEICON) (Icon visible in Windows Explorer)
|