1

Тема: AHK: GDIplus Image Rotate + Image Search

Ищу способы как сделать поиск изображения, которое может вращаться. На ум пришло два варианта:
1) сделать 360 вариантов иконки для каждого градуса и искать циклом. В теории должно работать, но явно есть способы получше.
2) использовать библиотеку GDI+ для поворота изображения ну и Gdip_ImageSearch в комплекте.

Попробовал 2-ой вариант. Не понимаю как совместить эти два модуля. По отдельности всё работает.

Gdip.Rotate

+ открыть спойлер

; gdi+ ahk tutorial 10 written by tic (Tariq Porter)
; Requires Gdip.ahk either in your Lib folder as standard library or using #Include
;
; Tutorial to rotate, flip or mirror an image

#SingleInstance, Force
#NoEnv
SetBatchLines, -1

; Uncomment if Gdip.ahk is not in your standard library
#Include, Gdip.ahk

; Start gdi+
If !pToken := Gdip_Startup()
{
	MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
	ExitApp
}
OnExit, Exit

; Create a gui where we can select the file we want to rotate, the angle to rotate it by and whether we want to flip it
Gui, 1: +ToolWindow +AlwaysOnTop
Gui, 1: Add, Edit, x10 y10 w300 vFile, 
Gui, 1: Add, Button, x+10 yp+0 w75 gFileSelect Default, &File...
Gui, 1: Add, Button, x+10 yp+0 w75 gGo, &Go

; Here is the slider allowing rotation between 0 and 360 degrees
Gui, 1: Add, Slider, x10 y+10 w300 Tooltip vAngle Range0-360, 0

; Create 2 checkboxes, to select whether we want to flip it horizontally or vertically
Gui, 1: Add, CheckBox, x+10 yp+0 vHorizontal, Flip horizontally
Gui, 1: Add, CheckBox, x+10 yp+0 vVertical, Flip vertically

Gui, 1: Show, x0 y0 AutoSize

; Create a layered window (+E0x80000 : must be used for UpdateLayeredWindow to work!) that is always on top (+AlwaysOnTop), has no taskbar entry or caption
; This will be used as the 2nd gui so that we can show our image on it
Gui, 2: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs

; Show the window
Gui, 2: Show, NA

; Get a handle to this window we have created in order to update it later
hwnd2 := WinExist()

; By placing this OnMessage here. The function WM_LBUTTONDOWN will be called every time the user left clicks on the gui. This can be used for dragging the image
OnMessage(0x201, "WM_LBUTTONDOWN")
Return

;#####################################################################

Go:

; Submit the variables to see the degress to rotate by and whether to flip the image
Gui, 1: +OwnDialogs
Gui, 1: Submit, NoHide

; If the file in the edit field is not a valid image then return
If !pBitmap := Gdip_CreateBitmapFromFile(File)
Return

; We should get the width and height of the image, in case it is too big for the screen then we can resize it to fit nicely
OriginalWidth := Gdip_GetImageWidth(pBitmap), OriginalHeight := Gdip_GetImageHeight(pBitmap)
Ratio := OriginalWidth/OriginalHeight

; If the image has a width larger than 1/2 of the width of the screen or height larger than 1/2 the screen, then we will resize it to be half of the screen
If (OriginalWidth >= A_ScreenWidth//2) || (OriginalHeight >= A_ScreenHeight//2)
{
	If (OriginalWidth >= OriginalHeight)
	Width := A_ScreenWidth//2, Height := Width*(1/Ratio)
	Else
	Height := A_ScreenHeight//2, Width := Height*Ratio
}
Else
Width := OriginalWidth, Height := OriginalHeight

; Width and Height now contain the new dimensions the image on screen will be

; When rotating a square image, then the bitmap or canvas will need to be bigger as you can imagine that once rotated then a triangle will be wider than a square
; We need to know the new dimensions of the image
; With Gdip_GetRotatedDimensions we can plug in the width and height of the image, and the angle it is to be rotated by
; The last 2 parameters are the variables in which tio store the new width and height of the rotated image
; RWidth and RHeight now contain the dimensions of the rotated image
Gdip_GetRotatedDimensions(Width, Height, Angle, RWidth, RHeight)

; We rotate an image about the top left corner of the image, however this will result in the image moving off the canvas
; We can use Gdip_GetRotatedTranslation to find how much the image should be 'shifted' by in the x and y coordinates in order for it to be back on the canvas
; As with the above function, we plug in the width, height and angle to rotate by
; The function will then make the last 2 parameters the x and y translation (this is the distance in pixels the image must be shifted by)
; xTranslation and yTranslation now contain the distance to shift the image by
Gdip_GetRotatedTranslation(Width, Height, Angle, xTranslation, yTranslation)

; We will now create a gdi bitmap to display the rotated image on the screen (as mentioned previously we must use a gdi bitmap to display things on the screen)
hbm := CreateDIBSection(RWidth, RHeight)

; Get a device context compatible with the screen
hdc := CreateCompatibleDC()

; Select the bitmap into the device context
obm := SelectObject(hdc, hbm)

; Get a pointer to the graphics of the bitmap, for use with drawing functions,
; and set the InterpolationMode to HighQualityBicubic = 7 so that when resizing the image still looks good
G := Gdip_GraphicsFromHDC(hdc), Gdip_SetInterpolationMode(G, 7)

; We can now shift our graphics or 'canvas' using the values found with Gdip_GetRotatedTranslation so that the image will be drawn on the canvas
Gdip_TranslateWorldTransform(G, xTranslation, yTranslation)

; We can also rotate the graphics by the angle we desire
Gdip_RotateWorldTransform(G, Angle)

; If we wish to flip the image horizontally, then we supply Gdip_ScaleWorldTransform(G, x, y) with a negative x transform
; We multiply the image by the x and y transform. So multiplying a direction by -1 will flip it in that direction and 1 will do nothing
; We must then shift the graphics again to ensure the image will be within the 'canvas'
; You can see that if we wish to flip vertically we supply a negative y transform
If Horizontal
Gdip_ScaleWorldTransform(G, -1, 1), Gdip_TranslateWorldTransform(G, -Width, 0)
If Vertical
Gdip_ScaleWorldTransform(G, 1, -1), Gdip_TranslateWorldTransform(G, 0, -Height)


; As you will already know....we must draw the image onto the graphics. We want to draw from the top left coordinates of the image (0, 0) to the top left of the graphics (0, 0)
; We are drawing from the orginal image size to the new size (this may not be different if the image was not larger than half the screen)
Gdip_DrawImage(G, pBitmap, 0, 0, Width, Height, 0, 0, OriginalWidth, OriginalHeight)

; Even though this is not necessary in this scenario, you should always reset the transforms set on the graphics. This will remove any of the rotations
Gdip_ResetWorldTransform(G)

; We will update the hwnd  with the hdc of our gdi bitmap. We are drawing it at the new width and height and in the centre of the screen
UpdateLayeredWindow(hwnd2, hdc, (A_ScreenWidth-RWidth)//2, (A_ScreenHeight-RHeight)//2, RWidth, RHeight)

; As always we will dispose of everything we created
; So we select the object back into the hdc, the delete the bitmap and hdc
SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc)
; We will then dispose of the graphics and bitmap we created
Gdip_DeleteGraphics(G), Gdip_DisposeImage(pBitmap)
Return

;#####################################################################

; This is a simple subroutine to select images and change the editbox with the image teh user selects
FileSelect:
Gui, 1: +OwnDialogs
Gui, 1: Submit, NoHide

FileSelectFile, File,,, Select image
If Errorlevel
Return
GuiControl,, File, %File%
Return

;#####################################################################

; This is the function to allow the user to drag the image drawn on the screen (this being gui 2)
WM_LBUTTONDOWN()
{
	If (A_Gui = 2)
	PostMessage, 0xA1, 2
}

;#####################################################################

; If the user closes the gui or closes the program then we want to shut down gdi+ and exit the application
Esc::
GuiClose:
Exit:
Gdip_Shutdown(pToken)
ExitApp
Return

https://i.ibb.co/9sbwRGK/rotate.jpg

Gdip_ImageSearch


bmpHaystack := Gdip_CreateBitmapFromFile("test2.png")
bmpNeedle := Gdip_CreateBitmapFromFile("test.png")
RET := Gdip_ImageSearch(bmpHaystack,bmpNeedle,LIST,0,0,0,0,50,0xFFFFFF,1,0)
Gdip_DisposeImage(bmpHaystack)
Gdip_DisposeImage(bmpNeedle)
Gdip_Shutdown(pToken)
MsgBox, % "Returned: " RET "`n`n" LIST

Тут в функцию надо передать изображение которое ищем "bmpNeedle". Если его передавать из файла как выше - то всё ок.
Пробовал передавать bmpNeedle := Gdip_RotateWorldTransform(G, Angle), но ругается. Не знаю как передать финальный объект после изменения угла.
Может есть у кого какие мысли на этот счёт?

GD

2

Re: AHK: GDIplus Image Rotate + Image Search

Вместо

; We will now create a gdi bitmap to display the rotated image on the screen (as mentioned previously we must use a gdi bitmap to display things on the screen)
hbm := CreateDIBSection(RWidth, RHeight)

; Get a device context compatible with the screen
hdc := CreateCompatibleDC()

; Select the bitmap into the device context
obm := SelectObject(hdc, hbm)

создавать Bitmap напрямую с Gdip_CreateBitmap(), далее Graphics получать из неё, используя Gdip_GraphicsFromImage(). Созданная Bitmap и будет результирующей. Правда, не тестировал пока.

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

3

Re: AHK: GDIplus Image Rotate + Image Search

teadrinker Поворачивает изображение, но совпадений нет. Если без поворота - то совпадение есть.


pToken := Gdip_Startup()
pBitmapA := Gdip_CreateBitmapFromFile("test.png")
bmpHaystack := Gdip_CreateBitmapFromFile("test2.png")

loop, 360
{
	pBitmapB := Gdip_RotateBitmap(pBitmapA, A_index, 0)
	Gdip_GetImageDimensions(pBitmapA, WidthA, HeightA), Gdip_GetImageDimensions(pBitmapB, WidthB, HeightB)
	RET := Gdip_ImageSearch(bmpHaystack, pBitmapB, LIST, 1700, 135, 1840, 240, 50, 0xFFFFFF, 1, 0)
	tooltip, % "Returned: " RET " Count: " A_index "`n`n" LIST
	if (RET != 0)
		ss := 1
	sleep, 10
}

Gdip_DisposeImage(bmpHaystack)
Gdip_DisposeImage(bmpNeedle)
Gdip_DisposeImage(pBitmapA)
Gdip_DisposeImage(pBitmapB)
Gdip_Shutdown(pToken)
	
msgbox, %ss%

return

Gdip_RotateBitmap(pBitmap, Angle, Dispose=1) 
{ 
	Gdip_GetImageDimensions(pBitmap, Width, Height)
	Gdip_GetRotatedDimensions(Width, Height, Angle, RWidth, RHeight)
	Gdip_GetRotatedTranslation(Width, Height, Angle, xTranslation, yTranslation)

	pBitmap2 := Gdip_CreateBitmap(RWidth, RHeight)
	G2 := Gdip_GraphicsFromImage(pBitmap2), Gdip_SetSmoothingMode(G2, 4), Gdip_SetInterpolationMode(G2, 7)
	Gdip_TranslateWorldTransform(G2, xTranslation, yTranslation)
	Gdip_RotateWorldTransform(G2, Angle)
	Gdip_DrawImage(G2, pBitmap, 0, 0, Width, Height)

	Gdip_ResetWorldTransform(G2)
	Gdip_DeleteGraphics(G2)
	if Dispose
	Gdip_DisposeImage(pBitmap)
	return pBitmap2
}

Exit:
Gdip_Shutdown(pToken)
ExitApp
Return

F12::ExitApp
GD

4

Re: AHK: GDIplus Image Rotate + Image Search

OpenCV.

5

Re: AHK: GDIplus Image Rotate + Image Search

Malcev Уверен что это крутая технология, только для ahk мало информации, если вообще есть. А скорее всего нужен кто-то, кто её адаптирует под ahk. Это даже звучит дорого, не говоря уже о понимании как она будет работать.

GD

6

Re: AHK: GDIplus Image Rotate + Image Search

Не знаю, в чём проблема, но вижу у вас в коде 4 переменные, которые нигде не используются: WidthA, HeightA, WidthB, HeightB.

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

7

Re: AHK: GDIplus Image Rotate + Image Search

Botsy пишет:

Malcev Уверен что это крутая технология, только для ahk мало информации, если вообще есть

Конечно есть, пользуйтесь поиском.
И работать она будет, так как для этого и преднозначена.
Ну и если не можете перенести сами, может стоит посмотреть в сторону питона?
На ахк же свет клином не сошелся.

8

Re: AHK: GDIplus Image Rotate + Image Search

teadrinker А это они использовались для отображения в окне.


Gdip_GetImageDimensions(pBitmapA, WidthA, HeightA), Gdip_GetImageDimensions(pBitmapB, WidthB, HeightB)

Gui 1:Add, Picture, % "x2 y2 w" WidthA " h" HeightA " BackgroundTrans 0xE vPictureA"
Gui 1:Add, Picture, % "x" WidthA+4 " y2 w" WidthB " h" HeightB " BackgroundTrans 0xE vPictureB"

SetBitmap2Pic(pBitmapA, "PictureA"), SetBitmap2Pic(pBitmapB, "PictureB")
Gdip_DisposeImage(pBitmapA)
Gdip_DisposeImage(pBitmapB)
Gdip_Shutdown(pToken)

GuiW := WidthA + WidthB + 6, GuiH := (HeightA > HeightB) ? HeightA : HeightB +4
Gui 1:Show, % "w" GuiW " h" GuiH, Original & Rotated
return

SetBitmap2Pic(pBitmap,ControlID,GuiNum=1) 
{ 
	GuiControlGet, hControl, %GuiNum%:hwnd, %ControlID%
	hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap), SetImage(hControl, hBitmap), DeleteObject(hBitmap) 
	GuiControl, %GuiNum%:MoveDraw, %ControlID% ; repaints the region of the GUI window occupied by the control
}
GD

9

Re: AHK: GDIplus Image Rotate + Image Search

Нашел причину, почему нет совпадений после поворота изображения. Почему-то меняется размер, хотя фактический размер в свойствах одинаковый. У обоих размер 31х29, но если закинуть в фш, то видна разница.
Слава - оригинал, справа - после обработки gdi.
https://i.ibb.co/t4wq89c/rotate.jpg

Даже если попробовать другим способом (нашел на оффе), результат такой же, всё равно картинка уменьшается.


#Include, Gdip.ahk
#Include Gdip_ImageSearch.ahk

pToken := Gdip_Startup()
pBitmapA := Gdip_CreateBitmapFromFile("test.png")
bmpHaystack := Gdip_CreateBitmapFromFile("test2.png")

loop, 1
{
	pBitmapB := Gdip_RotateBitmap_2(pBitmapA, 0)
	RET := Gdip_ImageSearch(bmpHaystack, pBitmapB, LIST, 1700, 135, 1840, 240, 10, 0xFFFFFF, 1, 0)
	tooltip, % "Returned: " RET " Count: " A_index "`n`n" LIST
	if (RET != 0)
		ss := 1
	sleep, 10
	Gdip_SaveBitmapToFile(pBitmapB, "new_test.png")
}
	Gdip_DisposeImage(bmpHaystack)
	Gdip_DisposeImage(pBitmapA)
	Gdip_DisposeImage(pBitmapB)
	Gdip_Shutdown(pToken)
	
msgbox, %ss%

return

Gdip_RotateBitmap_2(pBitmap, OriginRud) 
{ 
w:=h:=0
Gdip_GetImageDimensions(pBitmap,w,h)
Gdip_GetRotatedDimensions(w,h,OriginRud,rw,rh)
rw:=(rw>w?rw:w)
rh:=(rh>h?rh:h)

hbm:=CreateDIBSection(rw,rh), hdc:=CreateCompatibleDC(), obm:=SelectObject(hdc,hbm), G:=Gdip_GraphicsFromHDC(hdc) 

Gdip_TranslateWorldTransform(G, rw//2, rh//2)
Gdip_RotateWorldTransform(G,OriginRud)
Gdip_TranslateWorldTransform(G, -rw//2, -rh//2)
Gdip_DrawImage(G, pBitmap, (rw-w)//2, (rh-h)//2, w, h)
UpdateLayeredWindow(Childhwnd, hdc, 200-rw//2, 200-rh//2, rw, rh)

; new 'canvas' 
pBitmapRotated := Gdip_CreateBitmap(rw, rh)
pGraphicsRotated := Gdip_GraphicsFromImage(pBitmapRotated)

; reapply tranformations and save it
Gdip_TranslateWorldTransform(pGraphicsRotated, rw//2, rh//2)
Gdip_RotateWorldTransform(pGraphicsRotated,OriginRud)
Gdip_TranslateWorldTransform(pGraphicsRotated, -rw//2, -rh//2)
Gdip_DrawImage(pGraphicsRotated, pBitmap, (rw-w)//2, (rh-h)//2, w, h)

SelectObject(hdc,obm)
DeleteObject(hbm)
DeleteDC(hdc)

Gdip_DeleteGraphics(G)
Gdip_DeleteGraphics(pGraphicsRotated)
;Gdip_DisposeImage(pBitmapRotated)

return pBitmapRotated
}
GD

10

Re: AHK: GDIplus Image Rotate + Image Search

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

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