Using an animated Gif on a gui Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: Using an animated Gif on a gui

Post by AHKStudent » 22 Sep 2021, 01:59

@teadrinker another question -

I updated the gif because the old one had some bad frames, but this is unrelated to the question

I would like to disable the play once button when the animation starts and enable when it ends.

Like I said, the code is beyond me at the moment but I'm learning.

I added to the class and the part to disable works, but the part to enable does not even though it does trigger a message box when the animation is done

The complete code with my changes (line 111 and 116-118) maybe there is another way to do this?

Code: Select all

SetBatchLines, -1
gifFile := "welcome.gif"

exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
gui, color, black
Gui, Add, Picture, w480 y10 hwndhwndGif1 +BackgroundTrans, % gifFile
Gui, Add, Button, xp y+10 w80 h24 gPlay vPlaybtn, Play Once

UserFunc := Func("PlayGif").Bind(hwndGif1)
MyGif := new AnimateGif(gifFile, UserFunc, false)

Gui, Show,, Animated gif
Return

Play:
   MyGif.Play()
   Return

PlayGif(hPic, currentFrameIdx, hBitmap) {
   GuiControl,, % hPic, HBITMAP: %hBitmap%
}

GuiClose() {
   ExitApp
}

class AnimateGif
{
; UserFunc will be called with two params: currentFrameIdx and hBitmap
; user is responsible for deleting hBitmap
   __New(gifFile, UserFunc := "", cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
      this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, UserFunc, cycle)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
   }
   Play() {
      this.playing := true
      this.Frames.PlayFrames()
   }
   Pause() {
      if this.playing {
         this.playing := false
         timer := this.Frames._PlayTimer
         SetTimer, % timer, Delete
      }
   }
   Stop() {
      this.Pause()
      this.ShowFrame(1)
   }
   Prev() {
      this.ShowFrame("prev")
   }
   Next() {
      this.ShowFrame("next")
   }
   ShowFrame(which) {          ; 'which' can be "prev", "next" or "", or 1-based frame index
      this.Pause()
      (which = "prev" && this.Frames.currentFrame -= 2)
      (which + 0 && this.Frames.currentFrame := which - 1)
      this.Frames.PlayFrames()
   }
   GetFrameByIndex(idx) {
      Return hBitmap := this.Frames.GetFrame(idx - 1)
   }
   playing[] {
      get {
         Return this.Frames.playing
      }
      set {
         Return this.Frames.playing := value
      }
   }
   framesCount[] {
      get {
         Return this.Frames.frameCount
      }
   }
   __Delete() {
      this.Frames.Clear()
      this.GDIp.DisposeImage(this.pBitmap)
      this.Delete("Frames")
      this.GDIp.Release()
   }
   
   class _FramesHandling {
      __New(GDIp, pBitmap, userFunc, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.userFunc := userFunc
         this.cycle := cycle
         this.GetFrameCount()
         this.GetFrameDelay()
         this._PlayTimer := ObjBindMethod(this, "PlayFrames")
         this._currentFrame := 1
      }
      currentFrame[] {
         get {
            Return this._currentFrame
         }
         set {
            Return this._currentFrame := value < 1 ? this.frameCount + value : value > this.frameCount ? 1 : value
         }
      }
      PlayFrames() {
         Critical
		 guicontrol, disable, Playbtn
		frameIdx := ++this.currentFrame - 1
         if ( this.playing && this.currentFrame != (this.cycle ? 0xFFFFFFFF : this.frameCount) ) {
            timer := this._PlayTimer
            SetTimer, % timer, % "-" this.frameDelay[frameIdx]
         } else {
			MsgBox, done
			guicontrol, enable, Playbtn
		}
         if userFunc := this.userFunc
            %userFunc%(this.currentFrame, this.GetFrame(frameIdx))
      }
      GetFrameCount() {
         this.frameCount := this.GDIp.GetFrameCount(this.pBitmap, dimensionIDs, size)
         this.SetCapacity("dimensionIDs", size)
         this.pDimensionIDs := this.GetAddress("dimensionIDs")
         DllCall("RtlMoveMemory", "Ptr", this.pDimensionIDs, "Ptr", &dimensionIDs, "Ptr", size)
         VarSetCapacity(dimensionIDs, 0), dimensionIDs := ""
         this.currentFrame := 0
      }
      GetFrameDelay() {
         this.GDIp.GetPropertyItem(this.pBitmap, PropertyTagFrameDelay := 0x5100, item)
         len := NumGet(item, 4, "UInt")
         val := NumGet(item, 8 + A_PtrSize, "UPtr")
         this.frameDelay := []
         Loop, % len//4 {
            i := A_Index - 1
            n := NumGet(val + i*4, "UInt") * 10
            this.frameDelay[i] := n ? n : 100
         }
      }
      GetFrame(idx) {
         this.GDIp.ImageSelectActiveFrame(this.pBitmap, this.pDimensionIDs, idx)
         Return this.GDIp.CreateHBITMAPFromBitmap(this.pBitmap)
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         this.Delete("dimensionIDs")
      }
   }
}

class GDIplus {
   __New() {
      static Instance := ""
      if Instance.references {
         ++Instance.references
         Return Instance
      }
      this.references := 1
      if !DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("LoadLibrary", "Str", "gdiplus")
      VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
      Return Instance := this
   }
   Release() {
      if --this.references
         Return
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      if hModule := DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("FreeLibrary", "Ptr", hModule)
   }
   CreateBitmapFromFile(sFile) {
      DllCall("gdiplus\GdipCreateBitmapFromFile", "WStr", sFile, "PtrP", pBitmap)
      Return pBitmap
   }
   CreateHBITMAPFromBitmap(pBitmap, Background=0xffffffff) {
      DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", Background)
      Return hbm
   }
   GetImageDimensions(pBitmap, ByRef Width, ByRef Height) {
      DllCall("gdiplus\GdipGetImageWidth", "Ptr", pBitmap, "UIntP", Width)
      DllCall("gdiplus\GdipGetImageHeight", "Ptr", pBitmap, "UIntP", Height)
   }
   GetFrameCount(pBitmap, ByRef dimensionIDs, ByRef size) {
      DllCall("gdiplus\GdipImageGetFrameDimensionsCount", "Ptr", pBitmap, "UIntP", frameDimensions)
      VarSetCapacity(dimensionIDs, size := 16*frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameDimensionsList", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UInt", frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameCount", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UIntP", count)
      Return count
   }
   GetPropertyItem(pBitmap, tag, ByRef item) {
      DllCall("gdiplus\GdipGetPropertyItemSize", "Ptr", pBitmap, "UInt", tag, "UIntP", size)
      VarSetCapacity(item, size, 0)
      DllCall("gdiplus\GdipGetPropertyItem", "Ptr", pBitmap, "UInt", tag, "UInt", size, "Ptr", &item)
      Return size
   }
   ImageSelectActiveFrame(pBitmap, pDimensionIDs, idx) {
      Return DllCall("gdiplus\GdipImageSelectActiveFrame", "Ptr", pBitmap, "Ptr", pDimensionIDs, "Int", idx)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
my changes within the class

Code: Select all

  PlayFrames() {
         Critical
		 guicontrol, disable, Playbtn ; this works
		frameIdx := ++this.currentFrame - 1
         if ( this.playing && this.currentFrame != (this.cycle ? 0xFFFFFFFF : this.frameCount) ) {
            timer := this._PlayTimer
            SetTimer, % timer, % "-" this.frameDelay[frameIdx]
         } else {
			MsgBox, done ; this works
			guicontrol, enable, Playbtn ; this does not work
		}
         if userFunc := this.userFunc
            %userFunc%(this.currentFrame, this.GetFrame(frameIdx))
      }
EDIT - After playing around more I though that maybe this will work

Code: Select all

SetBatchLines, -1
gifFile := "welcome.gif"

exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
gui, color, black
Gui, Add, Picture, w480 y10 hwndhwndGif1 +BackgroundTrans, % gifFile
Gui, Add, Button, xp y+10 w80 h24 gPlay vPlaybtn, Play Once

UserFunc := Func("PlayGif").Bind(hwndGif1)
MyGif := new AnimateGif(gifFile, UserFunc, false)
numberOfFrames := MyGif.framesCount()
Gui, Show,, Animated gif
Return

Play:
	guicontrol, disable, Playbtn
   MyGif.Play()
   Return

PlayGif(hPic, currentFrameIdx, hBitmap) {
   global numberOfFrames
   GuiControl,, % hPic, HBITMAP: %hBitmap%
   if (currentFrameIdx = numberOfFrames) {
	MsgBox done ; works
	   guicontrol, enable, Playbtn ; dont work
   }
   tooltip, % currentFrameIdx " - " numberOfFrames
}

GuiClose() {
   ExitApp
}

class AnimateGif
{
; UserFunc will be called with two params: currentFrameIdx and hBitmap
; user is responsible for deleting hBitmap
   __New(gifFile, UserFunc := "", cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
    this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, UserFunc, cycle)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
   }
   Play() {
      this.playing := true
      this.Frames.PlayFrames()
   }
   Pause() {
      if this.playing {
         this.playing := false
         timer := this.Frames._PlayTimer
         SetTimer, % timer, Delete
      }
   }
   Stop() {
      this.Pause()
      this.ShowFrame(1)
   }
   Prev() {
      this.ShowFrame("prev")
   }
   Next() {
      this.ShowFrame("next")
   }
   ShowFrame(which) {          ; 'which' can be "prev", "next" or "", or 1-based frame index
      this.Pause()
      (which = "prev" && this.Frames.currentFrame -= 2)
      (which + 0 && this.Frames.currentFrame := which - 1)
      this.Frames.PlayFrames()
   }
   GetFrameByIndex(idx) {
      Return hBitmap := this.Frames.GetFrame(idx - 1)
   }
   playing[] {
      get {
         Return this.Frames.playing
      }
      set {
         Return this.Frames.playing := value
      }
   }
   framesCount[] {
      get {
         Return this.Frames.frameCount
      }
   }
   __Delete() {
      this.Frames.Clear()
      this.GDIp.DisposeImage(this.pBitmap)
      this.Delete("Frames")
      this.GDIp.Release()
   }
   
   class _FramesHandling {
      __New(GDIp, pBitmap, userFunc, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.userFunc := userFunc
         this.cycle := cycle
         this.GetFrameCount()
         this.GetFrameDelay()
         this._PlayTimer := ObjBindMethod(this, "PlayFrames")
         this._currentFrame := 1
      }
      currentFrame[] {
         get {
            Return this._currentFrame
         }
         set {
            Return this._currentFrame := value < 1 ? this.frameCount + value : value > this.frameCount ? 1 : value
         }
      }
      PlayFrames() {
         Critical
		frameIdx := ++this.currentFrame - 1
         if ( this.playing && this.currentFrame != (this.cycle ? 0xFFFFFFFF : this.frameCount) ) {
            timer := this._PlayTimer
            SetTimer, % timer, % "-" this.frameDelay[frameIdx]
         } 
         if userFunc := this.userFunc {
            %userFunc%(this.currentFrame, this.GetFrame(frameIdx))
		} 
      }
      GetFrameCount() {
         this.frameCount := this.GDIp.GetFrameCount(this.pBitmap, dimensionIDs, size)
         this.SetCapacity("dimensionIDs", size)
         this.pDimensionIDs := this.GetAddress("dimensionIDs")
         DllCall("RtlMoveMemory", "Ptr", this.pDimensionIDs, "Ptr", &dimensionIDs, "Ptr", size)
         VarSetCapacity(dimensionIDs, 0), dimensionIDs := ""
         this.currentFrame := 0
      }
      GetFrameDelay() {
         this.GDIp.GetPropertyItem(this.pBitmap, PropertyTagFrameDelay := 0x5100, item)
         len := NumGet(item, 4, "UInt")
         val := NumGet(item, 8 + A_PtrSize, "UPtr")
         this.frameDelay := []
         Loop, % len//4 {
            i := A_Index - 1
            n := NumGet(val + i*4, "UInt") * 10
            this.frameDelay[i] := n ? n : 100
         }
      }
      GetFrame(idx) {
         this.GDIp.ImageSelectActiveFrame(this.pBitmap, this.pDimensionIDs, idx)
         Return this.GDIp.CreateHBITMAPFromBitmap(this.pBitmap)
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         this.Delete("dimensionIDs")
      }
   }
}

class GDIplus {
   __New() {
      static Instance := ""
      if Instance.references {
         ++Instance.references
         Return Instance
      }
      this.references := 1
      if !DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("LoadLibrary", "Str", "gdiplus")
      VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
      Return Instance := this
   }
   Release() {
      if --this.references
         Return
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      if hModule := DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("FreeLibrary", "Ptr", hModule)
   }
   CreateBitmapFromFile(sFile) {
      DllCall("gdiplus\GdipCreateBitmapFromFile", "WStr", sFile, "PtrP", pBitmap)
      Return pBitmap
   }
   CreateHBITMAPFromBitmap(pBitmap, Background=0xffffffff) {
      DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", Background)
      Return hbm
   }
   GetImageDimensions(pBitmap, ByRef Width, ByRef Height) {
      DllCall("gdiplus\GdipGetImageWidth", "Ptr", pBitmap, "UIntP", Width)
      DllCall("gdiplus\GdipGetImageHeight", "Ptr", pBitmap, "UIntP", Height)
   }
   GetFrameCount(pBitmap, ByRef dimensionIDs, ByRef size) {
      DllCall("gdiplus\GdipImageGetFrameDimensionsCount", "Ptr", pBitmap, "UIntP", frameDimensions)
      VarSetCapacity(dimensionIDs, size := 16*frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameDimensionsList", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UInt", frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameCount", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UIntP", count)
      Return count
   }
   GetPropertyItem(pBitmap, tag, ByRef item) {
      DllCall("gdiplus\GdipGetPropertyItemSize", "Ptr", pBitmap, "UInt", tag, "UIntP", size)
      VarSetCapacity(item, size, 0)
      DllCall("gdiplus\GdipGetPropertyItem", "Ptr", pBitmap, "UInt", tag, "UInt", size, "Ptr", &item)
      Return size
   }
   ImageSelectActiveFrame(pBitmap, pDimensionIDs, idx) {
      Return DllCall("gdiplus\GdipImageSelectActiveFrame", "Ptr", pBitmap, "Ptr", pDimensionIDs, "Int", idx)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
Here are the changes

Code: Select all

UserFunc := Func("PlayGif").Bind(hwndGif1)
MyGif := new AnimateGif(gifFile, UserFunc, false)
numberOfFrames := MyGif.framesCount() ; i get the number of frames in hopes I can do something when the animation reaches that number
Gui, Show,, Animated gif
Return

Play:
	guicontrol, disable, Playbtn ; disable is working
   MyGif.Play()
   Return

PlayGif(hPic, currentFrameIdx, hBitmap) {
   global numberOfFrames
   GuiControl,, % hPic, HBITMAP: %hBitmap%
   if (currentFrameIdx = numberOfFrames) {
	MsgBox done ; works
	   guicontrol, enable, Playbtn ; dont work
   }
   tooltip, % currentFrameIdx " - " numberOfFrames ; just testing to make sure the numbers are correct
}
Attachments
welcome.gif
welcome.gif (124.7 KiB) Viewed 1618 times

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Using an animated Gif on a gui

Post by teadrinker » 22 Sep 2021, 03:04

Should be:

Code: Select all

Gui, New, +hwndhGui +E%exStyles%
...
UserFunc := Func("PlayGif").Bind(hGui, hwndGif1)
...
PlayGif(hGui, hPic, currentFrameIdx, hBitmap) {
   ...
   guicontrol, %hGui%: enable, Playbtn
   ...
}

AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: Using an animated Gif on a gui

Post by AHKStudent » 22 Sep 2021, 04:27

teadrinker wrote:
22 Sep 2021, 03:04
Should be:

Code: Select all

Gui, New, +hwndhGui +E%exStyles%
...
UserFunc := Func("PlayGif").Bind(hGui, hwndGif1)
...
PlayGif(hGui, hPic, currentFrameIdx, hBitmap) {
   ...
   guicontrol, %hGui%: enable, Playbtn
   ...
}
thank you! :thumbup: :thumbup:

fabricio234
Posts: 122
Joined: 06 Mar 2020, 21:48

Re: Using an animated Gif on a gui

Post by fabricio234 » 14 Jan 2022, 20:50

teadrinker wrote:
22 Sep 2021, 03:04
Should be:

Code: Select all

Gui, New, +hwndhGui +E%exStyles%
...
UserFunc := Func("PlayGif").Bind(hGui, hwndGif1)
...
PlayGif(hGui, hPic, currentFrameIdx, hBitmap) {
   ...
   guicontrol, %hGui%: enable, Playbtn
   ...
}
@teadrinker I'm testing with a gif 1920x1080 and its using a lot of CPU , like 12%, does it also use that many there?

Is the function that is "not well written" or what?

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Using an animated Gif on a gui

Post by teadrinker » 14 Jan 2022, 23:33

Can you share the comlete code that you use and your gif?

fabricio234
Posts: 122
Joined: 06 Mar 2020, 21:48

Re: Using an animated Gif on a gui

Post by fabricio234 » 15 Jan 2022, 11:18

@teadrinker
The gif: https://imgur.com/a/kJmhBcZ

Code: Select all

pToken := Gdip_Startup()
OnMessage(0x0201, "WM_LBUTTONDOWN")

FilePath := "Gif.gif" ; specify the file path to gif

Gui, +E0x02000000 +E0x00080000 +Resize -Caption
Gui, Color, 0
Gui, Add, Picture, x0 y0 hwndhwndGif1, % FilePath
Gui, Add, Button, xp y+10 w80 h24 gPlayPause hwndhwndPlayPause, Play

Gif1 := New Gif(FilePath, hwndGif1)
Gui, Show,, Animated Gif

Goto, PlayPause
Return




PlayPause:
isPlaying := Gif1.isPlaying
GuiControl,, % hwndPlayPause, % (isPlaying) ? "Play" : "Pause"
If (!isPlaying)
   Gif1.Play()
Else
   Gif1.Pause()
Return



GuiClose:
Esc::
ExitApp






Class Gif
{  


   __New(file, hwnd, cycle := True)
   {
      This.file := file
      This.hwnd := hwnd
      This.cycle := cycle
      This.pBitmap := Gdip_CreateBitmapFromFile(This.file)
      Gdip_GetImageDimensions(This.pBitmap, width, height)
      This.width := width, This.height := height
      This.isPlaying := False
      
      DllCall("Gdiplus\GdipImageGetFrameDimensionsCount", "ptr", This.pBitmap, "uptr*", frameDimensions)
      This.SetCapacity("dimensionIDs", 16*frameDimensions)
      DllCall("Gdiplus\GdipImageGetFrameDimensionsList", "ptr", This.pBitmap, "uptr", This.GetAddress("dimensionIDs"), "int", frameDimensions)
      DllCall("Gdiplus\GdipImageGetFrameCount", "ptr", This.pBitmap, "uptr", This.GetAddress("dimensionIDs"), "int*", count)
      This.frameCount := count
      This.frameCurrent := -1
      This.frameDelay := This.GetFrameDelay(This.pBitmap)
      This._Play("")
   }



   ; Return a zero-based array, containing the frames delay (in milliseconds)
   GetFrameDelay(pImage) {
      Static PropertyTagFrameDelay := 0x5100

      DllCall("Gdiplus\GdipGetPropertyItemSize", "Ptr", pImage, "UInt", PropertyTagFrameDelay, "UInt*", ItemSize)
      VarSetCapacity(Item, ItemSize, 0)
      DllCall("Gdiplus\GdipGetPropertyItem"    , "Ptr", pImage, "UInt", PropertyTagFrameDelay, "UInt", ItemSize, "Ptr", &Item)

      PropLen := NumGet(Item, 4, "UInt")
      PropVal := NumGet(Item, 8 + A_PtrSize, "UPtr")

      outArray := []
      Loop, % PropLen//4 {
         If !n := NumGet(PropVal+0, (A_Index-1)*4, "UInt")
            n := 10
         outArray[A_Index-1] := n * 10
      }
      Return outArray
   }
   


   Play() {
      This.isPlaying := true
      fn := This._Play.Bind(this)
      This._fn := fn
      SetTimer, % fn, -1
   }



   Pause() {
      This.isPlaying := False
      fn := This._fn
      SetTimer, % fn, Delete
   }
   


   _Play(mode := "set") {

      This.frameCurrent := mod(++This.frameCurrent, This.frameCount)
      DllCall("Gdiplus\GdipImageSelectActiveFrame", "ptr", This.pBitmap, "uptr", This.GetAddress("dimensionIDs"), "int", This.frameCurrent)
      hBitmap := Gdip_CreateHBITMAPFromBitmap(This.pBitmap)
      SetImage(This.hwnd, hBitmap)
      DeleteObject(hBitmap)
      If (mode = "set" && This.frameCurrent < (This.cycle ? 0xFFFFFFFF : This.frameCount - 1)) {
         fn := This._fn
         SetTimer, % fn, % -1 * This.frameDelay[This.frameCurrent]
      }
   
   }



   __Delete() {
      Gdip_DisposeImage(This.pBitmap)
      Object.Delete("dimensionIDs")
   }



}

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Using an animated Gif on a gui

Post by teadrinker » 16 Jan 2022, 06:44

This is not a gif, but mp4.

fabricio234
Posts: 122
Joined: 06 Mar 2020, 21:48

Re: Using an animated Gif on a gui

Post by fabricio234 » 16 Jan 2022, 09:14

@teadrinker
This is a gif, imggur who convert it to mp4, you need to click here:
image.png
image.png (25.06 KiB) Viewed 1374 times
to download as gif.

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Using an animated Gif on a gui

Post by teadrinker » 16 Jan 2022, 14:29

My image viewer also loads CPU with this gif:
 
 Image

garry
Posts: 3740
Joined: 22 Dec 2013, 12:50

Re: Using an animated Gif on a gui

Post by garry » 16 Jan 2022, 14:47

also an example , downloads a small gif to a_scriptdir

Code: Select all

#warn
#noenv
setworkingdir,%a_scriptdir%
filename1=GIF_PLAY
Gui,2: -dpiscale
Gui,2:Color,Black,Black
wa:=A_screenwidth,ha:=A_screenHeight,xx:=100
;--------------
url1=https://i.imgur.com/Vo6UxTX.gif
;url1=https://i.imgur.com/S7TXiLf.gif
f1:=A_ScriptDir . "\tex_avery_01.gif"
ifnotexist,%f1%
   urldownloadtofile,%url1%,%f1%
xxb=Shell.Explorer 
x:=(wa*1)/xx,y:=(ha*1)/xx,w:=(wa*28)/xx,h:=(ha*35)/xx
Gui,2: Add, ActiveX, x%x% y%y% w%w% h%h%  vWB,%xxb%
wb.Navigate("about:blank")
;-----------------------------
w1:=(wa*27.5)/xx ,h1:=(ha*34)/xx
w2:=(wa*23  )/xx ,h2:=(ha*28)/xx
p1:=(wa*.5  )/xx ,m1:=(ha*2 )/xx
;-----------------------------
html=
(Ltrim Join`r`n
<div style="margin:0px;padding:0px;width:%w1%px;height:%h1%px;border:0px solid black;background-color: #000000;">
<img src="%f1%" style="margin:%m1%px;padding:%p1%px;width:%w2%px;height:%h2%px;background-color: #000000">
</div>
)
wb.document.write(html)
x:=(wa*1)/xx,y:=(ha*1)/xx,w:=(wa*32)/xx,h:=(ha*40)/xx
Gui,2: show,x%x% y%y% w%w% h%h%,%filename1%
return
;----------------
2Guiclose:
WB.Document.close
WB := ""
exitapp
;=============================================

fabricio234
Posts: 122
Joined: 06 Mar 2020, 21:48

Re: Using an animated Gif on a gui

Post by fabricio234 » 16 Jan 2022, 15:00

@garry this way use much less CPU, is possible to draw without these white borders/scrollbars around?
-Edit-
This remove the control scrollbars: WB.document.body.style.overflow:="hidden"
Trying to find now how to remove these white areas around the gif.

garry
Posts: 3740
Joined: 22 Dec 2013, 12:50

Re: Using an animated Gif on a gui

Post by garry » 16 Jan 2022, 16:00

tried without border , use ESC to quit script

Code: Select all

;- modified=20220116
#warn
#noenv
setworkingdir,%a_scriptdir%
filename1=GIF_PLAY
Gui,2: -dpiscale -caption
Gui,2:Color,Black,Black
wa:=A_screenwidth,ha:=A_screenHeight,xx:=100
;--------------
url1=https://i.imgur.com/Vo6UxTX.gif
;url1=https://i.imgur.com/S7TXiLf.gif
f1:=A_ScriptDir . "\tex_avery_01.gif"
ifnotexist,%f1%
   urldownloadtofile,%url1%,%f1%
xxb=Shell.Explorer 
x:=(wa*0)/xx,y:=(ha*0)/xx,w:=(wa*28)/xx,h:=(ha*35)/xx
Gui,2: Add, ActiveX, x%x% y%y% w%w% h%h%  vWB,%xxb%
wb.Navigate("about:blank")
;-----------------------------
w1:=(wa*27.9)/xx ,h1:=(ha*34.1)/xx
w2:=(wa*23  )/xx ,h2:=(ha*28)/xx
p1:=(wa*.5  )/xx ,m1:=(ha*2 )/xx
;-----------------------------
html=
(Ltrim Join`r`n
<div style="margin:-75px;padding:0px;width:%w1%px;height:%h1%px;border:0px solid black;background-color: #000000;">
<img src="%f1%" style="margin:%m1%px;padding:%p1%px;width:%w2%px;height:%h2%px;background-color: #000000">
</div>
)
wb.document.write(html)
x:=(wa*1)/xx,y:=(ha*1)/xx,w:=(wa*23)/xx,h:=(ha*27.8)/xx
Gui,2: show,x%x% y%y% w%w% h%h%,%filename1%
return
;----------------
esc::
2Guiclose:
WB.Document.close
WB := ""
exitapp
;=============================================

fabricio234
Posts: 122
Joined: 06 Mar 2020, 21:48

Re: Using an animated Gif on a gui

Post by fabricio234 » 16 Jan 2022, 16:19

Why this way use less cpu but much more ram? testing with the gif i posted on imggur the script is using 1GB of RAM and around 3%cpu, compared to 10~% cpu using the class gif
Is possible to control the gif play speed?

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Using an animated Gif on a gui

Post by iseahound » 19 Jun 2022, 16:15

Before, your code played GIFs 10-20% slower:
image.png
image.png (143.52 KiB) Viewed 1109 times


Now I've fixed it, somewhat, by adding random variance to each frame delay.
image.png
image.png (214.92 KiB) Viewed 1109 times

Code: Select all

         if ( this.playing && this.currentFrame != (this.cycle ? 0xFFFFFFFF : this.frameCount) ) {
            timer := this._PlayTimer
            delay := this.frameDelay[frameIdx]

            ; Emulate behavior of setting 10 ms to 100 ms.
            (delay == 10) && delay := 100 ; See: https://www.biphelps.com/blog/The-Fastest-GIF-Does-Not-Exist

            ; Randomize the delay in intervals of 15.6
            resolution := 15.6
            Random rand, 1, 100000
            percentage := mod(delay, resolution) / resolution
            percentage *= 0.957 ; Higher is faster, lower is slower

            ; Randomized multiples of resolution
            if ((rand := rand / 100000) > percentage)
               res := Floor(delay / resolution) * resolution
            else
               res := Ceil(delay / resolution) * resolution

            SetTimer, % timer, % -1 * res
         }
Drawbacks:
* Assumes the timer resolution is 15.6 ms
* Uses a magic constant 0.957 - Don't ask me why it works, I don't know, I guessed.
* Frames will still desync (Chrome, Firefox, any good image viewer), because it uses Monte Carlo to achieve the proper frame rate.

Full Script

Code: Select all

#NoEnv
SetBatchLines, -1
SetWorkingDir, % A_ScriptDir

if !FileExist("abstract.gif")
   UrlDownloadToFile, https://i.imgur.com/egxGqKK.gif, abstract.gif
filePath := "abstract.gif"

Menu, Tray, Icon, % "HBITMAP:*" . LoadPicture(filePath, "GDI+ w32 h32")
exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles% +hwndhGui
Gui, Add, Picture,      hwndhPic  gOnClick, % "HBITMAP:" . LoadPicture(filePath, "GDI+")
OnMessage(0x20, Func("WM_SETCURSOR").Bind(hPic))
Gui, Add, Button, xp y+10 w75 h24 gOnClick Default, Play
Gui, Add, Button, x+5 yp  wp  hp  gOnClick        , Stop
Gui, Add, Button, x+5 yp  w35 hp  gOnClick        , <
Gui, Add, Button, x+5 yp  wp  hp  gOnClick        , >

UserFunc := Func("PlayGif").Bind(hGui, hPic, FramesCount := [])

MyGif := new AnimateGif(filePath, UserFunc) ; for every frame UserFunc will be called with two params: currentFrameIdx and hBitmap
                                            ; user is responsible for deleting hBitmap
count := FramesCount[1] := MyGif.framesCount
Gui, Show,, % "Frame: " . Format("{:0" . StrLen(count) . "}", 1) . "/" . count
Return

OnClick:
   try GuiControl,, Pause, Play
   Switch A_GuiControl {
      Case "Stop": MyGif.Stop()
      Case   "<" : MyGif.Prev()
      Case   ">" : MyGif.Next()
      Default:
         if MyGif.playing
            MyGif.Pause()
         else {
            GuiControl,, Play, Pause
            MyGif.Play()
         }
   }
   Return

GuiClose:
   ExitApp

PlayGif(hGui, hPic, FramesCount, currentFrameIdx, hBitmap) {
   GuiControl,, % hPic, HBITMAP: %hBitmap%
   count := FramesCount[1]
   frame := Format("{:0" . StrLen(count) . "}", currentFrameIdx)
   Gui, %hGui%: Show, NA, % "Frame: " . frame . "/" . count
}

WM_SETCURSOR(hPic, wp) {
   static hCursor, flags := (LR_DEFAULTSIZE := 0x40) | (LR_SHARED := 0x8000)
        , params := [ "Ptr", 0, "UInt", OCR_HAND := 32649
                              , "UInt", IMAGE_CURSOR := 2
                              , "Int", 0, "Int", 0, "UInt", flags, "Ptr" ]    
   (!hCursor && hCursor := DllCall("LoadImage", params*))
   if (wp = hPic)
      Return DllCall("SetCursor", "Ptr", hCursor)
}

class AnimateGif
{
; UserFunc will be called with two params: currentFrameIdx and hBitmap
; user is responsible for deleting hBitmap
   __New(gifFile, UserFunc := "", cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
      this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, UserFunc, cycle)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
   }
   Play() {
      this.playing := true
      this.Frames.PlayFrames()
   }
   Pause() {
      if this.playing {
         this.playing := false
         timer := this.Frames._PlayTimer
         SetTimer, % timer, Delete
      }
   }
   Stop() {
      this.Pause()
      this.ShowFrame(1)
   }
   Prev() {
      this.ShowFrame("prev")
   }
   Next() {
      this.ShowFrame("next")
   }
   ShowFrame(which) {          ; 'which' can be "prev", "next" or "", or 1-based frame index
      this.Pause()
      (which = "prev" && this.Frames.currentFrame -= 2)
      (which + 0 && this.Frames.currentFrame := which - 1)
      this.Frames.PlayFrames()
   }
   GetFrameByIndex(idx) {
      Return hBitmap := this.Frames.GetFrame(idx - 1)
   }
   playing[] {
      get {
         Return this.Frames.playing
      }
      set {
         Return this.Frames.playing := value
      }
   }
   framesCount[] {
      get {
         Return this.Frames.frameCount
      }
   }
   __Delete() {
      this.Frames.Clear()
      this.GDIp.DisposeImage(this.pBitmap)
      this.Delete("Frames")
      this.GDIp.Release()
   }
   
   class _FramesHandling {
      __New(GDIp, pBitmap, userFunc, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.userFunc := userFunc
         this.cycle := cycle
         this.GetFrameCount()
         this.GetFrameDelay()
         this._PlayTimer := ObjBindMethod(this, "PlayFrames")
         this._currentFrame := 1
      }
      currentFrame[] {
         get {
            Return this._currentFrame
         }
         set {
            Return this._currentFrame := value < 1 ? this.frameCount + value : value > this.frameCount ? 1 : value
         }
      }
      PlayFrames() {
         Critical
         frameIdx := ++this.currentFrame - 1

         if ( this.playing && this.currentFrame != (this.cycle ? 0xFFFFFFFF : this.frameCount) ) {
            timer := this._PlayTimer
            delay := this.frameDelay[frameIdx]

            ; Emulate behavior of setting 10 ms to 100 ms.
            (delay == 10) && delay := 100 ; See: https://www.biphelps.com/blog/The-Fastest-GIF-Does-Not-Exist

            ; Randomize the delay in intervals of 15.6
            resolution := 15.6
            Random rand, 1, 100000
            percentage := mod(delay, resolution) / resolution
            percentage *= 0.957 ; Higher is faster, lower is slower

            ; Randomized multiples of resolution
            if ((rand := rand / 100000) > percentage)
               res := Floor(delay / resolution) * resolution
            else
               res := Ceil(delay / resolution) * resolution

            SetTimer, % timer, % -1 * res
         }
         if userFunc := this.userFunc
            %userFunc%(this.currentFrame, this.GetFrame(frameIdx))


         ; Debug code
         static start := 0, sum := 0, count := 0, sum2 := 0, count2 := 0
         DllCall("QueryPerformanceFrequency", "int64*", frequency:=0)
         DllCall("QueryPerformanceCounter", "int64*", now:=0)
         time := (now - start) / frequency * 1000
         if (time < 10000) {
            sum += time
            count++
            average := sum / count
            sum2 += res
            count2++
            Tooltip % "Current Tick:`t" Round(time, 4)
                  . "`nAverage FPS:`t" Round(average, 4)
                  . "`nQueued FPS:`t" Round(sum2 / count2, 4)
                  . "`nTarget FPS:`t" delay
                  . "`nPercentage:`t" percentage ", " rand
                  . "`nFloor and Ceiling:`t" Floor(delay / resolution) * resolution ", " Ceil(delay / resolution) * resolution
         }
         start := now


      }
      GetFrameCount() {
         this.frameCount := this.GDIp.GetFrameCount(this.pBitmap, dimensionIDs, size)
         this.SetCapacity("dimensionIDs", size)
         this.pDimensionIDs := this.GetAddress("dimensionIDs")
         DllCall("RtlMoveMemory", "Ptr", this.pDimensionIDs, "Ptr", &dimensionIDs, "Ptr", size)
         VarSetCapacity(dimensionIDs, 0), dimensionIDs := ""
         this.currentFrame := 0
      }
      GetFrameDelay() {
         this.GDIp.GetPropertyItem(this.pBitmap, PropertyTagFrameDelay := 0x5100, item)
         len := NumGet(item, 4, "UInt")
         val := NumGet(item, 8 + A_PtrSize, "UPtr")
         this.frameDelay := []
         Loop, % len//4 {
            i := A_Index - 1
            n := NumGet(val + i*4, "UInt") * 10
            this.frameDelay[i] := n ? n : 100
         }
      }
      GetFrame(idx) {
         this.GDIp.ImageSelectActiveFrame(this.pBitmap, this.pDimensionIDs, idx)
         Return this.GDIp.CreateHBITMAPFromBitmap(this.pBitmap)
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         this.Delete("dimensionIDs")
      }
   }
}

class GDIplus {
   __New() {
      static Instance := ""
      if Instance.references {
         ++Instance.references
         Return Instance
      }
      this.references := 1
      if !DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("LoadLibrary", "Str", "gdiplus")
      VarSetCapacity(si, A_PtrSize = 8 ? 24 : 16, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
      Return Instance := this
   }
   Release() {
      if --this.references
         Return
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      if hModule := DllCall("GetModuleHandle", "Str", "gdiplus", "Ptr")
         DllCall("FreeLibrary", "Ptr", hModule)
   }
   CreateBitmapFromFile(sFile) {
      DllCall("gdiplus\GdipCreateBitmapFromFile", "WStr", sFile, "PtrP", pBitmap)
      Return pBitmap
   }
   CreateHBITMAPFromBitmap(pBitmap, Background=0xffffffff) {
      DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", Background)
      Return hbm
   }
   GetImageDimensions(pBitmap, ByRef Width, ByRef Height) {
      DllCall("gdiplus\GdipGetImageWidth", "Ptr", pBitmap, "UIntP", Width)
      DllCall("gdiplus\GdipGetImageHeight", "Ptr", pBitmap, "UIntP", Height)
   }
   GetFrameCount(pBitmap, ByRef dimensionIDs, ByRef size) {
      DllCall("gdiplus\GdipImageGetFrameDimensionsCount", "Ptr", pBitmap, "UIntP", frameDimensions)
      VarSetCapacity(dimensionIDs, size := 16*frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameDimensionsList", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UInt", frameDimensions)
      DllCall("gdiplus\GdipImageGetFrameCount", "Ptr", pBitmap, "Ptr", &dimensionIDs, "UIntP", count)
      Return count
   }
   GetPropertyItem(pBitmap, tag, ByRef item) {
      DllCall("gdiplus\GdipGetPropertyItemSize", "Ptr", pBitmap, "UInt", tag, "UIntP", size)
      VarSetCapacity(item, size, 0)
      DllCall("gdiplus\GdipGetPropertyItem", "Ptr", pBitmap, "UInt", tag, "UInt", size, "Ptr", &item)
      Return size
   }
   ImageSelectActiveFrame(pBitmap, pDimensionIDs, idx) {
      Return DllCall("gdiplus\GdipImageSelectActiveFrame", "Ptr", pBitmap, "Ptr", pDimensionIDs, "Int", idx)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}

teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Using an animated Gif on a gui

Post by teadrinker » 19 Jun 2022, 18:49

Thanks, interesting!

Post Reply

Return to “Ask for Help (v1)”