Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 14 May 2022, 14:01

I can add it - Variations are just RGB offsets, right? I was always concerned about proper color spaces, it seems like most people don't care and it would slow down the code.

User avatar
boiler
Posts: 16766
Joined: 21 Dec 2014, 02:44

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by boiler » 14 May 2022, 14:33

Yes, you would just add a check to see if each pixel's r/g/b component is within that range on either side (i.e., plus or minus that number) of the reference color's corresponding component.

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 16 May 2022, 19:08

Higher is better.
---------------------------
Benchmark.ahk
---------------------------
Built-in PixelSearch 25.39 fps
Custom PixelSearch 37.31 fps
Built-in PixelSearch with variation 24.44 fps
Custom PixelSearch with variation 38.13 fps
---------------------------
OK
---------------------------
I don't know why the variation beats the normal pixelsearch.
Normal PixelSearch - https://godbolt.org/z/9v7vzf5az
Variation PixelSearch - https://godbolt.org/z/oocoPndE8


New PixelSearch with variation

Code: Select all

CoordMode, Mouse, Screen

if xy := px2(0x54C845, 50)
   MouseMove xy[1], xy[2]

px2(color, variation := 3) {

   static hdc, hbm, obm, pBits
   if !hdc {
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      VarSetCapacity(bi, 40, 0)              ; sizeof(bi) = 40
         NumPut(       40, bi,  0,   "uint") ; Size
         NumPut(A_ScreenWidth, bi,  4,   "uint") ; Width
         NumPut(-A_ScreenHeight, bi,  8,    "int") ; Height - Negative so (0, 0) is top-left.
         NumPut(        1, bi, 12, "ushort") ; Planes
         NumPut(       32, bi, 14, "ushort") ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", &bi, "uint", 0, "ptr*", pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")
   }

   ; Retrieve the device context for the screen.
   static sdc := DllCall("GetDC", "ptr", 0, "ptr")

   ; Copies a portion of the screen to a new device context.
   DllCall("gdi32\BitBlt"
            , "ptr", hdc, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
            , "ptr", sdc, "int", 0, "int", 0, "uint", 0x00CC0020) ; SRCCOPY

   ; C source code - https://godbolt.org/z/oocoPndE8
   static bin := 0
   if !bin {
      code := (A_PtrSize == 4)
         ? "VYnlVlNRikUQilUcik0gil0ki3UIiEX3ikUUiEX2ikUYiEX1O3UMcyiKRgI6Rfd3GzpF9nIWikYBOkX1dw440HIKigY4yHcEONhzCIPGBOvTi3UMWonwW15dww=="
         : "VlNEilQkOESKXCRAilwkSECKdCRQSInISDnQcyuKSAJEOMF3HUQ4yXIYikgBRDjRdxBEONlyC4oIONl3BUA48XMJSIPABOvQSInQW17D"
      size := StrLen(RTrim(code, "=")) * 3 // 4
      bin := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")
      DllCall("VirtualProtect", "ptr", bin, "ptr", size, "uint", 0x40, "uint*", old:=0)
      DllCall("crypt32\CryptStringToBinary", "str", code, "uint", 0, "uint", 0x1, "ptr", bin, "uint*", size, "ptr", 0, "ptr", 0)
   }

   v := variation
   r := ((color & 0xFF0000) >> 16)
   g := ((color & 0xFF00) >> 8)
   b := ((color & 0xFF))

   ; When doing pointer arithmetic, *Scan0 + 1 is actually adding 4 bytes.
   byte := DllCall(bin, "ptr", pBits, "ptr", pBits + (4 * A_ScreenWidth * A_ScreenHeight)
            , "uchar", Min(r+v, 255)
            , "uchar", Max(r-v, 0)
            , "uchar", Min(g+v, 255)
            , "uchar", Max(g-v, 0)
            , "uchar", Min(b+v, 255)
            , "uchar", Max(b-v, 0)
            , "ptr")

   if (byte == pBits + (4 * A_ScreenWidth * A_ScreenHeight))
      return False

   offset := (byte - pBits) // 4
   return [mod(offset, A_ScreenWidth), offset // A_ScreenWidth]
}
New updated function

Code: Select all

px(color) {

   static hdc, hbm, obm, pBits
   if !hdc {
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      VarSetCapacity(bi, 40, 0)              ; sizeof(bi) = 40
         NumPut(       40, bi,  0,   "uint") ; Size
         NumPut(A_ScreenWidth, bi,  4,   "uint") ; Width
         NumPut(-A_ScreenHeight, bi,  8,    "int") ; Height - Negative so (0, 0) is top-left.
         NumPut(        1, bi, 12, "ushort") ; Planes
         NumPut(       32, bi, 14, "ushort") ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", &bi, "uint", 0, "ptr*", pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")
   }

   ; Retrieve the device context for the screen.
   static sdc := DllCall("GetDC", "ptr", 0, "ptr")

   ; Copies a portion of the screen to a new device context.
   DllCall("gdi32\BitBlt"
            , "ptr", hdc, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
            , "ptr", sdc, "int", 0, "int", 0, "uint", 0x00CC0020) ; SRCCOPY

   ; C source code - https://godbolt.org/z/9v7vzf5az
   static bin := 0
   if !bin {
      code := (A_PtrSize == 4)
         ? "VYnli1UMi00Qi0UIOdBzCTkIdAeDwATr84nQXcM="
         : "SInISDnQcwtEOQB0CUiDwATr8EiJ0MM="
      size := StrLen(RTrim(code, "=")) * 3 // 4
      bin := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")
      DllCall("VirtualProtect", "ptr", bin, "ptr", size, "uint", 0x40, "uint*", old:=0)
      DllCall("crypt32\CryptStringToBinary", "str", code, "uint", 0, "uint", 0x1, "ptr", bin, "uint*", size, "ptr", 0, "ptr", 0)
   }

   ; Lift color to 32-bits if first 8 bits are zero.
   (!(color >> 24)) && color |= 0xFF000000

   ; Pass the width * height, but the size is returned due to C interpreting Scan0 as a integer pointer.
   ; So when doing pointer arithmetic, *Scan0 + 1 is actually adding 4 bytes.
   byte := DllCall(bin, "ptr", pBits, "ptr", pBits + (4 * A_ScreenWidth * A_ScreenHeight), "uint", color, "ptr")
   if (byte == pBits + A_ScreenWidth * A_ScreenHeight * 4)
      return False

   offset := (byte - pBits) // 4
   return [mod(offset, A_ScreenWidth), offset // A_ScreenWidth]
}
Attachments
PixelSearch Benchmark.zip
Please run the benchmark yourself and see what you get.
(29.02 KiB) Downloaded 216 times
Last edited by iseahound on 22 May 2022, 15:24, edited 7 times in total.

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 16 May 2022, 19:10

I just want to thank TeaDrinker for this line of code:

Code: Select all

   ; Lift color to 32-bits if first 8 bits are zero.
   (!(color >> 24)) && color |= 0xFF000000
It's so obvious and so lovely. I used their coding style to help improve my script.

ddt442
Posts: 11
Joined: 04 Jan 2020, 15:02

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by ddt442 » 17 May 2022, 20:08

iseahound wrote:
14 May 2022, 14:01
I can add it - Variations are just RGB offsets, right? I was always concerned about proper color spaces, it seems like most people don't care and it would slow down the code.
Nice...thank you!
And if that is not too much to ask, is it possible you could add search specific area function like built-in pixel search?

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 17 May 2022, 21:29

ddt442 wrote:
17 May 2022, 20:08
add search specific area function like built-in pixel search?
Not at the moment. I think it should be straightforward to modify the function to do this. My main purpose was to optimize the pixel search assembly, and I've accomplished that.

Anyone is free to make their own modifications!

Edit: Oh I just remembered you can actually do this using ImagePut.
https://github.com/iseahound/ImagePut/wiki/PixelSearch-and-ImageSearch

Code: Select all

pic := ImagePutBuffer([0, 200, 500, 500])
xy := pic.PixelSearch2(0x1235FF, 3) ; variation of 3. 
ImageShow(pic) ; or pic.show()
MouseMove xy[1], xy[2]

ddt442
Posts: 11
Joined: 04 Jan 2020, 15:02

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by ddt442 » 18 May 2022, 20:46

iseahound wrote:
17 May 2022, 21:29
ddt442 wrote:
17 May 2022, 20:08
add search specific area function like built-in pixel search?
Not at the moment. I think it should be straightforward to modify the function to do this. My main purpose was to optimize the pixel search assembly, and I've accomplished that.

Anyone is free to make their own modifications!

Edit: Oh I just remembered you can actually do this using ImagePut.
https://github.com/iseahound/ImagePut/wiki/PixelSearch-and-ImageSearch

Code: Select all

pic := ImagePutBuffer([0, 200, 500, 500])
xy := pic.PixelSearch2(0x1235FF, 3) ; variation of 3. 
ImageShow(pic) ; or pic.show()
MouseMove xy[1], xy[2]
Thanks, this tool helps me achieve my goal, and it's fast!

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by hasantr » 22 May 2022, 13:41

I want to verify if the green color in the middle is there. But it throws pixel not found.
I don't know how to create the searched pixel. I take the color with idropper.
GreenDot.png
GreenDot.png (1.17 KiB) Viewed 2757 times

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 22 May 2022, 15:23

Code: Select all

CoordMode, Mouse, Screen

a::
if xy := px(0x19AE82)
   MouseMove xy[1], xy[2]
return

px(color) {

   static hdc, hbm, obm, pBits
   if !hdc {
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      VarSetCapacity(bi, 40, 0)              ; sizeof(bi) = 40
         NumPut(       40, bi,  0,   "uint") ; Size
         NumPut(A_ScreenWidth, bi,  4,   "uint") ; Width
         NumPut(-A_ScreenHeight, bi,  8,    "int") ; Height - Negative so (0, 0) is top-left.
         NumPut(        1, bi, 12, "ushort") ; Planes
         NumPut(       32, bi, 14, "ushort") ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", &bi, "uint", 0, "ptr*", pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")
   }

   ; Retrieve the device context for the screen.
   static sdc := DllCall("GetDC", "ptr", 0, "ptr")

   ; Copies a portion of the screen to a new device context.
   DllCall("gdi32\BitBlt"
            , "ptr", hdc, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
            , "ptr", sdc, "int", 0, "int", 0, "uint", 0x00CC0020) ; SRCCOPY

   ; C source code - https://godbolt.org/z/9v7vzf5az
   static bin := 0
   if !bin {
      code := (A_PtrSize == 4)
         ? "VYnli1UMi00Qi0UIOdBzCTkIdAeDwATr84nQXcM="
         : "SInISDnQcwtEOQB0CUiDwATr8EiJ0MM="
      size := StrLen(RTrim(code, "=")) * 3 // 4
      bin := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")
      DllCall("VirtualProtect", "ptr", bin, "ptr", size, "uint", 0x40, "uint*", old:=0)
      DllCall("crypt32\CryptStringToBinary", "str", code, "uint", 0, "uint", 0x1, "ptr", bin, "uint*", size, "ptr", 0, "ptr", 0)
   }

   ; Lift color to 32-bits if first 8 bits are zero.
   (!(color >> 24)) && color |= 0xFF000000

   ; Pass the width * height, but the size is returned due to C interpreting Scan0 as a integer pointer.
   ; So when doing pointer arithmetic, *Scan0 + 1 is actually adding 4 bytes.
   byte := DllCall(bin, "ptr", pBits, "ptr", pBits + (4 * A_ScreenWidth * A_ScreenHeight), "uint", color, "ptr")
   if (byte == pBits + A_ScreenWidth * A_ScreenHeight * 4)
      return False

   offset := (byte - pBits) // 4
   return [mod(offset, A_ScreenWidth), offset // A_ScreenWidth]
}
It shouldn't throw, use the newer version on the above post.

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by hasantr » 22 May 2022, 18:28

iseahound wrote:
22 May 2022, 15:23

Code: Select all

CoordMode, Mouse, Screen

a::
if xy := px(0x19AE82)
   MouseMove xy[1], xy[2]
return

px(color) {

   static hdc, hbm, obm, pBits
   if !hdc {
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      VarSetCapacity(bi, 40, 0)              ; sizeof(bi) = 40
         NumPut(       40, bi,  0,   "uint") ; Size
         NumPut(A_ScreenWidth, bi,  4,   "uint") ; Width
         NumPut(-A_ScreenHeight, bi,  8,    "int") ; Height - Negative so (0, 0) is top-left.
         NumPut(        1, bi, 12, "ushort") ; Planes
         NumPut(       32, bi, 14, "ushort") ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", &bi, "uint", 0, "ptr*", pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")
   }

   ; Retrieve the device context for the screen.
   static sdc := DllCall("GetDC", "ptr", 0, "ptr")

   ; Copies a portion of the screen to a new device context.
   DllCall("gdi32\BitBlt"
            , "ptr", hdc, "int", 0, "int", 0, "int", A_ScreenWidth, "int", A_ScreenHeight
            , "ptr", sdc, "int", 0, "int", 0, "uint", 0x00CC0020) ; SRCCOPY

   ; C source code - https://godbolt.org/z/9v7vzf5az
   static bin := 0
   if !bin {
      code := (A_PtrSize == 4)
         ? "VYnli1UMi00Qi0UIOdBzCTkIdAeDwATr84nQXcM="
         : "SInISDnQcwtEOQB0CUiDwATr8EiJ0MM="
      size := StrLen(RTrim(code, "=")) * 3 // 4
      bin := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")
      DllCall("VirtualProtect", "ptr", bin, "ptr", size, "uint", 0x40, "uint*", old:=0)
      DllCall("crypt32\CryptStringToBinary", "str", code, "uint", 0, "uint", 0x1, "ptr", bin, "uint*", size, "ptr", 0, "ptr", 0)
   }

   ; Lift color to 32-bits if first 8 bits are zero.
   (!(color >> 24)) && color |= 0xFF000000

   ; Pass the width * height, but the size is returned due to C interpreting Scan0 as a integer pointer.
   ; So when doing pointer arithmetic, *Scan0 + 1 is actually adding 4 bytes.
   byte := DllCall(bin, "ptr", pBits, "ptr", pBits + (4 * A_ScreenWidth * A_ScreenHeight), "uint", color, "ptr")
   if (byte == pBits + A_ScreenWidth * A_ScreenHeight * 4)
      return False

   offset := (byte - pBits) // 4
   return [mod(offset, A_ScreenWidth), offset // A_ScreenWidth]
}
It shouldn't throw, use the newer version on the above post.
I am very sorry. I wanted to find out if this pixel exists in the png file. My fault, I didn't explain correctly.
I mimicked your work with ImagePutBuffer. It found the blue color in the png file you provided. But I failed when I used my own file and green color.
viewtopic.php?f=6&t=101819#p456737
I'm interested in the png file, not the screen coordinates. I should have explained that.

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 22 May 2022, 23:04

Code: Select all

#include ImagePut.ahk

pic := ImagePutBuffer("GreenDot.png")
pic.show()
if xy := pic.PixelSearch(0x19AE82)
   MouseMove xy[1], xy[2]

MsgBox % "The color of the found pixel is: " pic[xy*]
Make sure you post additional questions about ImagePut here: viewtopic.php?f=6&t=76301

Just to explain pic[xy*], this uses the unpacking operator * as a shorthand for pic[xy[1], xy[2]]


EDIT: I reread your question and it seems a simpler snippet would suffice:

Code: Select all

#include ImagePut.ahk

filepath := "GreenDot.png"

if ImagePutBuffer(filepath).PixelSearch(0x19AE82)
   MsgBox Green Pixel Found
else
   MsgBox not found!

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by hasantr » 23 May 2022, 07:07

Thank you so much. I'll try when I get home. Both examples will work for me.
I forgot to use this part. That's why my attempts failed. "pic.show()" I understand why my attempt failed.
I think the second example is the ideal one for me.

KingPresLii
Posts: 13
Joined: 29 May 2022, 16:20

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by KingPresLii » 03 Nov 2022, 09:44

@iseahound
how can I add specific region to search?

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

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by iseahound » 03 Nov 2022, 11:29

Most people would use ImagePut for that.

https://github.com/iseahound/ImagePut
https://github.com/iseahound/ImagePut/wiki/PixelSearch-and-ImageSearch

Code: Select all

pic := ImagePutBuffer([100, 100, 50, 50])     ; Load image
pic.show() ; or ImageShow(pic)                         ; Show image
if xy := pic.PixelSearch(0xFFFFFF)                     ; Get [x, y] of 0xFFFFFF
    MouseMove xy[1], xy[2]                             ; Move cursor
else Reload                                            ; Restart

neutrowave
Posts: 1
Joined: 27 Nov 2022, 16:53

Re: Fastest Pixel Search - 1.6x faster than built in PixelSearch

Post by neutrowave » 27 Nov 2022, 17:00

@iseahound Please tell me if there is a V2 AutoHotkey version for your updated 'px' function from the top of this thread.

Thank you!
neutrowave


Post Reply

Return to “Scripts and Functions (v1)”