Can you help me run a "PixelGetColor" script a bit faster? Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 12:28

Using PixelGetColor on a png image with a 15x15 crossword puzzle grid, I'm getting the colors of all 225 squares (White\Black).
The script takes 3.75 seconds to run, and I need help refining the code to make it faster.
I have to run this on 10,000 png files, and at its current speed, and it would take over 10 hours.

Image

I haven't tried to use PixelSearch or ImageSearch. PixelGetColor seemed most appropriate for this, correct me if I'm wrong.

I have already tried optimizing my code as you can see in the beginning of the script below, but it did not improve the performance. You can see the code below. Maybe using an object array instead of a string?

To test my code, simply adjust the FirstBoxPosX and FirstBoxPosY values to fit your monitor. Since I'm using CoordMode, Pixel, Screen, you might also have to adjust the BoxSize value so it jumps to next box correctly, depending how much you scale the png on your screen. I used "Windows Spy" to find the values.

p.s. Using Window or Client in CoordMode might eliminate having to maintain the PNG at the same location and scale for the script to work. Any ideas? (Maybe if I convert the PNG to a BMP, the code would work better with a bitmap?)

I appreciate all of your help!



Here's the grid:

Image

Here's the output:

Image

Here's the code:

Code: Select all

#NoEnv
#KeyHistory 0
#NoTrayIcon
#SingleInstance Force ;Skips the dialog box and replaces the old instance automatically.
ListLines Off
Process, Priority, , A
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SetWinDelay, -1
SetControlDelay, -1
SendMode Input
;OPTIMIZATIONS END

PuzSize:= 15
BoxSize:= 35
FirstBoxPosX:= 2618
FirstBoxPosY:= 542
StartMidBoxX:= FirstBoxPosX + (BoxSize / 2)
StartMidBoxY:= FirstBoxPosY + (BoxSize / 2)
CurrX:= StartMidBoxX
CurrY:= StartMidBoxY

StartTime := A_TickCount
CoordMode, Pixel, Screen
Loop % PuzSize
{
    Loop % PuzSize
    {
        PixelGetColor, color, %CurrX%, %CurrY%
        colors .= color
        CurrX += BoxSize
    }
    CurrY += BoxSize
    CurrX:= StartMidBoxX
    colors .= "`n"
}
colors:= StrReplace(colors, "0xFCFEFC", " ⬜ ")
colors:= StrReplace(colors, "0x040204", " ⬛ ")

ElapsedTime := A_TickCount - StartTime
MsgBox, %ElapsedTime% milliseconds have elapsed.
msgbox % colors
ExitApp
Last edited by zvit on 02 Jul 2022, 13:51, edited 1 time in total.

User avatar
Spawnova
Posts: 554
Joined: 08 Jul 2015, 00:12
Contact:

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by Spawnova » 02 Jul 2022, 13:32

PixelGetColor is pretty slow, I can give you an example using my scanning class which should be significantly faster, although atm it only works if the image is found on the primary monitor.

Code: Select all

#NoEnv
#SingleInstance Force
SetBatchLines, -1
coordmode,mouse,screen

;download the class from github if it's not present
ifnotexist,ShinsImageScanClass.ahk
{
	urldownloadtofile,https://raw.githubusercontent.com/Spawnova/ShinsImageScanClass/main/ShinsImageScanClass.ahk,ShinsImageScanClass.ahk
	reload
}

#include *i ShinsImageScanClass.ahk

scan := new ShinsImageScanClass() ;only works on primary monitor atm
return


;hover mouse over the center of the top left square and press f1 to test
f1::
scan.AutoUpdate := 0
mousegetpos,x,y ;get mouse x y for starting box
pWidth := 15
pHeight := 15
BoxSize:= 35
CurrX:= x
CurrY:= y

puzzle := []

StartTime := A_TickCount
scan.Update() ;update image buffer once

Loop % pHeight {
	py := a_index
    Loop % pWidth {
		px := a_index
		puzzle[px,py] := scan.PixelPosition(0x000000,CurrX + ((px-1)*BoxSize),CurrY + ((py-1)*BoxSize),10) ;1 if black (10 variance), 0 otherwise
    }
}

;construct the string from the array values
colors := ""
Loop % pHeight {
	py := a_index
    Loop % pWidth {
		px := a_index
		colors .= (puzzle[px,py] = 1 ? " B " : " . ")
	}
	colors .= "`n"
}

scan.AutoUpdate := 1

ElapsedTime := A_TickCount - StartTime
MsgBox, %ElapsedTime% milliseconds have elapsed.
msgbox % colors

ExitApp
Does it have to be done from the screen though? If not you could automate all images in a folder by loading them into memory and scanning one by one, would be even faster if so

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 13:40

No, I don't need them to be on the screen at all; loading from memory would be great. Why are YOU loading from memory in your script? Do you have a memory load script example?

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 13:47

OMG! Your class is fast!!

Image

Can you direct me how to do this by what you said - loading into memory?
I'll research it, just point me to the documentary I need.

User avatar
Xtra
Posts: 2750
Joined: 02 Oct 2015, 12:15

Re: Can you help me run a "PixelGetColor" script a bit faster?  Topic is solved

Post by Xtra » 02 Jul 2022, 13:53

Need GDIP for this:

Code: Select all

#NoEnv
#KeyHistory 0
#SingleInstance Force
ListLines Off
Process, Priority,, A
SetBatchLines, -1
OnExit, Exit

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

PuzzleSize:= 15, OffsetPos:= 35

Loop, Files, % A_ScriptDir . "\Images\*.png"
{
	; StartTime := A_TickCount
	
	if !(pBitmap := Gdip_CreateBitmapFromFile(A_LoopFileLongPath)) {
		FileAppend, % A_LoopFileLongPath . "`r`n", error_log.txt
		continue
	}
	
	E1 := Gdip_LockBits(pBitmap, 0, 0, Gdip_GetImageWidth(pBitmap), Gdip_GetImageHeight(pBitmap), Stride, Scan0, BitmapData)
	
	boxX := boxY := 1
	
	Loop, % PuzzleSize {
		Loop, % PuzzleSize {
			output .= Gdip_GetLockBitPixel(Scan0, boxX, boxY, Stride)
			boxX += OffsetPos
		}
		output .= "`n", boxX := 1, boxY += OffsetPos
	}
	FileAppend, % RTrim(StrReplace(StrReplace(output, "4294770428", " W "), "4278452740", " B "),"`n"), % StrReplace(A_LoopFileName, ".png", ".txt")
	Gdip_UnlockBits(pBitmap, BitmapData), Gdip_DisposeImage(pBitmap), output := ""
	
	; MsgBox, % (A_TickCount - StartTime) " milliseconds have elapsed."
}

Exit:
Gdip_Shutdown(pToken)
ExitApp

#Include Gdip.ahk
0-16ms
Something you can play with have fun.

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 14:22

I had Gdip already, so I included it.

Please explain this line:

Code: Select all

FileAppend, % RTrim(StrReplace(StrReplace(output, "4294770428", " W "), "4278452740", " B "),"`n"), % StrReplace(A_LoopFileName, ".png", ".txt")
What's the .txt about, and what file are you appending - the "output" bitmap file? It seems as if the "output" was created during the loops. Explain please.

Also, I added msgbox % output after the FileAppend line, but it's giving the wrong results. Spawnova's class didn't have this issue because he's using PixelPosition which he gave a 10 varience in color.

For some reason, the StrReplace is not working.

This is the output I'm getting:

Image

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 14:31

I'd still like to know what the FileAppend is, but I removed it and changed the code to this and it works: (Were you trying to save the data to a txt file with the same name as the png? The script didn't create one.)

Code: Select all

    MsgBox, % (A_TickCount - StartTime) " milliseconds have elapsed."
    output:= RTrim(StrReplace(StrReplace(output, "4294770428", " ⬜ "), "4278452740", " ⬛ "),"`n")
    msgbox % output
Here's the speed and output! 0 milliseconds!!!

Image

Image

User avatar
Spawnova
Posts: 554
Joined: 08 Jul 2015, 00:12
Contact:

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by Spawnova » 02 Jul 2022, 14:34

Ah xtra beat me to it lol :D

Well anyway, here's another method using compiled c++ =P

Code: Select all

setbatchlines,-1

dir := "C:\Users\Shin\Desktop\images"

;start gdip
if (!DllCall("GetModuleHandle", "str", "gdiplus", "Ptr")) {
	DllCall("LoadLibrary", "str", "gdiplus")
	VarSetCapacity(gsi, 24, 0)
	NumPut(1,gsi,0,"uint")
	DllCall("gdiplus\GdiplusStartup", "Ptr*", token, "Ptr", &gsi, "Ptr", 0)
}

count := 0

;loop through images, allows sub directories
loop,files,%dir%\*.*,R
{
	if (RegExMatch(A_LoopFileLongPath,"\.jpeg$|\.jpg$|\.png$|\.gif$|\.bmp$")) {
		count++
		
		;get the data here
		a := ConvertImage(A_LoopFileLongPath,35) ;35=gridSize, returns an array in this format array := {rows:ROWS, cols:COLUMNS, data:[]}  
		
		;do stuff here
		
		;format string message box
		str := "Image: " count "`n" A_LoopFileLongPath "`n`n"
		Loop % a.cols {
			py := a_index
			Loop % a.rows {
				px := a_index
				str .= (a.data[px,py] = 1 ? " B " : " . ")
			}
			str .= "`n"
		}
		msgbox % str
		
	}
}
msgbox % "Finished processing " count " images!"
exitapp



ConvertImage(imagePath,gridSize) {
	static mc := 0
	if (mc = 0)
		mc := _mcode("VVdWU4PsDIt0JDSLTCQsifDR+IXJfmGLVCQohdJ+WYtcJCQDXCQog/4BdVkx7TH/MfaNtgAAAACLRCQgi1QkJI0MuAHqjXYAiwEl////AD0hIiIAfwPGAgGDwgGDwQQ52nXlg8YBA1wkKAN8JDADbCQoOXQkLHXAg8QMuAEAAABbXl9dw4t8JDCLbCQwxwQkAAAAAA+v/sHmAg+v6Il8JASLfCQgjQSHMf+JRCQIZpCLRCQIi1QkJI0MqAH6jXYAiwEl////AD0hIiIAfwPGAgGDwgEB8TnadeaDBCQBA1wkKIsEJAN8JCgDbCQEOUQkLHW9g8QMuAEAAABbXl9dww==|QVVBVFVXVlNIY0QkYEiJ04nCRInG0fpFhcl+YkWFwH5dg/gBdWZIY3wkWEmJyjHSRTHARI1e/0jB5wIPH0QAADHA6wcPH0AASInIQYsMgoHh////AIH5ISIiAH8KjQwCSGPJxgQLAUiNSAFMOdh12EGDwAFJAfoB8kU5wXXCuAEAAABbXl9dQVxBXcNEi0QkWItsJFhFMdtEjSw2RA+vwkhj0g+v6E1jwEwBwkhj7UGJ8EyNJJFIweUCSI0UhQAAAABmDx9EAABEicdNieIp94n5Zg8fRAAAQYsCJf///wA9ISIiAH8HSGPBxgQDAYPBAUkB0kQ5wXXfQYPDAUWNRD0ASQHsRTnZdb64AQAAAFteX11BXEFdw5CQkJCQkJCQ")

	DllCall("gdiplus\GdipCreateBitmapFromFile", "ptr", &imagePath, "Ptr*", bm)
	DllCall("gdiplus\GdipGetImageWidth", "Ptr", bm, "Uint*", w)
	DllCall("gdiplus\GdipGetImageHeight", "Ptr", bm, "Uint*", h)
	VarSetCapacity(r,16,0)
	NumPut(w,r,8,"uint")
	NumPut(h,r,12,"uint")
	VarSetCapacity(bmdata, 32, 0)
	DllCall("Gdiplus\GdipBitmapLockBits", "Ptr", bm, "Ptr", &r, "uint", 3, "int", 0x26200A, "Ptr", &bmdata)
	scan := NumGet(bmdata, 16, "Ptr")
	
	rows := floor(w / gridSize)
	cols := floor(h / gridSize)
	
	varsetcapacity(output,(rows*cols),0)
	dllcall(mc,"ptr",scan,"ptr",&output,"int",rows,"int",cols,"int",w,"int",gridSize)
	
	arr := []
	arr.rows := rows
	arr.cols := cols
	arr.data := []
	loop % cols {
		y := a_index-1
		loop % rows {
			x := a_index-1
			arr.data[x+1,y+1] := numget(output,x+y*rows,"uchar")
		}
	}
	
	DllCall("Gdiplus\GdipBitmapUnlockBits", "Ptr", bm, "Ptr", &bmdata)
	DllCall("gdiplus\GdipDisposeImage", "ptr", bm)
	
	return arr
}


_Mcode(str) {
	s := strsplit(str,"|")
	if (s.length() != 2)
		return
	if (!DllCall("crypt32\CryptStringToBinary", "str", s[(a_ptrsize = 8 ? 2 : 1)], "uint", 0, "uint", 1, "ptr", 0, "uint*", pp, "ptr", 0, "ptr", 0))
		return
	p := DllCall("GlobalAlloc", "uint", 0, "ptr", pp, "ptr")
	if (a_ptrsize = 8)
		DllCall("VirtualProtect", "ptr", p, "ptr", pp, "uint", 0x40, "uint*", op)
	if (DllCall("crypt32\CryptStringToBinary", "str", s[(a_ptrsize = 8 ? 2 : 1)], "uint", 0, "uint", 1, "ptr", p, "uint*", pp, "ptr", 0, "ptr", 0))
		return p
	DllCall("GlobalFree", "ptr", p)
}

/*
c++ source
int function(int* p, char* r, int rows, int cols, int w, int gridSize) {
	
	int sx =gridSize>>1;
	int sy = sx;
	
	for(int y = 0;y<cols;y++)
		for(int x = 0;x<rows;x++)
			if ((p[sx+(x*gridSize)+((sy+(y*gridSize))*w)] & 0xFFFFFF) < 0x222222)
				r[x+y*rows] = 1;

	return 1;
}
*/

User avatar
Xtra
Posts: 2750
Joined: 02 Oct 2015, 12:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by Xtra » 02 Jul 2022, 14:51

Output was saved to the same named file as the png image.

The file = (based on your OP before you edited)

Code: Select all

 W  W  W  W  B  W  W  W  W  B  W  W  W  W  W 
 W  W  W  W  B  W  W  W  W  B  W  W  W  W  W 
 W  W  W  W  B  W  W  W  W  W  W  W  W  W  W 
 B  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
 B  B  B  W  W  W  B  W  W  W  W  W  W  W  W 
 W  W  W  W  W  W  W  W  W  W  W  W  B  B  B 
 W  W  W  B  W  W  W  W  B  B  W  W  W  W  W 
 W  W  W  W  W  W  W  B  W  W  W  W  W  W  W 
 W  W  W  W  W  B  B  W  W  W  W  B  W  W  W 
 B  B  B  W  W  W  W  W  W  W  W  W  W  W  W 
 W  W  W  W  W  W  W  W  B  W  W  W  B  B  B 
 W  W  W  W  B  W  W  W  W  B  W  W  W  W  B 
 W  W  W  W  W  W  W  W  W  W  B  W  W  W  W 
 W  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
 W  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
Yes machine code will be even faster.

User avatar
Xtra
Posts: 2750
Joined: 02 Oct 2015, 12:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by Xtra » 02 Jul 2022, 14:56

For some reason, the StrReplace is not working.
That is because output does not contain the StrReplace() changes.

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 15:02

lol @Spawnova. I will test your code with 2000 images, to see which is faster (since both of them are less than a millisecond per image :-) )
But it's not working correctly. str is not showing the grid, here's the output:

Image

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 15:04

Xtra wrote:
02 Jul 2022, 14:51
Output was saved to the same named file as the png image.

The file = (based on your OP before you edited)

Code: Select all

 W  W  W  W  B  W  W  W  W  B  W  W  W  W  W 
 W  W  W  W  B  W  W  W  W  B  W  W  W  W  W 
 W  W  W  W  B  W  W  W  W  W  W  W  W  W  W 
 B  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
 B  B  B  W  W  W  B  W  W  W  W  W  W  W  W 
 W  W  W  W  W  W  W  W  W  W  W  W  B  B  B 
 W  W  W  B  W  W  W  W  B  B  W  W  W  W  W 
 W  W  W  W  W  W  W  B  W  W  W  W  W  W  W 
 W  W  W  W  W  B  B  W  W  W  W  B  W  W  W 
 B  B  B  W  W  W  W  W  W  W  W  W  W  W  W 
 W  W  W  W  W  W  W  W  B  W  W  W  B  B  B 
 W  W  W  W  B  W  W  W  W  B  W  W  W  W  B 
 W  W  W  W  W  W  W  W  W  W  B  W  W  W  W 
 W  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
 W  W  W  W  W  B  W  W  W  W  B  W  W  W  W 
Yes machine code will be even faster.
I tried your code before editing it, and don't get a txt file. The only thing I changed was the included file to Gdip_All.ahk because that's the version I have. Is that why?

BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by BoBo » 02 Jul 2022, 15:09

Spoiler

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 15:09

Ok, got it. Just had to add my path before A_LoopFileName :-)

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 15:17

@Spawnova I'm just waiting to hear why the str is not outputting the correct matrix, so I can test this.

Image

User avatar
zvit
Posts: 224
Joined: 07 Nov 2017, 06:15

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by zvit » 02 Jul 2022, 15:35

@Xtra Ok, so your code took 10 seconds to do 10,000 images! That's a little better than my 11-hour code... :lol:

Image

User avatar
Spawnova
Posts: 554
Joined: 08 Jul 2015, 00:12
Contact:

Re: Can you help me run a "PixelGetColor" script a bit faster?

Post by Spawnova » 02 Jul 2022, 16:55

@zvit I'm not really sure, it worked fine for me on both 32/64 bit, looks like the the array returned by ConvertImage() is empty, no idea why that would be. Since your only going over about 200 pixels per image the performance would be nearly the same as Xtra's version anyway =P

Post Reply

Return to “Ask for Help (v1)”