1
#Requires AutoHotkey v2.0

; ============================================================
; FUNCTION: SegmentsMatch
; ============================================================
SegmentsMatch(seg1, seg2, trim := 2, threshold := 20, varThreshold := 15) {
    s1 := [], s2 := []
    Loop seg1.Length - trim * 2
        s1.Push(seg1[A_Index + trim])
    Loop seg2.Length - trim * 2
        s2.Push(seg2[A_Index + trim])

    HexToRGB(hex) {
        hex := StrReplace(StrReplace(hex, "0x", ""), "#", "")
        n := Integer("0x" hex)
        return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
    }

    AvgRGB(seg) {
        r := 0, g := 0, b := 0
        for c in seg {
            rgb := HexToRGB(c)
            r += rgb[1], g += rgb[2], b += rgb[3]
        }
        n := seg.Length
        return [r / n, g / n, b / n]
    }

    RGBDist(a, b) {
        return Sqrt((a[1]-b[1])**2 + (a[2]-b[2])**2 + (a[3]-b[3])**2)
    }

    StdLuma(seg) {
        lumas := []
        for c in seg {
            rgb := HexToRGB(c)
            lumas.Push(0.299 * rgb[1] + 0.587 * rgb[2] + 0.114 * rgb[3])
        }
        mean := 0
        for l in lumas
            mean += l
        mean /= lumas.Length
        variance := 0
        for l in lumas
            variance += (l - mean)**2
        return Sqrt(variance / lumas.Length)
    }

    if RGBDist(AvgRGB(s1), AvgRGB(s2)) >= threshold
        return false

    if Abs(StdLuma(s1) - StdLuma(s2)) > varThreshold
        return false

    return true
}


; ============================================================
; FUNCTION: ScanPixelArrays
; Scans a 7x6 grid, logs each array to scan_log.txt
; Samples pixels in a cross/plus pattern (horizontal arm + vertical arm)
; centered on each cell. Uses a single GDI BitBlt capture.
;
; Cross layout (armLen=20, total=81 pixels per cell):
;   horizontal: center + armLen left  + armLen right  (41 px, left→right)
;   vertical:   center - armLen above + armLen below   (40 px, top→bottom, center excluded)
;   Array order: [horiz left→right | vert top→bottom]
; ============================================================
ScanPixelArrays(baseX := 670, baseY := 630, xStep := 64, yStep := 64,
                xCount := 7, yCount := 6, armLen := 20) {
    arrays  := []
    logFile := A_ScriptDir "\scan_log.txt"
    try FileDelete(logFile)
    FileAppend("Scan Log — " FormatTime(, "yyyy-MM-dd HH:mm:ss") "`n", logFile)
    FileAppend("========================================`n", logFile)

    ; --- single GDI capture of the entire grid region (plus arm overhang) ---
    captureX := baseX - armLen
    captureY := baseY - armLen
    captureW := (xCount - 1) * xStep + armLen * 2 + 1
    captureH := (yCount - 1) * yStep + armLen * 2 + 1

    hScreen  := DllCall("GetDC", "Ptr", 0, "Ptr")
    hMemDC   := DllCall("CreateCompatibleDC", "Ptr", hScreen, "Ptr")
    hBitmap  := DllCall("CreateCompatibleBitmap", "Ptr", hScreen, "Int", captureW, "Int", captureH, "Ptr")
    DllCall("SelectObject", "Ptr", hMemDC, "Ptr", hBitmap)
    DllCall("BitBlt", "Ptr", hMemDC, "Int", 0, "Int", 0,
            "Int", captureW, "Int", captureH,
            "Ptr", hScreen, "Int", captureX, "Int", captureY, "UInt", 0x00CC0020) ; SRCCOPY

    ReadPixel(hDC, bx, by) {
        raw := DllCall("GetPixel", "Ptr", hDC, "Int", bx, "Int", by, "UInt")
        r   := (raw        & 0xFF)
        g   := (raw >>  8) & 0xFF
        b   := (raw >> 16) & 0xFF
        return Format("0x{:06X}", (r << 16) | (g << 8) | b)
    }

    Loop yCount {
        yIndex := A_Index - 1
        y      := baseY + (yIndex * yStep)
        ; bitmap coords — offset by the top-left of our capture rect
        bmpCY  := armLen + (yIndex * yStep)   ; center row in bitmap

        Loop xCount {
            xIndex := A_Index - 1
            x      := baseX + (xIndex * xStep)
            bmpCX  := armLen + (xIndex * xStep)   ; center col in bitmap
            index  := (yIndex * xCount) + xIndex + 1

            colors := []

            ; horizontal arm: left → right across center row
            Loop armLen * 2 + 1
                colors.Push(ReadPixel(hMemDC, bmpCX - armLen + (A_Index - 1), bmpCY))

            ; vertical arm: top → bottom, skip center (already in horizontal)
            Loop armLen {
                colors.Push(ReadPixel(hMemDC, bmpCX, bmpCY - armLen + (A_Index - 1)))
            }
            Loop armLen {
                colors.Push(ReadPixel(hMemDC, bmpCX, bmpCY + A_Index))
            }

            arrays.Push(colors)

            colorStr := ""
            for c in colors
                colorStr .= c " "
            uniform := IsUniform(colors) ? "UNIFORM" : "ok"

            logLine := "[" index "]`t"
                     . "col=" (xIndex+1) " row=" (yIndex+1) "`t"
                     . "x=" x " y=" y "`t"
                     . uniform "`t"
                     . Trim(colorStr) "`n"
            FileAppend(logLine, logFile)
        }

        FileAppend("`n", logFile)  ; blank line between rows
    }

    ; --- clean up GDI resources ---
    DllCall("DeleteObject", "Ptr", hBitmap)
    DllCall("DeleteDC",     "Ptr", hMemDC)
    DllCall("ReleaseDC",    "Ptr", 0, "Ptr", hScreen)

    FileAppend("========================================`n", logFile)
    FileAppend("Total arrays: " arrays.Length "`n", logFile)
    return arrays
}


; ============================================================
; FUNCTION: IsUniform
; ============================================================
IsUniform(arr, lumaThreshold := 10) {
    HexToRGB(hex) {
        hex := StrReplace(StrReplace(hex, "0x", ""), "#", "")
        n := Integer("0x" hex)
        return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
    }

    lumas := []
    for c in arr {
        rgb := HexToRGB(c)
        lumas.Push(0.299 * rgb[1] + 0.587 * rgb[2] + 0.114 * rgb[3])
    }

    mean := 0
    for l in lumas
        mean += l
    mean /= lumas.Length

    variance := 0
    for l in lumas
        variance += (l - mean)**2

    return Sqrt(variance / lumas.Length) < lumaThreshold
}


; ============================================================
; FUNCTION: FilterUniform
; ============================================================
FilterUniform(arrays) {
    filtered := []
    removed  := 0
    for arr in arrays {
        if IsUniform(arr)
            removed++
        else
            filtered.Push(arr)
    }
    return { filtered: filtered, removed: removed }
}


; ============================================================
; FUNCTION: FindAllMatches
; ============================================================
FindAllMatches(arrays, trim := 2, threshold := 20, varThreshold := 15) {
    matches := []
    total := arrays.Length
    used := Map()

    Loop total - 1 {
        i := A_Index
        if used.Has(i) || IsUniform(arrays[i])
            continue
        Loop total - i {
            j := i + A_Index
            if used.Has(j) || IsUniform(arrays[j])
                continue
            if SegmentsMatch(arrays[i], arrays[j], trim, threshold, varThreshold) {
                x1 := 670 + Mod(i - 1, 7) * 64
                y1 := 625 + Floor((i - 1) / 7) * 64
                x2 := 670 + Mod(j - 1, 7) * 64
                y2 := 625 + Floor((j - 1) / 7) * 64
                ; MsgBox("A: " x1 "," y1 " | B: " x2 "," y2)
                MouseClickDrag("Left", x1, y1, x2, y2)
                Sleep(100)

                used.Set(i, true)
                used.Set(j, true)
                break
            }
        }
    }
    ;MsgBox("Done")
    return matches
}


; ============================================================
; MAIN — press F12 to run
; ============================================================
F12:: {
    arrays := ScanPixelArrays()
    ;Tooltip("Scan complete")
    matches := FindAllMatches(arrays)
	Tooltip("Done")
	SetTimer(() => ToolTip(), -2000)
}

For immediate assistance, please email our customer support: [email protected]

Download RAW File