ScrCmp() : Detects changes in screen by comparing two screenshots

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

ScrCmp() : Detects changes in screen by comparing two screenshots

03 Nov 2020, 02:42

ScrCmp( X, Y, W, H, Hwnd:=0x0, Sleep* )
 
ScrCmp() is a MemCmp() done between pixels of two screen shots to detect any changes in a screen region.
ScrCmp() will wait for a pixel change and will return True on success.
 
To cancel the function's wait state, you may set the global array element A_Args.ScrCmp.Wait := False
Refer the following Global array elements to know the screen coordinates where the pixel change occurred:
Window X,Y :    A_Args.ScrCmp.CX and A_Args.ScrCmp.CY
Screen X,Y :    A_Args.ScrCmp.SX and A_Args.ScrCmp.SY
 
How the function works?:
 
The function creates a Top-down bitmap measuring twice the height of the region to be monitored.
For eg., to monitor a screen area of 366x166, a 366x332 bitmap will be created.
The function takes a first screenshot for reference, at the bottom of the image stack.
Then within a loop, consecutive screen shots are done at the top of image stack.
For every screen shot, a binary compare is done between the top image and the reference (bottom) image
until any pixel change is detected
 
About Internal bitmap
 
Parameters:
 
  • X, Y, W, H : Coordinates and dimension of region of screen unless Hwnd parameter is non-zero in which case the region belongs to client area of a window.
  • Hwnd : Handle to a Window, Default value is 0x0
  • Sleep1, Sleep2 : Default values are 100, 100
    Sleep1 is duration (in ms) to wait between consecutive screen shots.
    When a screen change is detected, an additional screen shot is taken to confirm that change didn't occur because of a flicker.
    Sleep2 is duration (in ms) to wait between a regular screen shot and the additional confirmation screen shot.
    Example: ScrCmp(0, 0, 32, 32,, 60000, 200) will poll per minute.
    When a screen change is detected, a confirmation screen shot is taken after a delay of 200ms.
     
    For better readability, one may use ScrCmp(0, 0, 32, 32, 0x0, [60000,200]*) instead of ScrCmp(0, 0, 32, 32,, 60000, 200)
 
Usage example:
 

Code: Select all

#NoEnv
#Warn
#SingleInstance, Force
SetBatchLines, -1

ScrCmp(0, 0, 32, 32)
MsgBox Screen Region Change Detected!

; Paste ScrCmp() below
 
 
The function:
 

Code: Select all

ScrCmp( X, Y, W, H, Hwnd:=0x0, Sleep* )  {                                        ; v0.66 By SKAN on D3B3/D3B6 @ tiny.cc/scrcmp
Local
Global A_Args
  Sleep[1] := Sleep[1]="" ? 100 : Format("{:d}", Sleep[1])
  Sleep[2] := Sleep[2]="" ? 100 : Format("{:d}", Sleep[2])

  VarSetCapacity(BITMAPINFO, 40, 0)
  NumPut(32, NumPut(1, NumPut(0-H*2, NumPut(W, NumPut(40,BITMAPINFO,"Int"),"Int"),"Int"),"Short"),"Short")

  hBM := DllCall("Gdi32.dll\CreateDIBSection", "Ptr",0, "Ptr",&BITMAPINFO, "Int",0, "PtrP",pBits := 0, "Ptr",0, "Int",0, "Ptr")
  sDC := DllCall("User32.dll\GetDC", "Ptr",(Hwnd := WinExist("ahk_id" . Hwnd)), "Ptr")
  mDC := DllCall("Gdi32.dll\CreateCompatibleDC", "Ptr",sDC, "Ptr")
  DllCall("Gdi32.dll\SaveDC", "Ptr",mDC)
  DllCall("Gdi32.dll\SelectObject", "Ptr",mDC, "Ptr",hBM)
  DllCall("Gdi32.dll\BitBlt", "Ptr",mDC, "Int",0, "Int",H, "Int",W, "Int",H, "Ptr",sDC, "Int",X, "Int",Y, "Int",0x40CC0020)

  A_Args.ScrCmp := {"Wait": 1},   Bytes := W*H*4,  Count := 0
  hMod := DllCall("Kernel32.dll\LoadLibrary", "Str","ntdll.dll", "Ptr")
  While ( A_Args.ScrCmp.Wait && (Count<2) )
  {
      DllCall("Gdi32.dll\BitBlt", "Ptr",mDC, "Int",0, "Int",0, "Int",W, "Int",H, "Ptr",sDC, "Int",X, "Int",Y, "Int",0x40CC0020)
      Count := ( (Byte := DllCall("ntdll.dll\RtlCompareMemory", "Ptr",pBits, "Ptr",pBits+Bytes, "Ptr",Bytes) ) != Bytes )
               ? (Count + 1) : 0
      Sleep % (Count ? Sleep[2] : Sleep[1])
  }   Byte +=1
  DllCall("Kernel32.dll\FreeLibrary", "Ptr",hMod)

  SX := (CX := Mod((Byte-1)//4, W) + X),    SY := (CY := (Byte-1) // (W*4)   + Y)
  If (Hwnd)
    VarsetCapacity(POINT,8,0), NumPut(CX,POINT,"Int"), NumPut(CY,POINT,"Int")
  , DllCall("User32.dll\ClientToScreen", "Ptr",Hwnd, "Ptr",&POINT)
  , SX := NumGet(POINT,0,"Int"),  SY := NumGet(POINT,4,"Int")

  If (Wait := A_Args.ScrCmp.Wait)
      A_Args.ScrCmp := { "Wait":0, "CX":CX, "CY":CY, "SX":SX, "SY":SY }
  DllCall("Gdi32.dll\RestoreDC", "Ptr",mDC, "Int",-1)
  DllCall("Gdi32.dll\DeleteDC", "Ptr",mDC)
  DllCall("User32.dll\ReleaseDC", "Ptr",Hwnd, "Ptr",sDC)
  DllCall("Gdi32.dll\DeleteObject", "Ptr",hBM)
Return ( !!Wait )
}
My Scripts and Functions: V1  V2
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

03 Nov 2020, 03:49

Could the function return the coordinates where the change(s) occurred?
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

03 Nov 2020, 06:02

Can be of use.
Thank you like always for your work
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

05 Nov 2020, 15:02

ozzii wrote:
03 Nov 2020, 06:02
Can be of use.
Thank you like always for your work
 
:thumbup: :)
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

05 Nov 2020, 15:06

robodesign wrote:
03 Nov 2020, 03:49
Could the function return the coordinates where the change(s) occurred?
 
The function will return true on success. 
I have updated the code. The function now sets the coordinates as elements of global array A_Args

:)
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

05 Nov 2020, 15:25

Great :clap:

Works very well. I tested it with a script region selector (by jeeswg).
here is the scipt (basic usage without HWND)

shortcut:
Press win+LeftMouse to select a region to monitor.
Required:
:!: above ScrCmp() function

Code: Select all

#NoEnv
#Warn
#SingleInstance, Force
SetBatchLines, -1
CoordMode, Mouse, Screen


;note: 'CoordMode, Mouse, Screen' must be used in the auto-execute section

#LButton::

InputRect(vX1, vY1, vX2, vY2)

vW := vX2-vX1, vH := vY2-vY1
if (vInputRectState = -1)
	return
sleep, 200


ScrCmp(vX1, vY1, vW, vH)
MsgBox Screen Region Change Detected!




;==================================================
;thx jeeswg
;https://www.autohotkey.com/boards/viewtopic.php?t=42810

;based on LetUserSelectRect by Lexikos:
;LetUserSelectRect - select a portion of the screen - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/45921-letuserselectrect-select-a-portion-of-the-screen/

;note: 'CoordMode, Mouse, Screen' must be used in the auto-execute section

;e.g.
;InputRect(vWinX, vWinY, vWinR, vWinB)
;vWinW := vWinR-vWinX, vWinH := vWinB-vWinY
;if (vInputRectState = -1)
;	return

InputRect(ByRef vX1, ByRef vY1, ByRef vX2, ByRef vY2)
{
	global vInputRectState := 0
	DetectHiddenWindows, On
	Gui, 1: -Caption +ToolWindow +AlwaysOnTop +hWndhGuiSel
	Gui, 1: Color, Red
	WinSet, Transparent, 128, % "ahk_id " hGuiSel
	Hotkey, *LButton, InputRect_Return, On
	Hotkey, *RButton, InputRect_End, On
	Hotkey, Esc, InputRect_End, On
	KeyWait, LButton, D
	MouseGetPos, vX0, vY0
	SetTimer, InputRect_Update, 10
	KeyWait, LButton
	Hotkey, *LButton, Off
	Hotkey, Esc, InputRect_End, Off
	SetTimer, InputRect_Update, Off
	Gui, 1: Destroy
	return

	InputRect_Update:
	if !vInputRectState
	{
		MouseGetPos, vX, vY
		(vX < vX0) ? (vX1 := vX, vX2 := vX0) : (vX1 := vX0, vX2 := vX)
		(vY < vY0) ? (vY1 := vY, vY2 := vY0) : (vY1 := vY0, vY2 := vY)
		Gui, 1:Show, % "NA x" vX1 " y" vY1 " w" (vX2-vX1) " h" (vY2-vY1)
		return
	}
	vInputRectState := 1

	InputRect_End:
	if !vInputRectState
		vInputRectState := -1
	Hotkey, *LButton, Off
	Hotkey, *RButton, Off
	Hotkey, Esc, Off
	SetTimer, InputRect_Update, Off
	Gui, 1: Destroy

	InputRect_Return:
	return
}

;==================================================

; Paste ScrCmp() below
Cheers ;)
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

09 Nov 2020, 00:04

Ahk_fan wrote:
05 Nov 2020, 16:38
:bravo: this ist pretty cool
Thanks :thumbup: :)

@SpeedMaster
 
Very nice. Thanks. :thumbup: :)
pgarza
Posts: 4
Joined: 28 Jun 2019, 09:01

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

11 Sep 2021, 06:21

can this be used with:

Code: Select all

CoordMode, Pixel, Client
Jento
Posts: 6
Joined: 13 Oct 2021, 12:38

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

15 Oct 2021, 17:15

Hi,
I have question if you could (and if yes how) change much % of the pixels have to be changed to return true? Because im trying to make auto screenshoot script and i want to be able to adjust some value because sometimes it is just too sensitive.
mauritius
Posts: 25
Joined: 06 Nov 2020, 14:00
Contact:

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

21 Oct 2021, 07:54

Hello SKAN, thanks for an amazing function. Would you like to help me using it? I want to compare two screenshots - before and after me clicking. Can you please tell me how to do it? I haven't done any changes to you fuction because simply I don't understand it (although it works great). Oh and I was wondering if this is possible to compare screens only for specific period of time (so if there is no changes I can do sth instead of waiting infinitely.

So, I want:
1. make a screenshot of an area (for your function)
2. perform a click
3. wait for a changes in an area (using your function)
4. if there is no changes for let's say 10 seconds repeat steps 1-3

Sorry for asking, maybe you could point me in some direction?
bill_whitfill
Posts: 1
Joined: 28 Mar 2022, 09:41

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

28 Mar 2022, 09:49

hey everyone, I'm trying to use this function but when I do it says error call to non-existent function. My script is very basic and I made sure my version was on 1.1.33.10. Here is my script:

Code: Select all

#NoEnv
#Warn
#SingleInstance, Force
SetBatchLines, -1

ScrCmp(0, 0, 506, 818)
MsgBox Screen Region Change Detected!

; Paste ScrCmp() below
[Mod edit: [code][/code] tags added.]

I'm very new to AHK especially the more complicated stuff like this so any help is appreciated.
User avatar
MrDodel
Posts: 96
Joined: 28 Apr 2021, 09:03
Location: Event Horizon

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

28 Mar 2022, 10:09

bill_whitfill wrote:
28 Mar 2022, 09:49
Download the function (1st Post) 2nd section of code, save / rename it as ScrCmp().Ahk into your script folder, then in your script add #Include Scrcmp().ahk
So much universe, and so little time. GNU Sir Terry.
John1
Posts: 236
Joined: 11 May 2020, 11:54

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

15 Aug 2022, 08:57

@SKAN
Hello. Thank you for your awesome function.

I want to do one click. And when the screen changed where the mouse clicked/a programm reacted on a click. I want to stop it.
So far i have this code. It stopps not after one click, it stopps after 5-8 clicks.
How can i make it only send one click and see if the click changed something as fast as possible?

Thank you!

Code: Select all

1::
counter := new SecondCounter
counter.Start()
BlockInput, MouseMove
SetTimer, Clicker, 1
MouseGetPos, StartX1, StartY1
ScrCmp(StartX1-7,StartY1-7,14,14,,1) 
SetTimer, Clicker, Off
BlockInput, MouseMoveOff
counter.Stop()
return

Clicker:
Click
return

____________________________________

;A (SecondCounter)
; An example class for counting the seconds...
class SecondCounter {
    __New() {
        this.interval := 1
        this.count := 0
        ; Tick() has an implicit parameter "this" which is a reference to
        ; the object, so we need to create a function which encapsulates
        ; "this" and the method to call:
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start() {
        ; Known limitation: SetTimer requires a plain variable reference.
        timer := this.timer
        SetTimer % timer, % this.interval
        ToolTip % "Counter started"
    }
    Stop() {
        ; To turn off the timer, we must pass the same object as before:
        timer := this.timer
        SetTimer % timer, Off
        ToolTip % "Counter stopped at " this.count
    }
    ; In this example, the timer calls this method:
    Tick() {
        ToolTip % ++this.count
    }
}
;E (SecondCounter)
MancioDellaVega
Posts: 83
Joined: 16 May 2020, 12:27
Location: Italy

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

16 Sep 2022, 08:15

@SKAN
Great function as always...
Is it possible to turn ON/OFF the region monitoring at the push of a button?
Example.
I press once F1 = monitoring ON
I press again F1 = monitoring OFF
Courses on AutoHotkey
wer
Posts: 57
Joined: 29 Nov 2022, 21:28

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

29 Nov 2022, 21:34

sir, could you tell me if
change occurred --do nothing
no change for a while --do some thing
how to achieve this
Skrell
Posts: 302
Joined: 23 Jan 2014, 12:05

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

13 Dec 2022, 13:05

Could I use ScrCmp() to on demand sample a portion of the screen and then at a later point in time(also on demand), sample that same portion and determine if it's changed?
jdc99
Posts: 1
Joined: 20 Jan 2023, 10:27

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

20 Jan 2023, 10:29

Plus 1 vote for adding this feature
Skrell wrote:
13 Dec 2022, 13:05
Could I use ScrCmp() to on demand sample a portion of the screen and then at a later point in time(also on demand), sample that same portion and determine if it's changed?
aliztori
Posts: 117
Joined: 19 Jul 2022, 12:44

Re: ScrCmp() : Detects changes in screen by comparing two screenshots

21 May 2023, 13:53

I converted this code to version two, but I don't know why I get an error

Code: Select all

ScreenCompare(X, Y, W, H, Hwnd := 0, Delay1 := 100, Delay2 := 100) {

    Global A_Args
    
    BITMAPINFO := Buffer(40, 0)

    NumPut("Int", W,
        "Int", 0 - H * 2,
        "Short", 1,
        "Short", 32,
        BITMAPINFO
    )

    hBM := DllCall("Gdi32.dll\CreateDIBSection", "Ptr", 0, "Ptr", BITMAPINFO, "Int", 0, "PtrP", pBits := 0, "Ptr", 0, "Int", 0, "Ptr")
    sDC := DllCall("User32.dll\GetDC", "Ptr", Hwnd := WinExist("ahk_id" . Hwnd), "Ptr")
    mDC := DllCall("Gdi32.dll\CreateCompatibleDC", "Ptr", sDC, "Ptr")
    DllCall("Gdi32.dll\SaveDC", "Ptr", mDC)
    DllCall("Gdi32.dll\SelectObject", "Ptr", mDC, "Ptr", hBM)
    DllCall("Gdi32.dll\BitBlt", "Ptr", mDC, "Int", 0, "Int", H, "Int", W, "Int", H, "Ptr", sDC, "Int", X, "Int", Y, "Int", 0x40CC0020)

    A_Args.ScrCmp := { Wait: 1 }, Bytes := W * H * 4, Count := 0

    hMod := DllCall("Kernel32.dll\LoadLibrary", "Str", "ntdll.dll", "Ptr")

    While (A_Args.ScrCmp.Wait && (Count < 2))
    {
        DllCall("Gdi32.dll\BitBlt", "Ptr", mDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "Ptr", sDC, "Int", X, "Int", Y, "Int", 0x40CC0020)
        if (Byte := DllCall("ntdll.dll\RtlCompareMemory", "Ptr", pBits, "Ptr", pBits + Bytes, "Ptr", Bytes)) != Bytes
            Count++
        ; Count := ()
        ;     ? (Count + 1) : 0
        Sleep(Count ? Delay2 : Delay1)
    
    } Byte += 1

    DllCall("Kernel32.dll\FreeLibrary", "Ptr", hMod)

    SX := (CX := Mod((Byte - 1) // 4, W) + X),
    SY := (CY := (Byte - 1) // (W * 4) + Y)

    if (Hwnd)
        POINT := Buffer(8, 0),
            NumPut("int", CX, POINT),
            NumPut("int", CY, POINT),
            DllCall("User32.dll\ClientToScreen", "Ptr", Hwnd, "Ptr", &POINT),
            SX := NumGet(POINT, 0, "Int"),
            SY := NumGet(POINT, 4, "Int")

    if (wait := A_Args.ScrCmp.Wait)
        A_Args.ScrCmp := {
            Wait: 0,
            CX: CX,
            CY: CY,
            SX: SX,
            SY: SY
        }
    DllCall("Gdi32.dll\RestoreDC", "Ptr", mDC, "Int", -1)
    DllCall("Gdi32.dll\DeleteDC", "Ptr", mDC)
    DllCall("User32.dll\ReleaseDC", "Ptr", Hwnd, "Ptr", sDC)
    DllCall("Gdi32.dll\DeleteObject", "Ptr", hBM)

    return wait
}

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 122 guests