How can I get the pixel data from an area of the screen (much) faster in AHK?

人盡茶涼 提交于 2019-12-02 08:56:44

问题


I understand that for some things it would be better to write certain things in C++, but I'd really like to be able to do this in AHK:

I want to be able to retrieve the pixel data from a 100x300 area of the screen, however PixelGetColor is way too slow. Here's a test demonstrating that it takes about 0.02 seconds per pixel, which is roughly 11.5 hours to get the pixel data from an entire 1920 x 1080 screen.

In the test, it'll take about 4-5 seconds just to get the pixel data from a 15 x 15 area of the screen.

width := 15 ; 1920
height := 15 ; 1080
searchResolution := 1 ; 3
columns := width / searchResolution
rows := height / searchResolution
resultRows := {}
columnCounter := 0
rowCounter := 0
resultCounter := 0

start := getTimestamp()
loop, %columns%
{
    resultRows[columnCounter] := {}
    loop, %rows%
    {
        PixelGetColor, pixelColor, columnCounter, rowCounter
        resultRows[columnCounter][rowCounter] := pixelColor
        rowCounter += searchResolution
        resultCounter += 1
    }
    columnCounter += searchResolution
    rowCounter := 0
}
end := getTimestamp()

MsgBox % "Finished! It took " . (end - start) / 1000 . 
" seconds to record pixel data from a " . 
width . " x " . height . " area of the screen (" . resultCounter . " pixels)."

getTimestamp()
{
    DllCall("QueryPerformanceCounter", "Int64*", timestamp)
    DllCall("QueryPerformanceFrequency", "Int64*", frequency)
    return Round(timestamp * 1000 / frequency)
}

If you'd like the version which includes debug logging and exporting of the data to an XML file for inspection, it's here.

Is there any faster way to get pixel data from a portion of the screen?

PixelSearch searches very large areas of the screen very quickly, I'm not sure why PixelGetColor would be so very slow in comparison. There must be some .dll or some other function I can use to get pixel data from a small area of the screen much faster than this.


回答1:


I found a way to do it 103 times faster than Forivin's solution :D

SetBatchLines, -1
CoordMode, Pixel, screen

FileDelete, Log.txt

searchSpace := 400
jumpSize := 1 ; how many units to skip each interval
total := Round(((searchSpace * searchSpace) / jumpSize), 0)
startTimer := getTimestamp()
getPixelMapSlow(searchSpace, jumpSize)
endTimer := getTimestamp()
duration := endTimer - startTimer
rate := total / duration
FileAppend, % "[getPixelMapSlow] Retrieved " . total . " pixels from bitmap, duration: " . duration . "ms at at a rate of " . rate . " pixels/ms.`n", Log.txt

searchSpace := 400
jumpSize := 1 ; how many units to skip each interval
total := Round(((searchSpace * searchSpace) / jumpSize), 0)
startTimer := getTimestamp()
getPixelMapFast(searchSpace, jumpSize)
endTimer := getTimestamp()
duration := endTimer - startTimer
rate := total / duration
FileAppend, % "[getPixelMapFast] Retrieved " . total . " pixels from bitmap, duration: " . duration . "ms at at a rate of " . rate . " pixels/ms.`n", Log.txt


getPixelMapFast(searchSpace, jumpSize){
    width := 1920
    height := 1080
    centerX := width / 2
    centerY := height / 2
    searchSpacehalf := searchSpace / 2
    searchCounterX := 0
    searchCounterY := 0
    pixelMap := {}
    pBitmap := Gdip_BitmapFromScreen((centerX - searchSpacehalf) . "|" . (centerY - searchSpacehalf) . "|" . searchSpace . "|" . searchSpace)
    E1 := Gdip_LockBits(pBitmap, 0, 0, Gdip_GetImageWidth(pBitmap), Gdip_GetImageHeight(pBitmap), Stride, Scan0, BitmapData)
    Loop, %searchSpace%
    {
        tick := A_Index * jumpSize
        if (tick < searchSpace) {
            New_Index_X := tick
            Loop, %searchSpace%
            {
                tick := A_Index * jumpSize
                if (tick < searchSpace) {
                    New_Index_Y := tick
                    color1ARGB := Gdip_GetLockBitPixel(Scan0, New_Index_X, New_Index_Y, Stride)
                    SetFormat, Integer, H
                    color1RGB := 0x00ffffff & color1ARGB
                    SetFormat, Integer, D
                    if (!pixelMap[New_Index_X]){
                        pixelMap[New_Index_X] := {}
                    }
                    pixelMap[New_Index_X][New_Index_Y] := color1RGB
                }
            }
        }
    }
    Gdip_UnlockBits(pBitmap, BitmapData)
    Gdip_DisposeImage(pBitmap)
    return pixelMap
}

getPixelMapSlow(searchSpace, jumpSize){
    width := 1920
    height := 1080
    centerX := width / 2
    centerY := height / 2
    searchSpacehalf := searchSpace / 2
    searchCounterX := 0
    searchCounterY := 0
    pixelMap := {}
    pBitmap := Gdip_BitmapFromScreen((centerX - searchSpacehalf) . "|" . (centerY - searchSpacehalf) . "|" . searchSpace . "|" . searchSpace)
    Loop, %searchSpace%
    {
        tick := A_Index * jumpSize
        if (tick < searchSpace) {
            New_Index_X := tick
            Loop, %searchSpace%
            {
                tick := A_Index * jumpSize
                if (tick < searchSpace) {
                    New_Index_Y := tick
                    color1ARGB := Gdip_GetPixel(pBitmap, New_Index_X, New_Index_Y)
                    if (!pixelMap[New_Index_X]){
                        pixelMap[New_Index_X] := {}
                    }
                    color1RGB := ARGBtoRGB(color1ARGB)
                    pixelMap[New_Index_X][New_Index_Y] := color1RGB
                }
            }
        }
    }
    Gdip_DisposeImage(pBitmap)
    return pixelMap
}

ARGBtoRGB( ARGB ) {
    VarSetCapacity( RGB,6,0 )
    DllCall( "msvcrt.dll\sprintf", Str,RGB, Str,"%06X", UInt,ARGB<<8 )
    Return "0x" RGB
}

getTimestamp()
{
    DllCall("QueryPerformanceCounter", "Int64*", timestamp)
    DllCall("QueryPerformanceFrequency", "Int64*", frequency)
    return Round(timestamp * 1000 / frequency)
}

Of course include the relevant functions of the AHK Gdip library (found on Github) in your code for this to work.

Log:

[getPixelMapSlow] Retrieved 160000 pixels from bitmap, duration: 33161ms at at a rate of 4.824945 pixels/ms.
[getPixelMapFast] Retrieved 160000 pixels from bitmap, duration: 321ms at at a rate of 498.442368 pixels/ms.



回答2:


To reduce delays between commands to a minimum you should also use SetBatchLines, -1. This alone can give you a significant performance boost.
I think you have already figured out the rest.
But in case anyone else stumbles across this question. Here is how you can do it with GDI+:

SetBatchLines, -1

#Include Gdip.ahk

pToken := Gdip_Startup()

; Screen area ("X|Y|Width|Height")
pBitmap := Gdip_BitmapFromScreen("500|600|300|100")

; Read RGB color from pixel x290 y65
ARGB := Gdip_GetPixel( pBitmap, 290, 65 )
pixelColor := ARGBtoRGB( ARGB )
MsgBox, % pixelColor

; Read RGB color from pixel x167 y90
ARGB := Gdip_GetPixel( pBitmap, 167, 90 )
pixelColor := ARGBtoRGB( ARGB )
MsgBox, % pixelColor


Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)

ARGBtoRGB( ARGB ) {
    VarSetCapacity( RGB,6,0 )
    DllCall( "msvcrt.dll\sprintf", Str,RGB, Str,"%06X", UInt,ARGB<<8 )
    Return "0x" RGB
}

The code is mostly what I already posted in another answer right here.



来源:https://stackoverflow.com/questions/44621796/how-can-i-get-the-pixel-data-from-an-area-of-the-screen-much-faster-in-ahk

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!