I want to scan a given region for the 6 most common colors

Get help with using AutoHotkey and its commands and hotkeys
User avatar
Off Topic
Posts: 43
Joined: 07 Oct 2017, 20:57

I want to scan a given region for the 6 most common colors

25 Apr 2018, 01:49

And a given region from a mouse click and drag, but I'm not sure how the scanning part and comparing colors would be best done. I figure that I would need to loop through a grid whose dimensions are based on the width and height of the clickdrag and PixelGetColor each, then add distinct colors to a string and compare how many instances each had within this scanned region to determine the 6 most common. I think I'm off to a decent start but I don't know how to approach how many instances of color there were or how to best cycle through "scan lines" to check for color:

Code: Select all

SetTimer, mouseTrack, 20

mouseTrack:
MouseGetPos, vCurX, vCurY
Return

!#LButton::
	errCount = 0
	originX := vCurX, originY := vCurY
	SetTimer, WhileHolding, 20
	PixelGetColor, originColor, originX, originY, RGB
Return

WhileHolding:
width := (vCurX - originX), height := (vCurY - originY)
; ToolTip % vCurX ", " vCurY "`r`n" originX ", " originY  "`r`n" "w" width " h" height
Return

!#LButton Up::
	SetTimer, WhileHolding, Off
	spectrum := ""
	count = 0, Xoffset = 5, Yoffset = 5
	ScanX := (width/Xoffset), ScanAmount := Floor(ScanX)
	Loop, % ScanAmount
	{
		++count
		Xoffset += 5
		If (Xoffset > width) {
			Xoffset = 0
			Yoffset += 5
		}
		If !InStr(spectrum, newColor%A_Index%){
			spectrum .= " " newColor%A_Index%
		}	
		PixelGetColor, newColor%A_Index%, % (originX + Xoffset), % (originY + Yoffset), RGB
	}
	; MsgBox % vCurX ", " vCurY "`r`n" originX ", " originY  "`r`n" "w" width " h" height "`r`n" count "`r`n" ScanAmount "`r`n" ScanX
Return
Can I get any help? How would you personally do this, retrieving the six most common colors for any given region?
MaxAstro
Posts: 551
Joined: 05 Oct 2016, 13:00

Re: I want to scan a given region for the 6 most common colors

25 Apr 2018, 11:15

First off, for the love of all that is holy, use GDI+ instead of PixelGetColor, your script will be about a hundred times faster. GDI+ libraries attached.

Secondly, what I would probably do is check the color of each pixel in the area one at a time and build an array. Use the color as the key and the number of times you've seen that color as the value. Each time you scan a pixel, check the array to see if that key exists. If it doesn't, add it, if it does increment the value by one.

Once you've finished searching, you now have an array containing the number of occurrences of each color, and it should be easy to for-loop through that array and sort out the six highest.
Attachments
Gdip_PixelSearch.ahk
(1.02 KiB) Downloaded 22 times
Gdip_ImageSearch.ahk
(33.25 KiB) Downloaded 22 times
Gdip_All.ahk
(95.45 KiB) Downloaded 21 times
User avatar
jeeswg
Posts: 5406
Joined: 19 Dec 2016, 01:58
Location: UK

Re: I want to scan a given region for the 6 most common colors

25 Apr 2018, 12:36

To select a screen region:
selection rectangle: use mouse to select screen region - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42810

To printscreen a section of the screen, and get a list of pixel colours:

Code: Select all

;based on:
;Gdip: image binary data to hex string for OCR - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=35339

;[Gdip functions]
;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

q:: ;get frequency count for pixels in a rectangle
pToken := Gdip_Startup()
vPosX := 300, vPosY := 300
vPosW := 300, vPosH := 300
vPosW := 4, vPosH := 4
;vPosW := 1, vPosH := 1
vImgPos := Format("{}|{}|{}|{}", vPosX, vPosY, vPosW, vPosH)
pBitmap := Gdip_BitmapFromScreen(vImgPos)
;PixelFormat32bppARGB := 0x26200A
;PixelFormat24bppRGB := 0x21808
pBitmap2 := Gdip_CloneBitmapArea(pBitmap, 0, 0, vPosW, vPosH, 0x26200A)
hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap2)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_StrBinToHex(vAddr, vSize)
Clipboard := vHex
;get pixel count:
MsgBox, % Round(StrLen(vHex) / 8) " " (vPosW * vPosH)

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_DisposeImage(pBitmap2)
Gdip_Shutdown(pToken)

MsgBox, % vHex
;vOutput := RegExReplace(vHex, ".{8}", "$0,") ;BGRA
vOutput := RegExReplace(vHex, "(.{6})..", "$1,") ;BGR
vOutput := SubStr(vOutput, 1, -1)
MsgBox, % vOutput

oArray := {}
Loop, Parse, vOutput, % ","
	if oArray.HasKey("z" A_LoopField)
		oArray["z" A_LoopField]++
	else
		oArray["z" A_LoopField] := 1
;frequency count for pixel colours (BGR)
vOutput2 := ""
for vKey, vValue in oArray
	vOutput2 .= vValue "`t" SubStr(vKey, 2) "`r`n"
Clipboard := vOutput2
MsgBox, % vOutput2
return

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

;JEE_BinDataToHex
JEE_StrBinToHex(vAddr, vSize, vCase:="U")
{
	static vIsVistaPlus := (DllCall("kernel32\GetVersion", UInt) & 0xFF) >= 6
	if vIsVistaPlus
	{
		;CRYPT_STRING_NOCRLF := 0x40000000
		;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4000000C, Ptr,0, UIntP,vChars)
		VarSetCapacity(vHex, vChars*2, 0)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4000000C, Str,vHex, UIntP,vChars)
	}
	else
	{
		;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
		VarSetCapacity(vHex, vChars*2, 0)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
		vHex := StrReplace(vHex, "`r`n")
		vHex := StrReplace(vHex, " ")
	}
	if (vCase = "L")
		return vHex
	else
		return Format("{:U}", vHex)
}

;==================================================
Rohwedder
Posts: 1506
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 03:23

Hallo,
Hotkey c starts the color measurement.
Try:

Code: Select all

#SingleInstance, Force
#NoEnv
CoordMode, Mouse, Screen
CoordMode, Pixel, Screen
HotKey, LButton, Off
HotKey, LButton Up, Off
c::
Color =
ToolTip, click and drag as soon as this has disappeared
Sleep, 2000
ToolTip
HotKey, LButton, On
HotKey, LButton Up, On
Return
LButton::MouseGetPos, x,y
LButton Up::
	MouseGetPos, xdel,ydel
HotKey, LButton, Off
HotKey, LButton Up, Off
If (xdel < x)
	q:=x, x:=xdel, xdel:=q
If (ydel < y)
	q:=y, y:=ydel, ydel:=q
xdel -= x, ydel -= y, ya := y
If (!xdel Or !ydel)
	Goto c
Loop, %xdel%
{
	ToolTip, % A_Index* 100//xdel "%"
	y := ya
	Loop, %ydel%
	{
		PixelGetColor, C, x, y, RGB
		Color .= C ","
		y++
	}
	x++
}
Sort, Color, N D,
Color2 =
no = 0
cOld := SubStr(Color,1,8)
Loop, Parse, Color, CSV
{
	If (A_LoopField = cOld)
		no++
	Else
	{
		Color2 .= Format("0x{:06X}", no) SubStr(cOld,3,6) ","
		cOld := A_LoopField
	}
}
Sort, Color2, N R D,
Msg := "hex. No.       Color`n"
Loop, Parse, Color2, CSV
{
	If !A_LoopField
		Break
	No := SubStr(A_LoopField,1,8)
	Color := "0x" SubStr(A_LoopField,9,6)
	Msg .= No "    " Color "`n"
}
Until A_Index > 5
ToolTip
MsgBox, % Msg
Return
User avatar
sooyke
Posts: 7
Joined: 21 Apr 2018, 10:23

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 06:04

Image

Just for fun...

I made the screen area fixed in all codes to 300,300,20,20 and removed msgboxes and tooltips to compare them.

update:

Rohwedder's code using pixelgetcolor is very slow ( on win10) i tried a simple loop 1000 on winXP 32ms on win10 20000 ms!!!!!

The code is rather messy because of copy/paste from separate codes :)

Code: Select all



c::

SetWorkingDir, %A_ScriptDir%
SetBatchLines, -1

#include *i gdip_all.ahk

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

coordmode pixel
coordMode screen
t:=a_tickcount
spectrum:=""
res:=""

;mousegetpos,xm,ym
w:=h:=20  area right of cursor
xm:=300
ym:=300
screen:=xm "|" ym "|" w "|" h

pBitmap:=Gdip_BitmapFromScreen(screen)



E1 := Gdip_LockBits(pBitmap, 0, 0, w, h, Stride1, Scan1, BitmapData1)

x := -1
y := -1
xLoc := 0
yLoc := 0
Loop, %w%
  {
            Loop, %h%
              {
              spectrum .= format("{1:x}",Gdip_GetLockBitPixel(Scan1, xLoc, yLoc, Stride1)) "`n"
                         
              yLoc := yLoc + 1
              }
  xLoc := xLoc + 1
  yLoc :=0
  }
Gdip_UnlockBits(pBitmap1, BitmapData1)

Gdip_SaveBitmapToFile(pBitmap,"test.png")

Gdip_DisposeImage(pBitmap)

Gdip_Shutdown(pToken)
arr:={}
z:=""
m:=""
loop, Parse, spectrum, `n, `r
{
	if ( !Arr.Haskey(A_LoopField) )
		Arr[A_LoopField] := 1
	else
		Arr[A_LoopField] += 1
}	
for key, val in Arr
	z .= val "`t" key "`n"
	
Sort, z,N R

loop,parse,z,`n,`r
{
if A_Index=7
break
m .=A_LoopField "`n"
}


msgbox % a_tickcount-t "ms`n`n`n" m
exitapp

swagfag
Posts: 1536
Joined: 11 Jan 2017, 17:59

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 16:40

jeeswg wrote:

Code: Select all

; ...
hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap2)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_StrBinToHex(vAddr, vSize)
; ...
where tf do u come up with this shit, legit question

anyway, heres my go, done pixel by pixel the dumb way, using lockbits and arrays
youll need https://github.com/mmikeww/AHKv2-Gdip/b ... ip_All.ahk
and
noise.png
noise.png (1.97 MiB) Viewed 51 times
inside script folder

Code: Select all

#NoEnv
#WinActivateForce
#SingleInstance, Force
#MaxThreadsPerHotkey 2
#Warn, ClassOverwrite
SendMode, Input
SetBatchLines, -1
SetTitleMatchMode, 2
SetWorkingDir, %A_ScriptDir%

; https://github.com/mmikeww/AHKv2-Gdip/blob/master/Gdip_All.ahk
#Include, Gdip_All.ahk

pToken := initialize()

DebugModeController := new DebugModes()
mode := DebugModeController.cycle()
MsgBox, % Format("Current DebugMode set to [{}].`r`nTo change it press 'C'.", mode)

DllCall("QueryPerformanceFrequency", "Int64*", frequency)

x::
{
	DllCall("QueryPerformanceCounter", "Int64*", counterStart)
	DominantColors := getDominantColorScreen(10, mode)
	DllCall("QueryPerformanceCounter", "Int64*", counterStop)

	MsgBox, % print(DominantColors)

	elapsed := (counterStop - counterStart) * 1000
	elapsed /= frequency
	MsgBox, % "Executed in " . elapsed . " ms."
return
}

c::MsgBox, % Format("DebugMode changed to [{}]", mode := DebugModeController.cycle())
z::ExitApp

print(DominantColors) {
	for index, element in DominantColors {
		result .= Format("{}. `t{} `t{}`r`n", index, element.color, element.occurences) ; 1. 0xC0FFEE 385`r`n
	}

	return result
}

/*
	for testing/benchmark purposes
*/
createBitmapBasedOnMode(mode) {
	if (mode = "screen")
		return Gdip_BitmapFromScreen(1) ; main monitor

	if (mode = "debug")
		return Gdip_BitmapFromScreen("300|300|20|20") ; forum thread tests

	if (mode = "torture")
		return Gdip_CreateBitmapFromFile("noise.png") ; random color noise torture test

	throw Exception("Invalid mode specified!")
}

/*
	returns the specified amount of most dominant colors
*/
getDominantColorScreen(numDominantColors := 1, mode := "screen") {
	pBitmap := createBitmapBasedOnMode(mode)
	bitmapWidth := Gdip_GetImageWidth(pBitmap)
	bitmapHeight := Gdip_GetImageHeight(pBitmap)

	Gdip_LockBits(pBitmap, 0, 0, bitmapWidth, bitmapHeight, Stride, Scan0, BitmapData)

	ColorOccurences := getLockBitColorOccurences(bitmapWidth, bitmapHeight, Stride, Scan0)
	SortedColors := sortColorArray(ColorOccurences)
	DominantColors := pickDominantColorsFromSortedArray(SortedColors, numDominantColors)

	Gdip_UnlockBits(pBitmap, BitmapData)
	Gdip_DisposeImage(pBitmap)

	return DominantColors
}

/*
	retrieves the hex RGB of every pixel in the selected region sequentially
	computes the number of occurences of each pixel
	returns a data structure of the type:
	{
		"0xC0FFEE" : 385,
		"0x0DEAD0" : 22,
		"0x0BEEF0" : 1
	}
*/
getLockBitColorOccurences(width, height, stride, scan) {
	ColorOccurences := {}

	Loop, % width {
		x := A_Index - 1
		Loop, % height {
			y := A_Index - 1

			pixelColor := ARGBtoRGB(Gdip_GetLockBitPixel(scan, x, y, stride))

			if (ColorOccurences.HasKey(pixelColor . "")) {
				ColorOccurences[pixelColor . ""]++
			}
			else {
				ColorOccurences[pixelColor . ""] := 1
			}
		}
	}

	return ColorOccurences
}

/*
	sorts the passed array ascending in a way such that
	the key reflects the total number of times a given color has occured and
	the value contains an array of color, to allow for the case where
	multiple distinct colors have occured the same number of times
	return a data structure of the following type:
	{
		1 :
		{
			1 : "0x0BEEF0"
		},
		22 :
		{
			1 : "0x0DEAD0",
			2 : "0x1DEAD1"
		},
		385 :
		{
			1 : "0xC0FFEE"
		}
	}
*/
sortColorArray(Colors) {
	SortedColors := {}

	for pixelColor, numOccurences in Colors {
		if (SortedColors.HasKey(numOccurences)) {
			SortedColors[numOccurences].push(pixelColor)
		}
		else {
			SortedColors[numOccurences] := [pixelColor]
		}
	}

	return SortedColors
}

/*
	computes the numDominantColors most dominant colors
	if multiple colors have the same number of occurences,
	they are picked in the order that they appear in the data structure.
	returns example data structure for numDominantColors = 4:
	{
		1 : {"color" : "0xC0FFEE", "occurences" : 385 },
		2 : {"color" : "0x0DEAD0", "occurences" : 22 },
		3 : {"color" : "0x1DEAD1", "occurences" : 22 },
		4 : {"color" : "0x0BEEF0", "occurences" : 1 }
	}
*/
pickDominantColorsFromSortedArray(SortedColors, numDominantColors) {
	DominantColors := {}
	colorRank := 1

	Loop, % numDominantColors {
		if (colorRank > numDominantColors) {
			break
		}

		occurences := SortedColors.MaxIndex()
		lastElement := SortedColors.Pop()

		for index, color in lastElement {
			if (colorRank > numDominantColors) {
				break
			}

			DominantColors[colorRank] := {"occurences" : occurences, "color" : color}
			colorRank++
		}
	}

	return DominantColors
}

; ARBG to RGB conversion function
ARGBtoRGB(ARGB) {
	RGB := Format("0x{:X}", ARGB) ; prepend '0x' infront and convert to hex
	RGB := StrReplace(RGB, "0xFF", "0x") ; remove Alpha channel, leave only RGB
	return RGB
}

initialize() {
	checkForAdminPrivileges()
	pToken := initializeGdip()
	OnExit(Func("cleanUp").Bind(pToken))
	return
}

checkForAdminPrivileges() {
	if !(A_IsAdmin) {
		MsgBox, % "Run with administrative rights."
			. "`r`n" . "The script will now exit."
		ExitApp
	}
}

initializeGdip() {
	if !(pToken := Gdip_Startup()) {
		MsgBox, % "Failed to load GDI+."
			. "`r`n" . "The script will now exit."
		ExitApp
	}
	return pToken
}

cleanUp(pToken) {
	Gdip_Shutdown(pToken)
	ExitApp
}

class DebugModes {
	static currentMode := 0
	Modes := {1 : "SCREEN"
			, 2 : "DEBUG"
			, 3 : "TORTURE"}

	cycle() {
		if (++this.currentMode > this.Modes.Length()) {
			this.currentMode := 1
		}

		return this.Modes[this.currentMode]
	}
}
torture_20x20_results.PNG
torture_20x20_results.PNG (70.32 KiB) Viewed 568 times
torture_20x20_bench.PNG
torture_20x20_bench.PNG (65.63 KiB) Viewed 568 times
takes me about 14sec to run the 1080p color noise torture test with an i5 4690k@4.4ghz

on a side note, took me about an hour of trial/error to figure this shit out:

Code: Select all

numericStringKey := "0xC0FFEE"
array := {}
array[numericStringKey . ""] := "more undocumented v1 idiosyncrasies"
User avatar
jeeswg
Posts: 5406
Joined: 19 Dec 2016, 01:58
Location: UK

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 18:26

- @swagfag: In the past I'd tried to retrieve pixel info one pixel at a time, via PixelGetColor or the Gdip library's Gdip_GetPixel, but that seemed slow. I imagined that it would be faster to get the binary data for all of the pixels and convert it to hex, and parse it.
- The Gdip library allows you to save the data as a bmp (via Gdip_SaveBitmapToFile), and I could parse the file, although I'd have to understand bmps to know where the pixel data began. Bmp files start with structs e.g. DIBSECTION, and part of the information in the structs tells you where the data starts.
graphics: create bmp files from scratch - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=34952
- Common possibilities for storing colour data include: 4 bytes per pixel (BGRA where A is transparency), 3 bytes per pixel (BGR, blue/green/red), 1 byte per pixel (look it up in a lookup table of 256 colours), 1/2 byte per pixel (look it up in a lookup table of 16 colours), 1/8 byte = 1 bit per pixel (0 or 1, black or white).
- The simplest data to parse is 4-bytes-per-pixel BGRA data, this is because with other types of bmp, you get meaningless padding data, since file formats like to keep information in multiples of 4 bytes etc.
- It would seem plausible that instead of saving a file to disk first, there might be a more direct method to get hold of the bmp data. So I looked around, including investigating the Gdip library, I'm not sure where I found the answer eventually.
- Another thing to investigate would be the clipboard and clipboard formats, including image data. The clipboard e.g. for MS Paint, must be storing the image data somehow.
Last edited by jeeswg on 26 Apr 2018, 18:53, edited 1 time in total.
swagfag
Posts: 1536
Joined: 11 Jan 2017, 17:59

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 18:46

yeah i mean if u stop and think about what a bitmap actually is, it might eventually down upon one to skip the headers and read pixel data directly, but damn, never in a million years wouldve occurred to me

good effort man
User avatar
Off Topic
Posts: 43
Joined: 07 Oct 2017, 20:57

Re: I want to scan a given region for the 6 most common colors

26 Apr 2018, 21:42

Hey guys, thank you for all the replies and examples! I didn't expect this much and feel I'm going to need a few days to digest all of it, but I appreciate all the code given and have already tinkered with each. I'm going to try to get each to work and come at you individually for questions.

For a bit more context, I'm trying to use this on vector palettes like this one:
ScanTest.png
ScanTest.png (1.06 KiB) Viewed 522 times
So the amount of colors inside this click-drag region is going to be very low -- probably never more than 10. It's meant to be a way to snatch palettes and place the colors into a gui which I can use keys to then send into Illustrator as the active color (rather than individually using the eyedropper and clicking on each color every single time):

Image

In the above, I'm using PixelGetSearch for mouse position and it works well enough to drag over colors, but I'd like the ability to click and drag a region and instantly snatch all of them (excluding the color I start the click on, which would be the background color) for this gui or in general, which is why I'm asking for the most common 6 colors of a given region. To be honest, I've never touched GDIP before so a lot of this is a bit over my head and kind of daunting, I'm unsure of what I can and can't (or should and shouldn't) modify -- I've tried modifying them but I keep returning bad results or crashing AHK.
jeeswg wrote:To select a screen region:
selection rectangle: use mouse to select screen region - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42810
Thanks for this! I was planning on doing something similar after figuring this part out, but admittedly I'm having no luck trying to merge InputRect() with your other sample:

Image

So I can retrieve the dimensions correctly, but when I try to implement those dimensions into your sample (rather than the hard-coded ones you had) and run the color scan after the InputRect() ends then everything's showing up blank. It's a bit difficult to understand a code with this many working parts and things I'm unfamiliar with like GDIP, can you look this over and see what I'm doing incorrectly? I started a clean version of your sample and tried to modify as little as possible with comments where I've made changes:

Code: Select all

#NoEnv
#SingleInstance, Force
SetWorkingDir %A_ScriptDir%
#Include, Gdip_All.ahk

CoordMode, Mouse, Screen

;based on:
;Gdip: image binary data to hex string for OCR - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=35339

;[Gdip functions]
;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

q:: ;get frequency count for pixels in a rectangle

Scan:     ; Wanted to make this a label so I can GoSub it
pToken := Gdip_Startup()

; vPosX := 300, vPosY := 300
; vPosW := 300, vPosH := 300

vImgPos := Format("{}|{}|{}|{}", originX, originY, vW0, vH0)      ; here

pBitmap := Gdip_BitmapFromScreen(vImgPos)
pBitmap2 := Gdip_CloneBitmapArea(pBitmap, 0, 0, vW0, vH0, 0x26200A)       ; here
hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap2)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_StrBinToHex(vAddr, vSize)
Clipboard := vHex
;get pixel count:
MsgBox, % Round(StrLen(vHex) / 8) " " (vW0 * vH0)        ; here

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_DisposeImage(pBitmap2)
Gdip_Shutdown(pToken)

MsgBox, % vHex
vOutput := RegExReplace(vHex, "(.{6})..", "$1,") ;BGR
vOutput := SubStr(vOutput, 1, -1)
MsgBox, % vOutput

oArray := {}
Loop, Parse, vOutput, % ","
	if oArray.HasKey("z" A_LoopField)
		oArray["z" A_LoopField]++
	else
		oArray["z" A_LoopField] := 1
;frequency count for pixel colours (BGR)
vOutput2 := ""
for vKey, vValue in oArray
	vOutput2 .= vValue "`t" SubStr(vKey, 2) "`r`n"
Clipboard := vOutput2
MsgBox, % vOutput2
return

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

;JEE_BinDataToHex
JEE_StrBinToHex(vAddr, vSize, vCase:="U")
{
	static vIsVistaPlus := (DllCall("kernel32\GetVersion", UInt) & 0xFF) >= 6
	if vIsVistaPlus
	{
		;CRYPT_STRING_NOCRLF := 0x40000000
		;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4000000C, Ptr,0, UIntP,vChars)
		VarSetCapacity(vHex, vChars*2, 0)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4000000C, Str,vHex, UIntP,vChars)
	}
	else
	{
		;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
		VarSetCapacity(vHex, vChars*2, 0)
		DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
		vHex := StrReplace(vHex, "`r`n")
		vHex := StrReplace(vHex, " ")
	}
	if (vCase = "L")
		return vHex
	else
		return Format("{:U}", vHex)
}

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

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

;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

#LButton::
MouseGetPos, originX, originY
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: -DPIScale
	Gui, 1: Color, Red
	WinSet, Transparent, 128, % "ahk_id " hGuiSel
	Hotkey, *LButton, InputRect_End, 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
		MsgBox % "x" vX1 " y" vY1 " w" vW0 " h" vH0       ; To verify dimensions are correct
		ToolTip
		Gosub, Scan
	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)
			vW0 := (vX2-vX1), vH0 := (vY2-vY1)
			Gui, 1:Show, % "NA x" vX1 " y" vY1 " w" vW0 " h" vH0
			ToolTip % "x" vX1 " y" vY1 " w" vW0 " h" vH0
		return
	}
	vInputRectState := 1

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


	InputRect_Return:
	return
}


;==================================================
swagfag
Posts: 1536
Joined: 11 Jan 2017, 17:59

Re: I want to scan a given region for the 6 most common colors

27 Apr 2018, 05:46

Image

ive copypasted #LButton::, Scan: and InputRect() from your script and amended mine to include a bunch of other functions

Code: Select all

/*
	when passed an array of coordinates [x, y, w, h]
	returns a data structure containing the specifien N most dominant colors,
	from the region enclosed by the specified coordinates
*/
getDominantColorFromRegion(numDominantColors, region)
returns an object sorted in ascending order of the type:

Code: Select all

{
	1 : {
		"color" : "0xC0FFEE",
		"occurences" : 385
	},
	2 : {
		"color" : "0x0DEAD0",
		"occurences" : 22
	},
	3 : {
		"color" : "0x1DEAD1",
		"occurences" : 22
	},
	4 : {
		"color" : "0x0BEEF0",
		"occurences" : 1
	}
}
on a side note this rect selection script is giving me real wonky results, occasionally releasing my LButton and producing bogus coordinates, id look into using something else
nvm, i think my LMB switch might be dieing
GDIP_LockBits_DominantColor.ahk
(8.1 KiB) Downloaded 11 times

Code: Select all

#NoEnv
#WinActivateForce
#SingleInstance, Force
#MaxThreadsPerHotkey 2
#Warn, ClassOverwrite
SendMode, Input
SetBatchLines, -1
SetTitleMatchMode, 2
SetWorkingDir, %A_ScriptDir%
CoordMode, Mouse, Screen

; https://github.com/mmikeww/AHKv2-Gdip/blob/master/Gdip_All.ahk
#Include, Gdip_All.ahk

pToken := initialize()

DebugModeController := new DebugModes()
mode := DebugModeController.cycle()
MsgBox, % Format("Current DebugMode set to [{}].`r`nTo change it press 'C'.", mode)

DllCall("QueryPerformanceFrequency", "Int64*", frequency)
Return

Scan:
{
	numDominantColors := 10
	dimensions := [vWinX, vWinY, vWinW, vWinH]
	DominantColors := getDominantColorFromRegion(numDominantColors, dimensions)
	MsgBox, % print(DominantColors)
return
}

#LButton::
{
	MouseGetPos, originX, originY
	InputRect(vWinX, vWinY, vWinR, vWinB)
	vWinW := vWinR-vWinX, vWinH := vWinB-vWinY

	if (vInputRectState = -1) {
		return
	}
return
}

x::
{
	DllCall("QueryPerformanceCounter", "Int64*", counterStart)
	DominantColors := getDominantColorFromScreen(10)
	DllCall("QueryPerformanceCounter", "Int64*", counterStop)

	MsgBox, % print(DominantColors)

	elapsed := (counterStop - counterStart) * 1000
	elapsed /= frequency
	MsgBox, % "Executed in " . elapsed . " ms."
return
}

c::MsgBox, % Format("DebugMode changed to [{}]", mode := DebugModeController.cycle())
z::ExitApp

print(DominantColors) {
	for index, element in DominantColors {
		result .= Format("{}. `t{} `t{}`r`n", index, element.color, element.occurences) ; 1. 0xC0FFEE 385`r`n
	}

	return result
}

printError(e) {
	options := 16
	title := "Error!"
	text := Format("Error: `t{}`nFunc: `t{}`nLine: `t{}", e.Message, e.What, e.Line)
	MsgBox, % options, % title, % text
}


/*
	for testing/benchmark purposes
*/
createBitmapBasedOnMode(mode) {
	if (IsObject(mode) && mode.Length() == 4)
		return Gdip_BitmapFromScreen(Format("{}|{}|{}|{}", mode*)) ; format => "x|y|w|h"

	if (mode = "screen")
		return Gdip_BitmapFromScreen(1) ; main monitor

	if (mode = "debug")
		return Gdip_BitmapFromScreen("300|300|20|20") ; forum thread tests

	if (mode = "torture")
		return Gdip_CreateBitmapFromFile("noise.png") ; random color noise torture test

	throw Exception("Invalid mode specified!")
}

/*
	returns the specified amount of most dominant colors
*/
getDominantColor(numDominantColors := 1, mode := "screen") {
	try
		pBitmap := createBitmapBasedOnMode(mode)
	catch e {
		printError(e)
		return 0
	}

	bitmapWidth := Gdip_GetImageWidth(pBitmap)
	bitmapHeight := Gdip_GetImageHeight(pBitmap)

	Gdip_LockBits(pBitmap, 0, 0, bitmapWidth, bitmapHeight, Stride, Scan0, BitmapData)

	ColorOccurences := getLockBitColorOccurences(bitmapWidth, bitmapHeight, Stride, Scan0)
	SortedColors := sortColorArray(ColorOccurences)
	DominantColors := pickDominantColorsFromSortedArray(SortedColors, numDominantColors)

	Gdip_UnlockBits(pBitmap, BitmapData)
	Gdip_DisposeImage(pBitmap)

	return DominantColors
}

/*
	when passed an array of coordinates [x, y, w, h]
	returns a data structure containing the specifien N most dominant colors,
	from the region enclosed by the specified coordinates
*/
getDominantColorFromRegion(numDominantColors, region) {
	return getDominantColor(numDominantColors, region)
}

/*
	returns a data structure containing the specified N most dominant colors,
	from the main monitor
*/
getDominantColorFromScreen(numDominantColors) {
	return getDominantColor(numDominantColors, "screen")
}

/*
	retrieves the hex RGB of every pixel in the selected region sequentially
	computes the number of occurences of each pixel
	returns a data structure of the type:
	{
		"0xC0FFEE" : 385,
		"0x0DEAD0" : 22,
		"0x0BEEF0" : 1
	}
*/
getLockBitColorOccurences(width, height, stride, scan) {
	ColorOccurences := {}

	Loop, % width {
		x := A_Index - 1
		Loop, % height {
			y := A_Index - 1

			pixelColor := ARGBtoRGB(Gdip_GetLockBitPixel(scan, x, y, stride))

			if (ColorOccurences.HasKey(pixelColor . "")) {
				ColorOccurences[pixelColor . ""]++
			}
			else {
				ColorOccurences[pixelColor . ""] := 1
			}
		}
	}

	return ColorOccurences
}

/*
	sorts the passed array ascending in a way such that
	the key reflects the total number of times a given color has occured and
	the value contains an array of color, to allow for the case where
	multiple distinct colors have occured the same number of times
	return a data structure of the following type:
	{
		1 :
		{
			1 : "0x0BEEF0"
		},
		22 :
		{
			1 : "0x0DEAD0",
			2 : "0x1DEAD1"
		},
		385 :
		{
			1 : "0xC0FFEE"
		}
	}
*/
sortColorArray(Colors) {
	SortedColors := {}

	for pixelColor, numOccurences in Colors {
		if (SortedColors.HasKey(numOccurences)) {
			SortedColors[numOccurences].push(pixelColor)
		}
		else {
			SortedColors[numOccurences] := [pixelColor]
		}
	}

	return SortedColors
}

/*
	computes the numDominantColors most dominant colors
	if multiple colors have the same number of occurences,
	they are picked in the order that they appear in the data structure.
	returns example data structure for numDominantColors = 4:
	{
		1 : {"color" : "0xC0FFEE", "occurences" : 385 },
		2 : {"color" : "0x0DEAD0", "occurences" : 22 },
		3 : {"color" : "0x1DEAD1", "occurences" : 22 },
		4 : {"color" : "0x0BEEF0", "occurences" : 1 }
	}
*/
pickDominantColorsFromSortedArray(SortedColors, numDominantColors) {
	DominantColors := {}
	colorRank := 1

	Loop, % numDominantColors {
		if (colorRank > numDominantColors) {
			break
		}

		occurences := SortedColors.MaxIndex()
		lastElement := SortedColors.Pop()

		for index, color in lastElement {
			if (colorRank > numDominantColors) {
				break
			}

			DominantColors[colorRank] := {"occurences" : occurences, "color" : color}
			colorRank++
		}
	}

	return DominantColors
}

; ARBG to RGB conversion function
ARGBtoRGB(ARGB) {
	RGB := Format("0x{:X}", ARGB) ; prepend '0x' infront and convert to hex
	RGB := StrReplace(RGB, "0xFF", "0x") ; remove Alpha channel, leave only RGB
	return RGB
}

initialize() {
	checkForAdminPrivileges()
	pToken := initializeGdip()
	OnExit(Func("cleanUp").Bind(pToken))
	return
}

checkForAdminPrivileges() {
	if !(A_IsAdmin) {
		MsgBox, % "Run with administrative rights."
			. "`r`n" . "The script will now exit."
		ExitApp
	}
}

initializeGdip() {
	if !(pToken := Gdip_Startup()) {
		MsgBox, % "Failed to load GDI+."
			. "`r`n" . "The script will now exit."
		ExitApp
	}
	return pToken
}

cleanUp(pToken) {
	Gdip_Shutdown(pToken)
	ExitApp
}

class DebugModes {
	static currentMode := 0
	Modes := {1 : "SCREEN"
			, 2 : "DEBUG"
			, 3 : "TORTURE"}

	cycle() {
		if (++this.currentMode > this.Modes.Length()) {
			this.currentMode := 1
		}

		return this.Modes[this.currentMode]
	}
}

InputRect(ByRef vX1, ByRef vY1, ByRef vX2, ByRef vY2) {
	global vInputRectState := 0
	DetectHiddenWindows, On
	Gui, 1: -Caption +ToolWindow +AlwaysOnTop +hWndhGuiSel
	Gui, 1: -DPIScale
	Gui, 1: Color, Red
	WinSet, Transparent, 128, % "ahk_id " hGuiSel
	Hotkey, *LButton, InputRect_End, 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
		; MsgBox % "x" vX1 " y" vY1 " w" vW0 " h" vH0       ; To verify dimensions are correct
		ToolTip
		Gosub, Scan
	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)
			vW0 := (vX2-vX1), vH0 := (vY2-vY1)
			Gui, 1:Show, % "NA x" vX1 " y" vY1 " w" vW0 " h" vH0
			ToolTip % "x" vX1 " y" vY1 " w" vW0 " h" vH0
		return
	}
	vInputRectState := 1

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


	InputRect_Return:
	return
}

Return to “Ask For Help”

Who is online

Users browsing this forum: blad4, Google [Bot], Hellbent, jNizM, Nneran and 45 guests