AnimatedGIFSprite

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

AnimatedGIFSprite

Post by infratec »

This was a quick hack to use animated GIFs as a sprite on a screen.
Since it seams to work, at least in windows, I placed in in Tricks 'n' Tips.

Save it as AnimatedGIFSprite.pbi
And please wait a short time after starting the code, because it tries to download a free GIF file from internet.

Code: Select all

;
; https://www.purebasic.fr/english/viewtopic.php?p=594218
; 

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf


Structure AnimatedGIFInfo_Structure
  Image.i
  Frames.i
  ActualFrame.i
  Sprite.i
EndStructure

Structure AnimatedGIFSprite_Structure
  Window.i
  Map GIFInfoMap.AnimatedGIFInfo_Structure()
EndStructure


Global AnimatedGIFSprite.AnimatedGIFSprite_Structure


Procedure AnimateGIFSpritesTimerEvent()
  
  Protected *AnimatedGIFSprite.AnimatedGIFInfo_Structure
  
  
  *AnimatedGIFSprite = FindMapElement(AnimatedGIFSprite\GIFInfoMap(), Str(EventTimer()))
  If *AnimatedGIFSprite
    RemoveWindowTimer(AnimatedGIFSprite\Window, *AnimatedGIFSprite\Sprite)
    
    SetImageFrame(*AnimatedGIFSprite\Image, *AnimatedGIFSprite\ActualFrame)
    ;Debug "AddWindowTimer: " + Str(AnimatedGIFSprite\Window) + " " + Str(Sprite) + " " + Str(*AnimatedGIFSprite\FrameDelay)
    AddWindowTimer(AnimatedGIFSprite\Window, *AnimatedGIFSprite\Sprite, GetImageFrameDelay(*AnimatedGIFSprite\Image))
    
    If StartDrawing(SpriteOutput(*AnimatedGIFSprite\Sprite))
      DrawImage(ImageID(*AnimatedGIFSprite\Image), 0, 0)
      StopDrawing()
    EndIf
    
    *AnimatedGIFSprite\ActualFrame + 1
    If *AnimatedGIFSprite\ActualFrame >= *AnimatedGIFSprite\Frames
      *AnimatedGIFSprite\ActualFrame = 0
    EndIf
  EndIf
  
EndProcedure


Procedure AnimateGIFSpritesUpdate(EventLoop=#False)
  
  If Not EventLoop
    While WindowEvent() : Wend
  EndIf
  
EndProcedure


Procedure.i LoadAnimatedGIFSprite(Sprite.i, Filename$, Mode.i=0, StartAnimationPosition.i=0)
  
  Protected Image.i, Result.i, *AnimatedGIFSprite.AnimatedGIFInfo_Structure
  
  
  If AnimatedGIFSprite\Window = 0
    AnimatedGIFSprite\Window = OpenWindow(#PB_Any, 0, 0, 0, 0, "", #PB_Window_Invisible)
    BindEvent(#PB_Event_Timer, @AnimateGIFSpritesTimerEvent(), AnimatedGIFSprite\Window)
  EndIf
  
  Image = LoadImage(#PB_Any, Filename$)
  If Image
    If Sprite = #PB_Any
      Sprite = CreateSprite(#PB_Any, ImageWidth(Image), ImageHeight(Image), Mode)
      Result = Sprite
    Else
      Result = CreateSprite(Sprite, ImageWidth(Image), ImageHeight(Image), Mode)
    EndIf
    If Result
      *AnimatedGIFSprite = AddMapElement(AnimatedGIFSprite\GIFInfoMap(), Str(Sprite))
      *AnimatedGIFSprite\Image = Image
      *AnimatedGIFSprite\Sprite = Sprite
      *AnimatedGIFSprite\Frames = ImageFrameCount(Image)
      If StartAnimationPosition > 0 And StartAnimationPosition <= 100
        *AnimatedGIFSprite\ActualFrame = *AnimatedGIFSprite\Frames / (100 / StartAnimationPosition) - 1
        If *AnimatedGIFSprite\ActualFrame < 0
          *AnimatedGIFSprite\ActualFrame = 0
        EndIf
      EndIf
      AddWindowTimer(AnimatedGIFSprite\Window, *AnimatedGIFSprite\Sprite, 1)  ; to show it immediately
    EndIf
  EndIf
  
  ProcedureReturn Result
  
EndProcedure


Procedure FinishAnimatedGIFSprites()
  
  UnbindEvent(#PB_Event_Timer, @AnimateGIFSpritesTimerEvent(), AnimatedGIFSprite\Window)
  
  ForEach AnimatedGIFSprite\GIFInfoMap()
    FreeImage(AnimatedGIFSprite\GIFInfoMap()\Image)
    FreeSprite(AnimatedGIFSprite\GIFInfoMap()\Sprite)
  Next
  
EndProcedure




;-Demo
CompilerIf #PB_Compiler_IsMainFile
  
  Define.i Width, Height, Depth, ScreenReady, AnySprite
  Define GIFFilename$
  
  UseGIFImageDecoder()
  
  If InitSprite() = 0 Or InitKeyboard() = 0
    MessageRequester("AnimatedGIFSprite", "Sprite system can't be initialized")
    End
  EndIf
  
  If ExamineScreenModes()
    While NextScreenMode()
      Width = ScreenModeWidth()
      If Width >= 800
        Height = ScreenModeHeight()
        Depth = ScreenModeDepth()
        ;Debug Str(Width) + "x" + Str(Height) + "," + Str(Depth)
        Break
      EndIf
    Wend
  Else
    MessageRequester("AnimatedGIFSprite", "Screen modes can't be detected")
    End
  EndIf
  
  If MessageRequester("AnimatedGIFSprite", "Use OpenScreen()?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    If OpenScreen(Width, Height, Depth, "")
      ScreenReady = 1
    EndIf
  Else
    If OpenWindow(0, 0, 0, Width, Height, "Press ESC for exit", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)
      If OpenWindowedScreen(WindowID(0), 0, 0, Width, Height)
        ScreenReady = 2
      EndIf
    EndIf
  EndIf
  
  If ScreenReady
    GIFFilename$ = GetUserDirectory(#PB_Directory_Downloads) + "animated-dog-image-0175.gif"
    
    If FileSize(GIFFilename$) < 0
      ReceiveHTTPFile("https://www.animatedimages.org/data/media/202/animated-dog-image-0175.gif", GIFFilename$)
    EndIf
    
    If FileSize(GIFFilename$) > 0
      LoadAnimatedGIFSprite(1, GIFFilename$)
      AnySprite = LoadAnimatedGIFSprite(#PB_Any, GIFFilename$, 0, 50)
    Else
      LoadAnimatedGIFSprite(1, #PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif")
      AnySprite = LoadAnimatedGIFSprite(#PB_Any, #PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif", 0, 50)
    EndIf
    
    Repeat
      FlipBuffers()
      
      AnimateGIFSpritesUpdate() ; !!! only needed if no windows event loop is available !!!
      
      ClearScreen(RGB(0, 128, 0))
      
      DisplaySprite(1, 100, 100)
      DisplayTransparentSprite(AnySprite, 300, 300)
      
      ExamineKeyboard()
    Until KeyboardPushed(#PB_Key_Escape)
    
    FinishAnimatedGIFSprites()
    
    CloseScreen()
    If ScreenReady = 2
      CloseWindow(0)
    EndIf
    
    If FileSize(GIFFilename$) > 0
      If MessageRequester("AnimatedGIFSprite", "Remove the downloaded GIF?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
        DeleteFile(GIFFilename$)
      EndIf
    EndIf
  EndIf
  
CompilerEndIf
Now you have a choice:

mk-soft provided a second version without the hidden window and event stuff.
Maybe a disadvantage: you need variables to store the identifier and extra procedures to display the sprites.

Code: Select all

;
; https://www.purebasic.fr/english/viewtopic.php?p=594218
; 

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf

Structure AnimatedGIFSpriteInfo_Structure
  Sprite.i
  Delay.i
EndStructure

Structure AnimatedGIFSprite_Structure
  NextTime.i
  List SpriteList.AnimatedGIFSpriteInfo_Structure()
EndStructure




Procedure.i LoadAnimatedGIFSprite(Filename$, Mode.i=0, StartAnimationPosition.i=0)
  
  Protected Image.i, cnt.i, index,i, *AnimatedGIFSprite.AnimatedGIFSprite_Structure, ActualFrame.i
  
  
  Image = LoadImage(#PB_Any, Filename$)
  If Image
    
    *AnimatedGIFSprite = AllocateStructure(AnimatedGIFSprite_Structure)
    If *AnimatedGIFSprite
      
      cnt = ImageFrameCount(Image) - 1
      For index = 0 To cnt
        AddElement(*AnimatedGIFSprite\SpriteList())
        SetImageFrame(Image, index)
        *AnimatedGIFSprite\SpriteList()\Sprite = CreateSprite(#PB_Any, ImageWidth(Image), ImageHeight(Image), Mode)
        *AnimatedGIFSprite\SpriteList()\Delay = GetImageFrameDelay(Image)
        If StartDrawing(SpriteOutput(*AnimatedGIFSprite\SpriteList()\Sprite))
          DrawImage(ImageID(Image), 0, 0)
          StopDrawing()
        EndIf
      Next
      
      If StartAnimationPosition > 0 And StartAnimationPosition <= 100
        ActualFrame = cnt / (100 / StartAnimationPosition) - 1
        If ActualFrame < 0
          ActualFrame = 0
        EndIf
      EndIf
      
      SelectElement(*AnimatedGIFSprite\SpriteList(), ActualFrame)
      *AnimatedGIFSprite\NextTime = ElapsedMilliseconds() + *AnimatedGIFSprite\SpriteList()\Delay
      
    EndIf
    
    FreeImage(Image)
  EndIf
  
  ProcedureReturn *AnimatedGIFSprite
  
EndProcedure


Procedure DisplayAnimatedGifSprite(*AnimatedGIFSprite.AnimatedGIFSprite_Structure, x, y)
  
  Protected time.i
  
  
  With *AnimatedGIFSprite
    
    DisplaySprite(\SpriteList()\Sprite, x, y)
    
    time = ElapsedMilliseconds()
    If time - \NextTime >= 0
      If Not NextElement(\SpriteList())
        FirstElement(\SpriteList())
      EndIf
      ;\NextTime + \SpriteList()\Delay
      \NextTime = time + \SpriteList()\Delay
    EndIf
    
  EndWith
  
EndProcedure


Procedure DisplayAnimatedGifTransparentSprite(*AnimatedGIFSprite.AnimatedGIFSprite_Structure, x, y, Intensity=255, Color.q=-1)
  
  Protected time.i
  
  
  With *AnimatedGIFSprite
    
    If Color = -1
      DisplayTransparentSprite(\SpriteList()\Sprite, x, y, Intensity)
    Else
      DisplayTransparentSprite(\SpriteList()\Sprite, x, y, Intensity, Color)
    EndIf
    
    time = ElapsedMilliseconds()
    If time - \NextTime >= 0
      If Not NextElement(\SpriteList())
        FirstElement(\SpriteList())
      EndIf
      ;\NextTime + \SpriteList()\Delay
      \NextTime = time + \SpriteList()\Delay
    EndIf
    
  EndWith
  
EndProcedure




;-Demo
CompilerIf #PB_Compiler_IsMainFile
  
  Define.i Width, Height, Depth, ScreenReady
  Define *AnySprite, *AnySprite2
  Define GIFFilename$
  
  
  UseGIFImageDecoder()
  
  If InitSprite() = 0 Or InitKeyboard() = 0
    MessageRequester("AnimatedGIFSprite", "Sprite system can't be initialized")
    End
  EndIf
  
  If ExamineScreenModes()
    While NextScreenMode()
      Width = ScreenModeWidth()
      If Width >= 800
        Height = ScreenModeHeight()
        Depth = ScreenModeDepth()
        ;Debug Str(Width) + "x" + Str(Height) + "," + Str(Depth)
        Break
      EndIf
    Wend
  Else
    MessageRequester("AnimatedGIFSprite", "Screen modes can't be detected")
    End
  EndIf
  
  If MessageRequester("AnimatedGIFSprite", "Use OpenScreen()?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
    If OpenScreen(Width, Height, Depth, "")
      ScreenReady = 1
    EndIf
  Else
    If OpenWindow(0, 0, 0, 800, 600, "Press ESC for exit", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)
      If OpenWindowedScreen(WindowID(0), 0, 0, 800, 600)
        ScreenReady = 2
      EndIf
    EndIf
  EndIf
  
  If ScreenReady > 0
    GIFFilename$ = GetUserDirectory(#PB_Directory_Downloads) + "animated-dog-image-0175.gif"
    
    If FileSize(GIFFilename$) < 0
      ReceiveHTTPFile("https://www.animatedimages.org/data/media/202/animated-dog-image-0175.gif", GIFFilename$)
    EndIf
    
    If FileSize(GIFFilename$) > 0
      *AnySprite = LoadAnimatedGIFSprite(GIFFilename$)
      *AnySprite2 = LoadAnimatedGIFSprite(GIFFilename$, 0, 50)
    Else
      *AnySprite = LoadAnimatedGIFSprite(#PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif")
      *AnySprite2 = LoadAnimatedGIFSprite(#PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif", 0, 50)
    EndIf
    
    Repeat
      
      ; else I get an inresponsible window if I click with mouse
      If ScreenReady = 2
        While WindowEvent() : Wend
      EndIf
      
      FlipBuffers()
      
      ClearScreen(RGB(0, 128, 0))
      
      DisplayAnimatedGifSprite(*AnySprite, 100, 100)
      DisplayAnimatedGifTransparentSprite(*AnySprite2, 300, 300)
      
      ExamineKeyboard()
      
    Until KeyboardPushed(#PB_Key_Escape)
    
    CloseScreen()
    If ScreenReady = 2
      CloseWindow(0)
    EndIf
    
    If FileSize(GIFFilename$) > 0
      If MessageRequester("AnimatedGIFSprite", "Remove the downloaded GIF?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
        DeleteFile(GIFFilename$)
      EndIf
    EndIf
  EndIf
  
CompilerEndIf
Last edited by infratec on Tue Jan 24, 2023 12:26 pm, edited 16 times in total.
User avatar
idle
Always Here
Always Here
Posts: 5097
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: AnimatedGIFSprite

Post by idle »

It was Dog gone! As in black screen, Change the block from line 140

Code: Select all

    ReceiveHTTPFile("https://www.animatedimages.org/data/media/202/animated-dog-image-0175.gif", GIFFilename$)
    If FileSize(GIFFilename$) > 0
      LoadAnimatedGIFSprite(1,GIFFilename$)
      AnySprite = LoadAnimatedGIFSprite(#PB_Any, GIFFilename$) 
    Else
      LoadAnimatedGIFSprite(1, #PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif")
      AnySprite = LoadAnimatedGIFSprite(#PB_Any, #PB_Compiler_Home + "Examples/Sources/Data/PureBasicLogo.gif")
    EndIf

User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

I agree with this little change of code, but it still doesn't work on my Mac, only in Windows.
But it's an interesting piece of code and I'll change it and use it to create a program to convert Animated GIFs into a Stack of JPGs to be used as individual frames, if the situation and program complexity will need it to be done this way.

Thanks for your code!

PS: Just to let you know, problem with MAC is on this "#PB_Directory_Downloads" that doesn't look to function at all. I'll check it better!
Last edited by Kamarro on Sun Jan 22, 2023 10:32 am, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Ups ...

loading the file from internet was a last 'extension' at a late time. :oops:
And since the file already exists in my folder, I didn't noticed this bug.

I also process now all current window events, since the 'walk' stopped when moving the mouse.

Changed the code above.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Changed the code above:

Noticed, that against the documentation, SetImageFrame() also works with SpriteOutput().
The first StartDrawing() can be avoided.

Let the dogs run on a green field.
It is the original intention to show the first dog with DisplaySprite() and the second with DisplayTransparentSprite()
To show the right usage.
Last edited by infratec on Sun Jan 22, 2023 11:08 am, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Changed the code above:

Added a parameter to LoadAnimatedGIFSprite() called StartAnimationPosition.
It is a percentage value for the animation start frame.

Now the dogs don't need to walk in lockstep.
User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

I'm getting an error on line 90 "Specified Timeout is 0 or negative", but this is probably because I haven't installed PB as usually should be done and I'm using it on a different folder without Examples, Souces, etc...

I have to see this with another look. I'll tell you after!
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

This means GetImageFrameDelay(Image) returns a negative or a 0 value.

You have to check the used GIF file.
Both in the example used files are correct wih a positive value as frame delay.

Is your GIF is really animated?

Can you provide a link to your used GIF file?
User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

I'm using your program AS IS, with your GIF... Probably MAC issue, I can't figure it out!

But, as an extra, on my program, I tried to add a SetFrame(image,x) before one of my Animations and off coursed I got an error, because I can't use it on Sprites, BUT...

... after running the program and delete the SetFrame lines, when I re-Run the program, animations appear as static and do not flicker anymore, that's weird!

Checking...
User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

If I disable the Debugger, I get no errors, but the gif is still a black rectangle, no dog at all...

Checking!
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Simply move the line

Code: Select all

Debug "AddWindowTimer: " + Str(AnimatedGIFSprite\Window) + " " + Str(Sprite) + " " + Str(*AnimatedGIFSprite\FrameDelay)
before AddWindowTimer() and look at the value of FrameDelay.

Since I have no access to a mac at the moment, I can not check anyhing.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Small tool:

Code: Select all

EnableExplicit

Define Filename$, Message$

UseGIFImageDecoder()

Filename$ = OpenFileRequester("Choose an animated GIF", "", "GIF|*.gif", 0)
If Filename$
  If LoadImage(0, Filename$)
    Message$ = GetFilePart(Filename$) + #LF$ + #LF$
    Message$ + "Size: " + Str(ImageWidth(0)) + "x" + Str(ImageHeight(0)) + #LF$
    Message$ + "Frames: " + Str(ImageFrameCount(0)) + #LF$
    Message$ + "FrameDelay: " + Str(GetImageFrameDelay(0)) + "ms"
    Debug Message$
    MessageRequester("GIF Info", Message$)
    FreeImage(0)
  Else
    MessageRequester("Error", "Error loading GIF" + #LF$ + #LF$ + Filename$)
  EndIf
EndIf
Last edited by infratec on Sun Jan 22, 2023 1:40 pm, edited 1 time in total.
User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

This is the result: AddWindowTimer: 4297420760 1 0

But for me, don't worry. I am very happy with what I have at this moment. Also, I'm getting older or more stupid, because I can't figure out why when I started this game 7 years ago, I chosed the OpenScreen instead... what is the big thing that made me use OpenScreen instead of OpenWindow? Transparency Sprites, DisplayTransparentSprite on JPEGs...??? I can't remember.

I'll have to re-check the main differences between these two modes, but that's another subject. And your program is really well done!
User avatar
Kamarro
User
User
Posts: 65
Joined: Thu Mar 19, 2015 7:55 pm
Location: England

Re: AnimatedGIFSprite

Post by Kamarro »

Your Small Tool is nice. I've done something like that, a bit more detailled on layout,but is almost the same. I called it ShowAnimGIFSpecs, but I'll not post it here because it includes large PNGs and things like that. Yours is much more praticable and easier to use... better, is faster than mine!

Nice little code!
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: AnimatedGIFSprite

Post by infratec »

Changed code above:

To avoid a crash at AddWindowTimer() I added an If to check the delay value.
Post Reply