Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
GIFPlay
#1
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.

The latest version of the library will always be a part of InForm-PE. You can find the code inside InForm-PE/InForm/extensions.

I am also attaching a standalone zip file here. This zip file contains just the GIFPlay library, demo and its dependencies.

Library documentation is here: InForm-PE/InForm/docs/GIFPlay.md

You'll find some other useful (?) stuff in the InForm/extensions directory.  Smile

Cheers!

[Image: Screenshot-2023-12-01-040855.png]
[Image: Screenshot-2023-12-01-040927.png]


Attached Files
.zip   GIFPlay.zip (Size: 1.81 MB / Downloads: 62)
Reply
#2
Great improvement! Thanks for that!
Reply
#3
@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 Smile
grymmjack (gj!)
GitHubYouTube | Soundcloud | 16colo.rs
Reply
#4
(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 Smile
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.

Also see https://github.com/a740g/InForm-PE/blob/...GIFPlay.md for complete documentation.

I might just add FLI / FLC and aseprite support in the future. Big Grin 

Code: (Select All)
' GIFPlay Demo - Standalone
' a740g, 2023

$RESIZE:SMOOTH
DEFLNG A-Z
OPTION _EXPLICIT

'$INCLUDE:'InForm/extensions/GIFPlay.bi'

' 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

DO
    DIM gifFileName AS STRING: gifFileName = _OPENFILEDIALOG$("Open GIF", , "*.gif|*.GIF|*.Gif", "GIF Files")
    IF LEN(gifFileName) = 0 THEN EXIT DO

    _TITLE gifFileName

    ' GIF_LoadFromMemory can load a GIF from a STRING buffer
    IF GIF_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 AS LONG: surface = _NEWIMAGE(GIF_GetWidth(MY_GIF_ID), GIF_GetHeight(MY_GIF_ID), 32)

        SCREEN surface ' we'll directly assign this to the window
        _ALLOWFULLSCREEN _SQUAREPIXELS , _SMOOTH

        GIF_Play MY_GIF_ID ' kickstart playback

        DO
            DIM k AS LONG: k = _KEYHIT

            IF k = 32 THEN
                IF GIF_IsPlaying(MY_GIF_ID) THEN GIF_Pause (MY_GIF_ID) ELSE GIF_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

            _DISPLAY

            _LIMIT 120
        LOOP UNTIL k = 27

        ' 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 Smile
        GIF_Free MY_GIF_ID

        SCREEN 0
        _FREEIMAGE surface
    END IF
LOOP

SYSTEM

'$INCLUDE:'InForm/extensions/GIFPlay.bas'
Reply
#5
<3 Asesprite!

Ok I will check what you shared.
grymmjack (gj!)
GitHubYouTube | Soundcloud | 16colo.rs
Reply
#6
@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
grymmjack (gj!)
GitHubYouTube | Soundcloud | 16colo.rs
Reply
#7
(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. Big Grin
Reply
#8
(12-05-2023, 08:56 AM)a740g Wrote:
(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. Big Grin

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!
grymmjack (gj!)
GitHubYouTube | Soundcloud | 16colo.rs
Reply
#9
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

    'grab fli/flc header
    header$ = INPUT$(128, 1) 'read 128 bytes for header info
    FlicId = CVI(MID$(header$, 5, 2)) 'flic id
    frames = CVI(MID$(header$, 7, 2)) 'number of frames
    FlicWidth = CVI(MID$(header$, 9, 2)) 'flic width
    FlicHeight = CVI(MID$(header$, 11, 2)) 'flic height
    Ticks = CVI(MID$(header$, 17, 2)) 'timing

    '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

Find my programs here in Dav's QB64 Corner
Reply




Users browsing this thread: 2 Guest(s)