带帧数的播放GIF示例.ahk

; https://www.autohotkey.com/boards/viewtopic.php?p=468847#p468847
#NoEnv
SetBatchLines -1
SetWorkingDir %A_ScriptDir%

filePath := "abstract.gif"
FileSelectFile, filePath, , %A_WorkingDir%,选择要播放的GIF,*.gif
if (filePath="")
   ExitApp

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 % "当前刻度:`t" Round(time, 4)
            . "`n平均FPS:`t" Round(average, 4)
            . "`n排队FPS:`t" Round(sum2 / count2, 4)
            . "`n目标FPS:`t" delay
            . "`n百分率:`t" percentage ", " rand
            . "`n下限和上限:`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)
  }
}

 

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。