1 (изменено: sanny0112, 2021-05-30 18:18:03)

Тема: AHK: Текст поверх GIF

Доброго времени суток, понадобилось вывести текст поверх .gif. Для вывода анимированной гифки использовал следующую функцию. Чтобы сделать текст поверх картинки применил данный метод. Но, к сожалению, одно с другим не состыкуется и блок текста уходит на задний план. Есть ли у Вас какие-либо идеи как разрешить данную проблему?

+ Код
global ChatText
global ChatIndex := 0

Gui, Font, S10 CDefault , Verdana
Gui, Add, Edit, x0 y364 w720 h20 vChatEnter,

;Gui, Add, Picture, x0 y0 w720 h365 , bg.gif
AddAnimatedGIF(A_ScriptDir "\bg.gif",0,0,720,365)

Gui, Font, S10 CFFFFFF Bold, Arial
Gui, Add, Text, x12 y9 w650 h365 BackgroundTrans vChat,



Gui, Show, x649 y337 h384 w674, Game
Return

GuiClose:
ExitApp

Enter::
{
	IfWinActive, Game
	{
		GuiControlGet, ChatEnter
		print(ChatEnter)
		GuiControl,,ChatEnter,
	}
	else Send {Enter}
	Return
}

F5::Reload

print(Message) {
	if(Message != "") {
		if (ChatText != "") {
			if (ChatIndex > 21) {
				RegExMatch(ChatText, "^.*?\n(.*)", StrDel)
				ChatText := StrDel1 "`n" Message
			}
			else ChatText := ChatText "`n" Message
		}
		else ChatText := Message
		ChatIndex++
		GuiControl,,Chat, %ChatText%
	}
	Return
}

AddAnimatedGIF(imagefullpath , x="", y="", w="", h="", guiname = "1")
{
	global AG1,AG2,AG3,AG4,AG5,AG6,AG7,AG8,AG9,AG10
	static AGcount:=0, pic
	AGcount++
	html := "<html><body style='background-color: transparent' style='overflow:hidden' leftmargin='0' topmargin='0'><img src='" imagefullpath "' width=" w " height=" h " border=0 padding=0></body></html>"
	Gui, AnimGifxx:Add, Picture, vpic, %imagefullpath%
	GuiControlGet, pic, AnimGifxx:Pos
	Gui, AnimGifxx:Destroy
	Gui, %guiname%:Add, ActiveX, % (x = "" ? " " : " x" x ) . (y = "" ? " " : " y" y ) . (w = "" ? " w" picW : " w" w ) . (h = "" ? " h" picH : " h" h ) " vAG" AGcount, Shell.Explorer
	AG%AGcount%.navigate("about:blank")
	AG%AGcount%.document.write(html)
	return "AG" AGcount
}
+ Снимки экрана

Как в приведённом коде
https://i.imgur.com/LPjXLnH.png

Поменял местами блоки вывода GIF и текста
https://i.imgur.com/YN0eb5L.png

Раскомментировал штатный способ добавления картинок и закомментировал функцию (Получил статичную картинку)
https://i.imgur.com/P14991A.png

P.S. Сама гифка

2

Re: AHK: Текст поверх GIF

Отрисовывайте гиф через gdi и там уже производите необходимые вам манипуляции.

3

Re: AHK: Текст поверх GIF

Пример:

#NoEnv
#Include Gdip_All.ahk
SetBatchLines, -1

filePath := "C:\Users\User\Desktop\bg76852ee8ba300133.gif"

exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
Gui, Add, Picture, y10 hwndhPic, % filePath

MyGif := new Gif(filePath, hPic, "Это текст, который будет поверх gif", "Calibri", "Centre y100 r4 s30 cFFFFFFFF")
MyGif.Play()
Gui, Show
return

GuiClose:
   ExitApp

class Gif
{	
   __New(file, hwnd, text := "", font := "Arial", textOptions := "", cycle := true)
   {
      pToken := Gdip_Startup()
      this.file := file
      this.hwnd := hwnd
      this.cycle := cycle
      this.text := text
      this.font := font
      this.textOptions := textOptions
      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)
      if (this.text != "") {
         G := Gdip_GraphicsFromImage(this.pBitmap)
         Gdip_GetDimensions(this.pBitmap, W, H)
         Gdip_TextToGraphics(G, this.text, this.textOptions, this.font, W, H)
         Gdip_DeleteGraphics(G)
      }
      hBitmap := Gdip_CreateHBITMAPFromBitmap(this.pBitmap)
      GuiControl,, % this.hwnd, HBITMAP: %hBitmap%
      ; SetImage(this.hwnd, hBitmap) ; old variant
      ; 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")
   }
}

Понадобится GDI+ standard library 1.45 by tic, параметр «options» описан в восьмом примере на странице по ссылке.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

4

Re: AHK: Текст поверх GIF

Хотя можно и через ActiveX:

gifFilePath := "C:\Users\User\Desktop\bg76852ee8ba300133.gif"
FixIE()
Size := GetImageSize(gifFilePath)

Gui, Add, ActiveX, % "w" . Size.W . " h" . Size.H . " vDoc", htmlfile
Doc.write("<body style='margin: 0; overflow: hidden;'><img src='" gifFilePath "' width='" Size.W "'></body>")
par := Doc.createElement("p")
par.innerHTML := "Это текст, который будет поверх gif"
styles := par.style
for k, v in { color: "white", fontFamily: "Calibri"
            , fontSize: 30, textAlign: "center"
            , position: "fixed", top: "100px", width: Size.W . "px" }
   styles[k] := v
Doc.body.appendChild(par)
Gui, Show
Return

GuiClose:
   ExitApp

GetImageSize(imageFilePath) {
   if !hBitmap := LoadPicture(imageFilePath, "GDI+")
      throw "Failed to load the image"
   VarSetCapacity(BITMAP, size := 4*4 + A_PtrSize*2, 0)
   DllCall("GetObject", "Ptr", hBitmap, "Int", size, "Ptr", &BITMAP)
   DllCall("DeleteObject", "Ptr", hBitmap)
   Return { W: NumGet(BITMAP, 4, "UInt"), H: NumGet(BITMAP, 8, "UInt") }
}

FixIE() {
   static regKey := "HKCU\Software\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION"
   SplitPath, % A_IsCompiled ? A_ScriptFullPath : A_AhkPath, exeName
   RegRead, value, % regKey, % exeName
   if (value != 11000)
      RegWrite, REG_DWORD, % regKey, % exeName, 11000
   Return !ErrorLevel
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

5

Re: AHK: Текст поверх GIF

Поигрался воспроизведением гиф через activex.
Совсем беда - не больше 5 контролов дает создать с анимацией.

imagefullpath := "D:\back.gif"

html := "<html><body><img src='" imagefullpath "'></body></html>"
Count := 5
loop % Count
{
   Gui, Add, ActiveX, w100 h100 va%A_Index%, htmlfile
   a%A_Index%.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=EDGE"">")
   sleep 50
}
loop % Count
   a%A_Index%.write(html)
Gui, Show

6

Re: AHK: Текст поверх GIF

У меня ни на семёрке, ни на десятке нет такого ограничения.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

7

Re: AHK: Текст поверх GIF

Хотя нет, иногда только один- подгружает.
Я имею в виду анимрованные гифы.
Часть из них у меня показывается статичными.

8

Re: AHK: Текст поверх GIF

У меня вот:
 
 https://i.imgur.com/lV1uQny.gif

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

9

Re: AHK: Текст поверх GIF

И похоже это каким-то образом зависит от гифов.
Те, которые с прозрачностью совсем глючат.
https://gifyu.com/image/19Xv

10

Re: AHK: Текст поверх GIF

Еще видно от кеша зависит.
Если вставлять новые, то бывает, что только один подгружается.
https://giphy.com/explore/big

11

Re: AHK: Текст поверх GIF

https://i.imgur.com/BeIOWzn.gif

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

12

Re: AHK: Текст поверх GIF

Кстати, довёл до ума класс с GDIplus:

#NoEnv
SetBatchLines, -1

filePath := "D:\OneDrive\Scripts\GDI+\Gif\gif files\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
{
   __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() {                    ; UserFunc will be called with two params: currentFrameIdx and hBitmap
      this.playing := true     ; user is responsible for deleting hBitmap
      this.Frames.PlayFrames()
   }
   Pause() {
      if this.playing {
         this.playing := false
         timer := this.Frames._PlayTimer
         SetTimer, % timer, Delete
      }
   }
   Stop() {
      this.Pause()
      this.PlayFrame(1)
   }
   Prev() {
      this.PlayFrame("prev")
   }
   Next() {
      this.PlayFrame("next")
   }
   PlayFrame(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.Delete("GDIp")
   }
   
   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")
         this.Delete("GDIp")
      }
   }
}

class GDIplus {
   __New() {
      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
   }
   __Delete() {
      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)
   }
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

13

Re: AHK: Текст поверх GIF

И у тебя постоянно показываются все гифы?

14

Re: AHK: Текст поверх GIF

Пока не встречал проблемных.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

15

Re: AHK: Текст поверх GIF

Интересно. У меня на 2 компьютерах с разным виндовсом 10 одна и та же ерунда.
Кстати, на оф.форуме тоже пишут:

One problem it has is that if you add more than one animated GIF with it, often one of them would be frozen or wouldn't be shown. But if you want just one, this seems to work well. I don't know if the dll-based version you posted will show multiple GIFs without problems.

https://www.autohotkey.com/boards/viewt … 632#p38632

16

Re: AHK: Текст поверх GIF

Чёрт его знает. У меня с семёркой довольно старый комп, всё ок.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

17 (изменено: Malcev, 2021-06-16 05:10:27)

Re: AHK: Текст поверх GIF

А вот так у тебя тоже все анимируется на win10?

imagefullpath := "D:\back.gif"

html := "<html><img src='" imagefullpath "'></html>"
Count := 2
loop % Count
{
   Gui, Add, ActiveX, w100 h100 va%A_Index%, about:<!DOCTYPE html><meta http-equiv="X-UA-Compatible" content="IE=edge">
   a%A_Index%.document.write(html)
}
Gui, Show
return
f11::
a2.refresh()
return

У меня вторая гифка анимируется только при зажатии f11.

18

Re: AHK: Текст поверх GIF

Malcev пишет:

У меня вторая гифка анимируется только при зажатии f11.

У меня так же.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

19 (изменено: Malcev, 2021-06-16 12:38:13)

Re: AHK: Текст поверх GIF

Понял причину.
Гифы не могут быть одинакового пути.
Интересный глюк.
https://docs.microsoft.com/hr-HR/troubl … -instances

imagefullpath1 := "D:\bel\1.gif"
imagefullpath2 := "D:\bel\2.gif"

Count := 2
loop % Count
{
   html := "<html><img src='" imagefullpath%A_Index% "'></html>"
   Gui, Add, ActiveX, w100 h100 va%A_Index%, about:<!DOCTYPE html><meta http-equiv="X-UA-Compatible" content="IE=edge">
   a%A_Index%.document.write(html)
}
Gui, Show

20

Re: AHK: Текст поверх GIF

Интересно, но я бы в любом случае использовал вариант с GDIplus, так как есть возможность управления. На самом деле, в html тоже можно управлять gif через javascript, но довольно сложно.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

21

Re: AHK: Текст поверх GIF

У html есть плюс в том, что удобно листы предпросмотра делать.
https://www.autohotkey.com/boards/viewt … amp;t=1277
А на паузу ставить (показывать 1ый кадр) можно через отрисовку canvas после загрузки гифа.

22

Re: AHK: Текст поверх GIF

Ну наверно. В принципе, так же можно и с простым окном сделать. Другое дело, что html может проигрывать gif асинхронно.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

23

Re: AHK: Текст поверх GIF

При больших количествах анимационных гифов через gdi может быть будет иметь смысл использовать CachedBitmap.
https://docs.microsoft.com/en-us/window … chedbitmap
Но я тут постил скорость прорисовки gdi на современных версиях виндовс.
И с каждой новой версией ситуация всё плачевней.
https://twitter.com/ADeltaXForce/status … 5827950597

24

Re: AHK: Текст поверх GIF

Malcev пишет:

будет иметь смысл использовать CachedBitmap.

Не знал раньше про CachedBitmap, но тоже думал про сохранение bitmap между итерациями, может попробую.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

25

Re: AHK: Текст поверх GIF

Посмотрел, это не совсем то, что нужно. Я думал, что это как-то сохраняет Bitmap для последующего использования, а это только оптимизирует отрисовку картинки в Graphics, который связан с hDC окна. Это подойдёт для Layered Windows, а для обычного не очень удобно.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

26 (изменено: Malcev, 2021-06-17 06:37:59)

Re: AHK: Текст поверх GIF

teadrinker пишет:

а это только оптимизирует отрисовку картинки в Graphics

У меня скорость почти в 5 раз больше, чем если выводить заранее сохраненные фреймы и почти в 10, чем если каждый раз вызывать GdipImageSelectActiveFrame .
Результаты без UpdateLayeredWindow:
1938
1031
219

file := "1.gif"
setbatchlines -1

Gdip_Startup()
pBitmap := Gdip_CreateBitmapFromFile(file)
Gdip_GetImageDimensions(pBitmap, Width, Height)
hbm := CreateDIBSection(Width, Height)
hdc := CreateCompatibleDC()
obm := SelectObject(hdc, hbm)
G := Gdip_GraphicsFromHDC(hdc)
Gdip_SetInterpolationMode(G, 7)

DllCall("Gdiplus\GdipImageGetFrameDimensionsCount", "ptr", pBitmap, "uint*", count)
VarSetCapacity(dID, 16*count, 0)
DllCall("Gdiplus\GdipImageGetFrameDimensionsList", "ptr", pBitmap, "ptr", &dID, "uint", count)
DllCall("Gdiplus\GdipImageGetFrameCount", "ptr", pBitmap, "ptr", &dID, "uint*", CountFrames)

Gui, -DPIScale +E0x80000 +hwndhGui
Gui, Show, w%Width% h%Height%

a := a_tickcount
loop % 1000//CountFrames
{
   loop % CountFrames
   {
      DllCall("Gdiplus\GdipImageSelectActiveFrame", "ptr", pBitmap, "ptr", &dID, "int", A_Index-1)
      Gdip_DrawImage(G, pBitmap)
      UpdateLayeredWindow(hGui, hdc,0,0,width,height)
   }
}
msgbox % a_tickcount - a

loop % CountFrames
{
   DllCall("Gdiplus\GdipImageSelectActiveFrame", "ptr", pBitmap, "ptr", &dID, "int", A_Index-1)
   pBitmap%A_Index% := Gdip_CloneBitmapArea(pBitmap, 0, 0, width, height)
}
a := a_tickcount
loop % 1000//CountFrames
{
   loop % CountFrames
   {
      Gdip_DrawImage(G, pBitmap%A_Index%)
      UpdateLayeredWindow(hGui, hdc,0,0,width,height)
   }
}
msgbox % a_tickcount - a

loop % CountFrames
{
   DllCall("Gdiplus\GdipImageSelectActiveFrame", "ptr", pBitmap, "ptr", &dID, "int", A_Index-1)
   DllCall("Gdiplus\GdipCreateCachedBitmap", "ptr", pBitmap, "ptr", G, "ptr*", CachedBitmap%A_Index%)
}
a := a_tickcount
loop % 1000//CountFrames
{
   loop % CountFrames
   {
      DllCall("Gdiplus\GdipDrawCachedBitmap", "ptr", G, "ptr", CachedBitmap%A_Index%, "int", 0, "int", 0)
      UpdateLayeredWindow(hGui, hdc,0,0,width,height)
   }
}
msgbox % a_tickcount - a

27

Re: AHK: Текст поверх GIF

Да, но для чистоты эксперимента нужно проводить его в условиях практического применения — с большим количеством окон, сделав их дочерними, и с обычными окнами (не layered) с обработкой WM_PAINT.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

28

Re: AHK: Текст поверх GIF

Продолжил тестировать activex и выяснил, что htmlfile в отличии от WebBrowser не работает нормально в WS_EX_COMPOSITED окне.
При посылании клика иногда мерцает.
Поймать сложно, но можно.

gui, main:  +E0x02000000
gui, main:show, w500 h400

gui, 1: +ParentMain
gui, 1: Add, ActiveX, x0 y0 w100 h100, about:<!DOCTYPE html><meta http-equiv="X-UA-Compatible" content="IE=edge">
gui, 1: Show, x100 y100

gui, 2: +ParentMain
gui, 2: Add, ActiveX, x0 y0 w100 h100, htmlfile
gui, 2: Show, x300 y100
return

29

Re: AHK: Текст поверх GIF

Подтверждаю.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

30

Re: AHK: Текст поверх GIF

Понял, как применить CachedBitmap к обычному окну, но разницы не ощутил. Основное время в данном случае занимает отрисовка контрола через

GuiControl,, % hPic, HBITMAP: %hBitmap%

Вот первоначальный вариант с добавленным позиционированием множества контролов:

#NoEnv
SetBatchLines, -1

filePath := "D:\OneDrive\Scripts\GDI+\Gif\gif files\girl.gif"

AnimateGif.Gifs := {}
exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
Gui, Margin, 5, 5
GuiW := 12 + 5 ; borders + margin
Loop 20 {
   if (A_Index > 1) {
      GuiW += W + 5
      if (GuiW < A_ScreenWidth)
         x := "x+5", y := "yp"
      else
         x := "xm", y := "y+5", GuiW := 12 + 5 + W
   }
   Gui, Add, Pic, % "hwndhGif" . A_Index . " " . (A_Index = 1 ? "" : x . " " . y), % "HBITMAP:" . LoadPicture(filePath, "GDI+")
   if (A_Index = 1) {
      WinGetPos,,, W,, % "ahk_id" hGif%A_Index%
      GuiW += W
   }
   handler := Func("OnClick").Bind(hGif%A_Index%)
   GuiControl, +g, % hGif%A_Index%, % handler
   AnimateGif.Gifs[hGif%A_Index%] := new AnimateGif(filePath, Func("PlayGif").Bind(hGif%A_Index%))
   AnimateGif.Gifs[hGif%A_Index%].Play()
}
OnMessage(0x20, "WM_SETCURSOR")
Gui, Show
Return

GuiClose:
   AnimateGif.Gifs := ""
   ExitApp

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

OnClick(hPic) {
   if AnimateGif.Gifs[hPic].playing
      AnimateGif.Gifs[hPic].Pause()
   Else
      AnimateGif.Gifs[hPic].Play()
}

WM_SETCURSOR(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 AnimateGif.Gifs.HasKey(wp)
      Return DllCall("SetCursor", "Ptr", hCursor)
}

class AnimateGif
{
   __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() {                    ; UserFunc will be called with two params: currentFrameIdx and hBitmap
      this.playing := true     ; user is responsible for deleting hBitmap
      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)
   }
}

Вот с CachedBitmap:

#NoEnv
SetBatchLines, -1

filePath := "D:\OneDrive\Scripts\GDI+\Gif\gif files\girl.gif"

AnimateGif.Gifs := {}
exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
Gui, Margin, 5, 5
GuiW := 12 + 5 ; borders + margin
Loop 20 {
   if (A_Index > 1) {
      GuiW += W + 5
      if (GuiW < A_ScreenWidth)
         x := "x+5", y := "yp"
      else
         x := "xm", y := "y+5", GuiW := 12 + 5 + W
   }
   Gui, Add, Pic, % "hwndhGif" . A_Index . " " . (A_Index = 1 ? "" : x . " " . y), % "HBITMAP:" . LoadPicture(filePath, "GDI+")
   if (A_Index = 1) {
      WinGetPos,,, W,, % "ahk_id" hGif%A_Index%
      GuiW += W
   }
   handler := Func("OnClick").Bind(hGif%A_Index%)
   GuiControl, +g, % hGif%A_Index%, % handler
   AnimateGif.Gifs[hGif%A_Index%] := new AnimateGif(filePath, Func("PlayGif").Bind(hGif%A_Index%))
   AnimateGif.Gifs[hGif%A_Index%].Play()
}
OnMessage(0x20, "WM_SETCURSOR")
Gui, Show
Return

GuiClose:
   AnimateGif.Gifs := ""
   ExitApp

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

OnClick(hPic) {
   if AnimateGif.Gifs[hPic].playing
      AnimateGif.Gifs[hPic].Pause()
   Else
      AnimateGif.Gifs[hPic].Play()
}

WM_SETCURSOR(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 AnimateGif.Gifs.HasKey(wp)
      Return DllCall("SetCursor", "Ptr", hCursor)
}

class AnimateGif
{
   __New(gifFile, UserFunc := "", cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
      this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, width, height, UserFunc, cycle)
   }
   Play() {                    ; UserFunc will be called with two params: currentFrameIdx and hBitmap
      this.playing := true     ; user is responsible for deleting hBitmap
      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, width, height, userFunc, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
         this.DC := new CompatibleDC(this.hDC, width, height)
         this.G := this.GDIp.GraphicsFromHDC(this.DC.hCDC)
         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)
         DllCall("gdiplus\GdipCreateCachedBitmap", "Ptr", this.pBitmap, "Ptr", this.G, "PtrP", pCachedBitmap)
         DllCall("gdiplus\GdipDrawCachedBitmap", "Ptr", this.G, "Ptr", pCachedBitmap, "Int", 0, "Int", 0)
         DllCall("gdiplus\GdipDeleteCachedBitmap", "Ptr", pCachedBitmap)
         Return this.DC.hCBM
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         this.Delete("dimensionIDs")
         this.GDIp.DeleteGraphics(this.G)
         this.DC := ""
         DllCall("ReleaseDC", this.hDC)
      }
   }
}

class CompatibleDC
{
   __New(hDC, w, h)  {
      this.hCDC := DllCall("CreateCompatibleDC", Ptr, hDC, Ptr)
      this.hCBM := DllCall("CreateCompatibleBitmap", Ptr, hDC, Int, w, Int, h, Ptr)
      this.oBM := DllCall("SelectObject", Ptr, this.hCDC, Ptr, this.hCBM, Ptr)
   }
   
   __Delete()  {
      DllCall("SelectObject", Ptr, this.hCDC, Ptr, this.oBM, Ptr)
      DllCall("DeleteDC", Ptr, this.hCDC)
      DllCall("DeleteObject", Ptr, this.hCBM)
   }
}


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
   }
   GraphicsFromHDC(hdc) {
       DllCall("gdiplus\GdipCreateFromHDC", "Ptr", hdc, "PtrP", pGraphics)
       return pGraphics
   }
   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)
   }
   DeleteGraphics(pGraphics) {
      return DllCall("gdiplus\GdipDeleteGraphics", "Ptr", pGraphics)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}

Зато очень шустро рисует GdipDrawImageRectI, но не поддерживает WS_EX_COMPOSITED | WS_EX_LAYERED, и размазывает области с прозрачностью:

#NoEnv
SetBatchLines, -1

filePath := "D:\OneDrive\Scripts\GDI+\Gif\gif files\girl.gif"

AnimateGif.Gifs := {}
; exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
; Gui, New, +E%exStyles%
Gui, Margin, 5, 5
GuiW := 12 + 5 ; borders + margin
Loop 20 {
   if (A_Index > 1) {
      GuiW += W + 5
      if (GuiW < A_ScreenWidth)
         x := "x+5", y := "yp"
      else
         x := "xm", y := "y+5", GuiW := 12 + 5 + W
   }
   Gui, Add, Pic, % "hwndhGif" . A_Index . " " . (A_Index = 1 ? "" : x . " " . y), % "HBITMAP:" . LoadPicture(filePath, "GDI+")
   if (A_Index = 1) {
      WinGetPos,,, W,, % "ahk_id" hGif%A_Index%
      GuiW += W
   }
   handler := Func("OnClick").Bind(hGif%A_Index%)
   GuiControl, +g, % hGif%A_Index%, % handler
   AnimateGif.Gifs[hGif%A_Index%] := new AnimateGif(filePath, hGif%A_Index%)
   AnimateGif.Gifs[hGif%A_Index%].Play()
}
OnMessage(0x20, "WM_SETCURSOR")
Gui, Show
Return

GuiClose:
   AnimateGif.Gifs := ""
   ExitApp

OnClick(hPic) {
   if AnimateGif.Gifs[hPic].playing
      AnimateGif.Gifs[hPic].Pause()
   Else
      AnimateGif.Gifs[hPic].Play()
}

WM_SETCURSOR(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 AnimateGif.Gifs.HasKey(wp)
      Return DllCall("SetCursor", "Ptr", hCursor)
}

class AnimateGif
{
   __New(gifFile, hPic, cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
      this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, hPic, width, height, cycle)
   }
   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()
   }
   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, hPic, width, height, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.G := this.GDIp.GraphicsFromHwnd(hPic)
         this.width := width
         this.height := height
         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]
         }
         this.DrawFrame(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
         }
      }
      DrawFrame(idx) {
         this.GDIp.ImageSelectActiveFrame(this.pBitmap, this.pDimensionIDs, idx)
         DllCall("gdiplus\GdipDrawImageRectI", "Ptr", this.G, "Ptr", this.pBitmap, "Int", 0, "Int", 0, "Int", this.width, "Int", this.height)
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         this.Delete("dimensionIDs")
         this.GDIp.DeleteGraphics(this.G)
      }
   }
}

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)
   }
   GraphicsFromHwnd(hwnd) {
      DllCall("Gdiplus\GdipCreateFromHWND", "Ptr", hwnd, "PtrP", pGraphics)
      Return pGraphics
   }
   DeleteGraphics(pGraphics) {
      return DllCall("gdiplus\GdipDeleteGraphics", "Ptr", pGraphics)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

31

Re: AHK: Текст поверх GIF

А зачем ты каждый раз создаешь CachedBitmap, отрисовываешь, удаляешь, а не до запуска скрипта всех их создаешь и потом при необходимости отрисовываешь?

32

Re: AHK: Текст поверх GIF

Ну, на практике я так делать бы не стал — более, чем в 10 раз увеличивается расход памяти. Но попробовал:

#NoEnv
SetBatchLines, -1

filePath := "D:\OneDrive\Scripts\GDI+\Gif\gif files\girl.gif"

AnimateGif.Gifs := {}
exStyles := (WS_EX_COMPOSITED := 0x02000000) | (WS_EX_LAYERED := 0x80000)
Gui, New, +E%exStyles%
Gui, Margin, 5, 5
GuiW := 12 + 5 ; borders + margin
Loop 24 {
   if (A_Index > 1) {
      GuiW += W + 5
      if (GuiW < A_ScreenWidth)
         x := "x+5", y := "yp"
      else
         x := "xm", y := "y+5", GuiW := 12 + 5 + W
   }
   Gui, Add, Pic, % "hwndhGif" . A_Index . " " . (A_Index = 1 ? "" : x . " " . y), % "HBITMAP:" . LoadPicture(filePath, "GDI+")
   if (A_Index = 1) {
      WinGetPos,,, W,, % "ahk_id" hGif%A_Index%
      GuiW += W
   }
   handler := Func("OnClick").Bind(hGif%A_Index%)
   GuiControl, +g, % hGif%A_Index%, % handler
   AnimateGif.Gifs[hGif%A_Index%] := new AnimateGif(filePath, Func("PlayGif").Bind(hGif%A_Index%))
   AnimateGif.Gifs[hGif%A_Index%].Play()
}
OnMessage(0x20, "WM_SETCURSOR")
Gui, Show
Return

GuiClose:
   AnimateGif.Gifs := ""
   ExitApp

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

OnClick(hPic) {
   if AnimateGif.Gifs[hPic].playing
      AnimateGif.Gifs[hPic].Pause()
   Else
      AnimateGif.Gifs[hPic].Play()
}

WM_SETCURSOR(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 AnimateGif.Gifs.HasKey(wp)
      Return DllCall("SetCursor", "Ptr", hCursor)
}

class AnimateGif
{
   __New(gifFile, UserFunc := "", cycle := true) {
      this.GDIp := new GDIplus
      this.pBitmap := this.GDIp.CreateBitmapFromFile(gifFile)
      this.GDIp.GetImageDimensions(this.pBitmap, width, height)
      this.width := width
      this.height := height
      this.Frames := new this._FramesHandling(this.GDIp, this.pBitmap, width, height, UserFunc, cycle)
   }
   Play() {                    ; UserFunc will be called with two params: currentFrameIdx and hBitmap
      this.playing := true     ; user is responsible for deleting hBitmap
      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, width, height, userFunc, cycle) {
         this.GDIp := GDIp
         this.pBitmap := pBitmap
         this.hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
         this.DC := new CompatibleDC(this.hDC, width, height)
         this.G := this.GDIp.GraphicsFromHDC(this.DC.hCDC)
         this.userFunc := userFunc
         this.cycle := cycle
         this.GetFrameCount()
         this.GetFrameDelay()
         this.CreateBitmapsArray()
         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
         }
      }
      CreateBitmapsArray() {
         this.CachedBitmaps := []
         Loop % this.frameCount {
            this.GDIp.ImageSelectActiveFrame(this.pBitmap, this.pDimensionIDs, A_Index - 1)
            DllCall("gdiplus\GdipCreateCachedBitmap", "Ptr", this.pBitmap, "Ptr", this.G, "PtrP", pCachedBitmap)
            this.CachedBitmaps[A_Index - 1] := pCachedBitmap
         }
      }
      GetFrame(idx) {
         DllCall("gdiplus\GdipDrawCachedBitmap", "Ptr", this.G, "Ptr", this.CachedBitmaps[idx], "Int", 0, "Int", 0)
         Return this.DC.hCBM
      }
      Clear() {
         this.playing := false
         timer := this._PlayTimer
         SetTimer, % timer, Delete
         Loop % this.frameCount
            DllCall("gdiplus\GdipDeleteCachedBitmap", "Ptr", this.CachedBitmaps[A_Index - 1])
         this.Delete(CachedBitmaps)
         this.Delete("dimensionIDs")
         this.GDIp.DeleteGraphics(this.G)
         this.DC := ""
         DllCall("ReleaseDC", this.hDC)
      }
   }
}

class CompatibleDC
{
   __New(hDC, w, h)  {
      this.hCDC := DllCall("CreateCompatibleDC", Ptr, hDC, Ptr)
      this.hCBM := DllCall("CreateCompatibleBitmap", Ptr, hDC, Int, w, Int, h, Ptr)
      this.oBM := DllCall("SelectObject", Ptr, this.hCDC, Ptr, this.hCBM, Ptr)
   }
   
   __Delete()  {
      DllCall("SelectObject", Ptr, this.hCDC, Ptr, this.oBM, Ptr)
      DllCall("DeleteDC", Ptr, this.hCDC)
      DllCall("DeleteObject", Ptr, this.hCBM)
   }
}

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
   }
   GraphicsFromHDC(hdc) {
       DllCall("gdiplus\GdipCreateFromHDC", "Ptr", hdc, "PtrP", pGraphics)
       return pGraphics
   }
   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)
   }
   DeleteGraphics(pGraphics) {
      return DllCall("gdiplus\GdipDeleteGraphics", "Ptr", pGraphics)
   }
   DisposeImage(pBitmap) {
      Return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}

Действительно, стало чуть поживее. Но всё равно медленнее, чем GdipDrawImageRectI, и, как оказалось, тоже размазывает области с прозрачностью.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

33

Re: AHK: Текст поверх GIF

Я бы на практике вообще не стал бы пихать много гифов в одно окно.
Либо использовал предназначенные для этого контролы типа WebControl, либо отрисовывал бы в дочерних окнах.

34

Re: AHK: Текст поверх GIF

На практике — я имею в виду на продажу. Поэтому решение с дочерними layered windows отпадает, как не универсальное, решение с предзагрузкой, думаю, тоже, как отъедающее слишком много памяти. Пришлось бы выбирать из оставшихся, тут в зависимости от конкретных условий задачи.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

35 (изменено: Malcev, 2021-06-18 11:47:02)

Re: AHK: Текст поверх GIF

Поэтому решение с дочерними layered windows отпадает, как не универсальное

Почему не универсальное?

решение с предзагрузкой, думаю, тоже, как отъедающее слишком много памяти

У меня отъедает всего в 3 раза больше, чем без предзагрузки.
Но цифры смешные по сравнению с тем сколько кушает любой современный броузер.
Насчет одного окна - попробуй запихать 100 гифов в одно окно и посмотри на тормоза.

36

Re: AHK: Текст поверх GIF

Так скрипт для этого и сделан — пробовать запихнуть много гифов, я даже больше пробовал.
Браузер — это многофункциональная тяжелая программа, тут расход памяти хотя бы оправдан, а мы просто картинки показываем.
Как почему? На семёрке это не работает.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

37

Re: AHK: Текст поверх GIF

Так скрипт для этого и сделан — пробовать запихнуть много гифов, я даже больше пробовал.

Попробуй запихнуть 100 этих гифов и подвигать окно.
https://gifyu.com/image/14sf

teadrinker пишет:

Как почему? На семёрке это не работает.

Многие производители софта для таких случаев пишут системные требования.
Ты бы еще XP вспомнил.

38

Re: AHK: Текст поверх GIF

Да тоже пробовал, да, подтормаживает.
Про семерку обсуждали же уже, в России порядка 30% пользователей.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

39

Re: AHK: Текст поверх GIF

Про семерку, когда говорили, ты говорил, что у тебя клиенты есть, которые сидят на win7.
Поэтому, наверное, при конкретном заказе есть резон их операционную систему поддержать.
Но изготавливая новый софт вгонять себя в рамки из-за устаревшей системы... Ну имхо то еще развлечение.

40

Re: AHK: Текст поверх GIF

А зачем терять покупателей? Просто для разных систем могут быть разные версии.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

41

Re: AHK: Текст поверх GIF

Ну наверное это разумно при условии, что вообще будут покупать. Особенно в России, где практически любой популярный продукт сразу же ломается и выставляется в Варезе на рубоарде.

42

Re: AHK: Текст поверх GIF

Я собственноручно никогда продажами не занимался, но заказчики как-то умудряются продавать.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

43

Re: AHK: Текст поверх GIF

А можешь скинуть ссылку на какую-нибудь программу?

44

Re: AHK: Текст поверх GIF

Вряд ли тебе будет интересно, там для игр в основном. Например, к этому имею отношение. Но моя работа непосредственно со взаимодействием с игрой не связана.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder