11-30-2023, 10:48 PM (This post was last modified: 09-20-2024, 08:00 PM by a740g.
Edit Reason: Update to latest version
)
This is an animated GIF player library written purely in QB64-PE.
I was kind of annoyed and frustrated with the issues in the animated GIF library implementation we have listed in the wiki. Unfortunately, InForm's GIF support is derived from the same code, and it suffers from the same limitations and issues. Basically, it does not support frame local color tables and does not correctly support all of GIF's frame disposal methods. So, I set out to write an animated GIF library that can work standalone and also with InForm-PE.
12-02-2023, 12:33 AM (This post was last modified: 12-03-2023, 08:38 AM by a740g.
Edit Reason: Fixed some typo
)
(12-01-2023, 10:39 PM)grymmjack Wrote: @a740g This is great work! Would it be possible to use only the GIFPlay components outside of inform? Or are there requirements for inform too?
I guess what I'm asking is, as I've not used InForm yet myself, how would I integrate something like this in for example a formless thing like a game?
Thanks as usual
Certainly we can. That's how I designed it.
Example below. I've also include this example in the zip file and updated the first post.
' This is a unique number that can identify the GIF (think of this like a QB file number) ' We can load multiple GIFs using different GIF Ids CONST MY_GIF_ID = 101
' GIF_LoadFromMemory can load a GIF from a STRING buffer IFGIF_LoadFromFile(MY_GIF_ID, gifFileName) THEN ' GIF_Draw can only render to 32bpp surfaces. Hence our destination surface must be 32bpp ' Why 32bpp? That's because GIF animations *can* display more than 256 colors using frame local palettes ' See SmallFullColourGIF.gif DIM surface ASLONG: surface = _NEWIMAGE(GIF_GetWidth(MY_GIF_ID), GIF_GetHeight(MY_GIF_ID), 32)
IF k = 32THEN IFGIF_IsPlaying(MY_GIF_ID) THENGIF_Pause (MY_GIF_ID) ELSEGIF_Play (MY_GIF_ID) END IF
' This renders a GIF frame to the current _DEST. The _DEST must be 32bpp as noted above ' The whole _DEST is used. Meaning the frame will be scaled and stretched to fit if _DEST does not match the frame size ' We need not worry about timing as it is handled internally by the library. Hence this loop can be run at any frequency (we are using 120 below) ' Note: Extremely low frequency will cause frame skips and sometime artifacts (due to intra-frame dependencies) GIF_Draw MY_GIF_ID
' This is technically not required as a call to GIF_LoadFromFile/Memory will simply free a previously loaded GIF and re-use the Id ' But we'll be good citizens and do it anyway GIF_Free MY_GIF_ID
12-05-2023, 05:24 AM (This post was last modified: 12-05-2023, 05:25 AM by grymmjack.
Edit Reason: Misspelled a740g stupid ipad
)
@a740g this is excellent! Could you possibly add a way to jump to a frame, then we could use this for a stack of animation for our sprites. E.g idle - frame 1-2, walk - frame 3-5, jump = frame 6-10 etc.
Then we could do in our code check and loop over frames, stop playing walk on first frame of idle, etc
(12-05-2023, 05:24 AM)grymmjack Wrote: @a740g this is excellent! Could you possibly add a way to jump to a frame, then we could use this for a stack of animation for our sprites. E.g idle - frame 1-2, walk - frame 3-5, jump = frame 6-10 etc.
Then we could do in our code check and loop over frames, stop playing walk on first frame of idle, etc
That's a good idea. I was going to add seek support. The only reason I did not do that is because of GIFs last-frame dependencies. It can cause weird artifacts with some GIFs if you seek to a random frame. The only way I can get past the frame dependency issue is if I completely pre-render every frame. That's the plan anyway. Once I have the base framework locked, I'll start adding FLI/FLC and aseprite support.
Maybe we can also do a PlaySegment(m, n) kind of routine, where it only loops from frame m to n. That way you'll not even have to check and manually loop frames. We can also do reverse loops.
(12-05-2023, 05:24 AM)grymmjack Wrote: @a740g this is excellent! Could you possibly add a way to jump to a frame, then we could use this for a stack of animation for our sprites. E.g idle - frame 1-2, walk - frame 3-5, jump = frame 6-10 etc.
Then we could do in our code check and loop over frames, stop playing walk on first frame of idle, etc
That's a good idea. I was going to add seek support. The only reason I did not do that is because of GIFs last-frame dependencies. It can cause weird artifacts with some GIFs if you seek to a random frame. The only way I can get past the frame dependency issue is if I completely pre-render every frame. That's the plan anyway. Once I have the base framework locked, I'll start adding FLI/FLC and aseprite support.
Maybe we can also do a PlaySegment(m, n) kind of routine, where it only loops from frame m to n. That way you'll not even have to check and manually loop frames. We can also do reverse loops.
That is awesome to hear! Yes ! I will help test! This is huge!
Opens up tons of possibilities for easy game making with just GIFs!
09-20-2024, 03:20 PM (This post was last modified: 09-20-2024, 03:24 PM by Dav.)
Nice work on GIFPlay, @a740g.
Was wondering, did you ever write that FLI/FLC player? I wrote a FLI player in Qbasic years ago (here) which I updated to QB64 code (below). It's very limited but does play FLI's, here it is if it will help at all.
- Dav
Code: (Select All)
PlayFlic "CLOWN.FLI"
SUB PlayFlic (file$)
'open fli/flc file
OPEN file$ FOR BINARY AS #1
IF LOF(1) = 0 THEN
CLOSE 1: KILL file$ 'if file empty, close and delete
EXIT SUB
END IF
'validate fli/flc id
IF (FlicId <> &HAF11 AND FlicId <> &HAF12 AND FlicId <> &HAF13) THEN
CLOSE 1: EXIT SUB 'if not, exit
END IF
'setup screen with width and height
SCREEN _NEWIMAGE(FlicWidth, FlicHeight, 32)
DO 'loop animation
SEEK 1, 129 'skip the fli/flc header
FOR f = 1 TO frames 'do all frames
framepos& = SEEK(1) 'save file pointer pos for current frame
FrameHeader$ = INPUT$(16, 1) 'frame header
FrameSize& = CVL(MID$(FrameHeader$, 1, 4)) 'size of the frame
FrameChunks = CVI(MID$(FrameHeader$, 7, 2)) 'number of chunks in frame
d$ = INKEY$: IF d$ <> "" THEN EXIT DO 'exit playback if key pressed
FOR c = 1 TO FrameChunks 'do chunks in frame
chunkpos& = SEEK(1) 'save file pointer pos for current chunk
chunkheader$ = INPUT$(6, 1) 'chunk header
ChunkSize& = CVL(MID$(chunkheader$, 1, 4)) 'size of the chunk
ChunkType = CVI(MID$(chunkheader$, 5, 2)) 'chunk type ID
chunkpos& = chunkpos& + ChunkSize& 'move chunk pos to next chunk
SELECT CASE ChunkType 'do chuck type ID
CASE 11 'set palette chunk
REDIM pal&(255) 'palette array
clr = 0
packs = ASC(INPUT$(2, 1))
FOR d = 1 TO packs
skip = ASC(INPUT$(1, 1))
change = ASC(INPUT$(1, 1))
IF change = 0 THEN change = 256
clr = clr + skip
FOR s = 1 TO change
r = ASC(INPUT$(1, 1)) * 4 'red
g = ASC(INPUT$(1, 1)) * 4 'green
b = ASC(INPUT$(1, 1)) * 4 'blue
pal&(clr) = _RGB(r, g, b)
clr = clr + 1
NEXT
NEXT
CASE 12 'line compressed chunk (LC)
skip = CVI(INPUT$(2, 1)) 'unused lines to skip
change = CVI(INPUT$(2, 1)) 'lines to change
FOR y = skip TO change + (skip - 1)
packpos& = SEEK(1)
PackData$ = INPUT$(500, 1): midpos = 1 'grab all pack data
packs = ASC(MID$(PackData$, midpos, 1)) 'number of packs
midpos = midpos + 1: x = 0 'update midpos
FOR d = 1 TO packs
s% = ASC(MID$(PackData$, midpos, 1)) 'skip value
p = ASC(MID$(PackData$, midpos + 1, 1)) 'pixel value
midpos = midpos + 2: x = x + s% 'update midpos
IF p > 127 THEN 'if packed data
p = (256 - p) 'decode pixel data
LINE (x, y)-STEP(p - 1, 0), pal&(ASC(MID$(PackData$, midpos, 1)))
x = x + p: midpos = midpos + 1 'update position
ELSE 'raw pixel data
Row$ = MID$(PackData$, midpos, p) 'read pixels
midpos = midpos + p
FOR k = 1 TO LEN(Row$)
PSET (x + k - 1, y), pal&(ASC(MID$(Row$, k, 1)))
NEXT
x = x + p 'update position ahead
END IF
NEXT
SEEK #1, packpos& + midpos - 1: 'PackData$ = ""
NEXT
CASE 13 'clear screen chunk
CLS 'clear screen to black
CASE 15 'brun compressed chunk
x = 0
FOR y = 0 TO FlicHeight - 1 'do row
packpos& = SEEK(1) 'save current pack pos
PackData$ = INPUT$(500, 1): midpos = 1 'load pack data
packs = ASC(MID$(PackData$, midpos, 1)) 'number of packs
midpos = midpos + 1 'update for mid$ usage
FOR d = 1 TO packs 'do packs
p = ASC(MID$(PackData$, midpos, 1)): midpos = midpos + 1
IF p < 128 THEN
LINE (x, y)-STEP(p - 1, 0), pal&(ASC(MID$(PackData$, midpos, 1)))
midpos = midpos + 1
ELSE 'if packed data
p = (256 - p)
Row$ = MID$(PackData$, midpos, p) 'raw pixels
midpos = midpos + p
FOR k = 1 TO LEN(Row$)
PSET (x + k - 1, y), pal&(ASC(MID$(Row$, k, 1))) 'draw pixels
NEXT
END IF
x = x + p 'update x position
NEXT
x = 0: SEEK #1, packpos& + midpos - 1 ': PackData$ = ""
NEXT
CASE 16 'raw image data chunk
x = 0
FOR y = 0 TO FlicHeight - 1
Row$ = INPUT$(FlicWidth, 1)
FOR k = 1 TO LEN(Row$)
PSET (x + k - 1, y), pal&(ASC(MID$(Row$, k, 1)))
NEXT
NEXT
CASE ELSE: CLOSE 1: EXIT SUB 'invalid chunk type, exit
END SELECT
SEEK #1, chunkpos& 'move file pos to next chunk
NEXT
SEEK 1, framepos& + FrameSize& 'move to the next frame
_LIMIT Ticks * 8 'playback speed
_DISPLAY
NEXT
LOOP
CLOSE 1
END SUB
09-20-2024, 07:09 PM (This post was last modified: 09-20-2024, 07:10 PM by Pete.)
Sam, I need you to add slow motion, for my golf swing...
Oh wait, I'm old, better make that 3x speed. That will make me feel much better.
I have some image flipping stuff I played around with a decade or so ago, but you had to keep a database of images to call. Very cool if this gets those images straight from a single gif file.
Pete
+1
Shoot first and shoot people who ask questions, later.