Что нового

FindPixel - поиск пикселей

Prog

Продвинутый
Сообщения
592
Репутация
72
Небольшая библиотека для быстрого поиска пикселей. Описание функций в архиве.
Пример.
Код:
#include <WinAPIGdi.au3>

$dll=DllOpen("FindPixel.dll")
$t=TimerInit()

; FindPixel(x, y, Width, Height, Color, Shade, Steps, hWnd)
$Ret=DllCall($dll, "int", "FindPixel", "int",  0, "int",  0, "int",  1000, "int",  700, "int", _WinAPI_RGB(255, 0, 0), "int", 0, "int", 1, "int", 0)

ConsoleWrite("time "&TimerDiff($t)&" ms" & @CRLF)
$Ret=$Ret[0]
if $Ret>=0 then
   ConsoleWrite("Pixel found: X=" & BitAND($Ret, 0xFFFF) & "    Y="& BitAND(BitShift($Ret, 16), 0xFFFF) & @CRLF)
Else
  if $Ret=-1 Then
	 ConsoleWrite("Error!" & @CRLF)
  elseif $Ret=-2 Then
	 ConsoleWrite("Pixel not found" & @CRLF)
  endif
endif
DllClose($dll)
Правда функция _WinAPI_RGB работает не так как ожидается. Она кодирует не в формате RGB, а формате BGR. Учитывайте это.
 

Вложения

  • FindPixel_v1.0.zip
    32 КБ · Просмотры: 113

InnI

AutoIT Гуру
Сообщения
4,950
Репутация
1,444
Первый же тест быстродействия не обнаружил различий в скорости
Код:
1858.17576760174
1848.00372551522
Код:
; PixelSearch
$t = TimerInit()
For $i = 0 To 50
  PixelSearch(0, 0, @DesktopHeight, @DesktopWidth, 0x112233)
Next
ConsoleWrite(TimerDiff($t) & @CRLF)

; FindPixel
$dll = DllOpen("FindPixel.dll")
$t = TimerInit()
For $i = 0 To 50
  $Ret = DllCall($dll, "int", "FindPixel", "int",  0, "int",  0, "int",  @DesktopHeight, "int",  @DesktopWidth, "int", 0x112233, "int", 0, "int", 1, "int", 0)
Next
ConsoleWrite(TimerDiff($t) & @CRLF)
DllClose($dll)
Чем вы мотивируете пользователей к использованию вашей dll?

Не понял, как работать с буфером? Вероятно, в функцию нужно передать указатель на буфер с картинкой? Примерчик бы...

А почему нет функции получения цвета пикселя сразу с экрана, без работы с буфером?

И самое главное. Нужна функция, аналогичная FindPixel(), но возвращающая двумерный массив координат найденных цветов. Если эта функция по скорости будет сопоставима с PixelSearch() - вот это будет мотивация.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
InnI [?]
Не понял, как работать с буфером? Вероятно, в функцию нужно передать указатель на буфер с картинкой? Примерчик бы...
Буфер это просто картинка, сохраненная в памяти. Поместить картинку в буфер можно одной из следующих функций:
Код:
FindPixel()
ScreenToBuff()
ImageLoadMem()
ImageLoad()
Поскольку картинка уже загружена, то поиск по ней будет проходить намного быстрее, ведь отсутствует необходимость ее постоянно подгружать. И если на картинке нужно найти несколько пикселей, скорость окажется выше чем у AutoIt-функции PixelSearch.


Нужна функция, аналогичная FindPixel(), но возвращающая двумерный массив координат найденных цветов.
Это возможно, но функция вернет не массив в формате AutoIt, а указатель на начало двумерного массива.
 

InnI

AutoIT Гуру
Сообщения
4,950
Репутация
1,444
Prog
С буфером разобрался. Получается, у вас один буфер на всю dll, а-ля буфер обмена Windows - текущие данные затирают предыдущие.

функция вернет не массив в формате AutoIt
В чём проблема? Создайте простенькую UDF, работающую с вашей dll. Иначе круг её пользователей будет сильно ограничен.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
InnI [?]
Получается, у вас один буфер на всю dll, а-ля буфер обмена Windows - текущие данные затирают предыдущие.
Да. Для моих задач этого было достаточно.
 

InnI

AutoIT Гуру
Сообщения
4,950
Репутация
1,444
dll работает с перекрытыми и "задвинутыми" окнами. Это плюс.

Код:
Color - Задает цвет для поиска, должен быть в формате RGB.
А мои опыты показывают, что формат должен быть BGR. Непривычно.

И ещё. В описаниях функций, где присутствует hWnd, указано, что координаты относительно окна. На самом деле - относительно рабочей области окна (без заголовка и меню).
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
InnI [?]
Нужна функция, аналогичная FindPixel(), но возвращающая двумерный массив координат найденных цветов.
Добавил функции FindPixel_Buff_ToArr() и GetPixelArr().
Пример поиска в файле.
Код:
#include <WinAPIGdi.au3>

$dll=DllOpen("FindPixel.dll")
$t=TimerInit()

; ImageLoad(FileName)
$Ret=DllCall($dll, "int", "ImageLoad", "str",  "Image.bmp")

if $Ret[0]=0 Then
  ConsoleWrite("Image not found" & @CRLF)
else
  ; FindPixel_Buff_ToArr(x, y, Width, Height, Color, Shade, Steps)
  $Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int",  0, "int",  0, "int",  90, "int",  90, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)

  ConsoleWrite("time "&TimerDiff($t)&" ms" & @CRLF)
  $Count=$Ret[0]
  ConsoleWrite("Count pixel "&$Count & @CRLF)
  if $Count>0 then

	 $Arr = DllStructCreate("dword[" & $Count & "]")

	 ; GetPixelArr(*Buff, Count)
	 $Ret=DllCall($dll, "int", "GetPixelArr", "ptr",  DllStructGetPtr($Arr), "Int", $Count)
	 For $i=1 to $Ret[0]
	   $Pos = DllStructGetData($Arr, 1, $i)
       ConsoleWrite("Pixel found: X=" & BitAND($Pos, 0xFFFF) & "    Y="& BitAND(BitShift($Pos, 16), 0xFFFF) & @CRLF)
     Next

  endif
endif
DllClose($dll)
 

Вложения

  • FindPixel v1.1.zip
    36 КБ · Просмотры: 24

InnI

AutoIT Гуру
Сообщения
4,950
Репутация
1,444
Prog
Пример поиска в файле
Изменил на
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int",  20, "int",  60, "int",  40, "int",  40, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)
Результат: -1. Тогда сделал так
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int",  20, "int",  60, "int",  80, "int",  100, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)
Результат: 2.
Получается, что параметры не "Width, Height", а "Right, Bottom".

Следующий поиск для вашего примера
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int",  15, "int",  10, "int",  45, "int",  90, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)
находит пиксель в координатах 10:18, хотя данная координата не входит в область поиска.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
InnI [?]
Получается, что параметры не "Width, Height", а "Right, Bottom".
Изначально задумывалось именно как Width и Height, но по факту действительно работает как Right и Bottom. Исправил.


находит пиксель в координатах 10:18, хотя данная координата не входит в область поиска.
Входит, т. к. ищет слева на право и сверху вниз. При переходе на новую строку, начинает искать с нулевой позиции X.
Это было сделано для того чтобы была возможность найти все пиксели требуемого цвета.
Код:
#include <WinAPIGdi.au3>

Const $FP_SM_Def = 0  ; Метод поиска по умолчанию - построчно слева на право и сверху вниз.
Const $FP_SM_Clip = 1 ; Метод поиска с отсечением пикселей по координате X.

$dll=DllOpen("FindPixel.dll")

; ImageLoad(FileName)
$Ret=DllCall($dll, "int", "ImageLoad", "str",  "Image.bmp")

if $Ret[0]=0 Then
  ConsoleWrite("Image not found" & @CRLF)
else

  ; SetSearchMode(Mode)
  DllCall($dll, "int", "SetSearchMode", "int", $FP_SM_Def)

 $Ret = 0

 Do
   ; FindPixel_Buff(x, y, Width, Height, Color, Shade, Steps)
   $Ret=DllCall($dll, "int", "FindPixel_Buff", "int",  BitAND($Ret, 0xFFFF), "int",   BitAND(BitShift($Ret, 16), 0xFFFF), "int",  100, "int",  100, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)

   $Ret=$Ret[0]

   if $Ret>0 then
    ConsoleWrite("Pixel found: X=" & BitAND($Ret, 0xFFFF) & "    Y="& BitAND(BitShift($Ret, 16), 0xFFFF) & @CRLF)
    $Ret+=1
   endif

  until $Ret<0

endif
DllClose($dll)


Но можно с помощью функции SetSearchMode изменить логику поиска.
Код:
#include <WinAPIGdi.au3>

Const $FP_SM_Def = 0  ; Метод поиска по умолчанию - построчно слева на право и сверху вниз.
Const $FP_SM_Clip = 1 ; Метод поиска с отсечением пикселей по координате X.

$dll=DllOpen("FindPixel.dll")
$t=TimerInit()

; ImageLoad(FileName)
$Ret=DllCall($dll, "int", "ImageLoad", "str",  "Image.bmp")

if $Ret[0]=0 Then
  ConsoleWrite("Image not found" & @CRLF)
else

; SetSearchMode(Mode)
DllCall($dll, "int", "SetSearchMode", "int", $FP_SM_Clip)

; FindPixel_Buff_ToArr(x, y, Width, Height, Color, Shade, Steps)
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int",  15, "int",  10, "int",  45, "int",  90, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)

  ConsoleWrite("time "&TimerDiff($t)&" ms" & @CRLF)
  $Count=$Ret[0]
  ConsoleWrite("Count pixel "&$Count & @CRLF)
  if $Count>0 then

	 $Arr = DllStructCreate("dword[" & $Count & "]")

	 ; GetPixelArr(*Buff, Count)
	 $Ret=DllCall($dll, "int", "GetPixelArr", "ptr",  DllStructGetPtr($Arr), "Int", $Count)
	 For $i=1 to $Ret[0]
	   $Pos = DllStructGetData($Arr, 1, $i)
       ConsoleWrite("Pixel found: X=" & BitAND($Pos, 0xFFFF) & "    Y="& BitAND(BitShift($Pos, 16), 0xFFFF) & @CRLF)
     Next

  endif
endif
DllClose($dll)
 

Вложения

  • FindPixel_v1.2.zip
    38 КБ · Просмотры: 14

InnI

AutoIT Гуру
Сообщения
4,950
Репутация
1,444
Prog
При переходе на новую строку, начинает искать с нулевой позиции X.
Логика, мягко говоря, странная. Сначала задаём область поиска, затем эту область игнорируем. Лучше бы вы сделали поиск "с отсечением" по умолчанию.

Следующий вызов приводит к аварийному завершению работы AutoIt
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int", 0, "int", 0, "int", 0, "int", 0, "int", _WinAPI_RGB(0, 0, 255), "int", 0, "int", 1)


Теперь ищем серый цвет в вашем примере
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int", 0, "int", 0, "int", 1, "int", 1, "int", _WinAPI_RGB(200, 200, 200), "int", 0, "int", 1)
и находим два пикселя, вместо одного. При поиске
Код:
$Ret=DllCall($dll, "int", "FindPixel_Buff_ToArr", "int", 0, "int", 0, "int", 2, "int", 2, "int", _WinAPI_RGB(200, 200, 200), "int", 0, "int", 1)
находим семь пикселей вместо четырёх. При поиске 3х3 будет найдено 13 пикселей. Поиск в области 1х0 находит два пикселя, а поиск в области 0х1 крашит AutoIt.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
InnI [?]
Логика, мягко говоря, странная.
Такой метод позволяет найти все пиксели требуемого цвета без использования FindPixel_Buff_ToArr. Пример выше.

Все остальные ошибки исправил.
 

Вложения

  • FindPixel_1.3.zip
    38 КБ · Просмотры: 39

iamOmg

Новичок
Сообщения
97
Репутация
2
Библиотека отличная, было бы классно если бы ещё поиск по картинке добавить.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
Есть библиотека BmpSearch, чем она не подошла? Скоростью работы или функционалом?
Практика показывает что вариантов поиска может быть много (например поиск с процентным совпадением, или отклонением цветов, многопоточный поиск и т. д.) и реализация их всех потребует много времени. Проще сделать библиотеку с требуемым функционалом по индивидуальному заказу.
 

Yura

Знающий
Сообщения
36
Репутация
7
Есть у меня Bitmap в памяти, который я получил через загрузку картинки средствами WinAPI или после скриншота или еще как-то, надо загрузить в буфер через ImageLoadMem(Mem, Size). Описание: Функция загружает в буфер BMP картинку из памяти. Если выполнено успешно, вернет не 0. Может я туплю, но мне такое описание мало о чем говорит. Нужно описание каждого параметра- что это такое, и если нет обертки в виде функции вызова dll, то надо тип параметров еще. Я вот час убил гадая подбирая и все равно толку 0.
Например, я получил $hBitmap из картинки через _WinAPI_LoadImage. Mem- это может быть $hBitmap? Что за Size? Тот, что через _WinAPI_GetBitmapDimension получается или ширина картинки умноженная на высоту или размер памяти, которую она занимает? :stars:
Вот набросок функции-обертки, подправьте параметры, пожалуйста.
Код:
$hBMP = _WinAPI_LoadImage(0, 'image.bmp', $IMAGE_BITMAP, 0, 0, $LR_LOADFROMFILE); загрузка image.bmp (в папке скрипта)
;Как теперь положить в буфер $hBMP?
$tSIZE = _WinAPI_GetBitmapDimension($hBMP)
$hBMP_Size = DllStructGetData($tSIZE, 'X') * DllStructGetData($tSIZE, 'Y'); длинна*высоту BMP

$Ret = _ImageLoadFromMemory($hBMP, $hBMP_Size)
If @error Then
	ConsoleWrite("An error occurred."&@CR)
Else
	ConsoleWrite($Ret&@CR)
EndIf

;-------------------------------------------------------------
Func _ImageLoadFromMemory($hBMP, $Size) ;обертка для вызова длл
	$Result = DllCall('FindPixel.dll', 'int', 'ImageLoadMem', 'handle', $hBMP, 'int', $Size)
	If @error Then Return SetError(1)

	Return $Result[0]
EndFunc
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Mem - это адрес на буфер с данными битмапа (пикселами), а не хэндл на битмап.

Код:
$hDIB = _WinAPI_CopyBitmap($hBitmap)
$tDIB = DllStructCreate($tagDIBSECTION)
_WinAPI_GetObject($hDIB, DllStructGetSize($tDIB), DllStructGetPtr($tDIB))
_WinAPI_DeleteObject($hDIB)
; $tDIB.bmBits - это то, что нужно


_WinAPI_CopyBitmap() необходима, если указанный битмап DDB, иначе $tDIB.bmBits будет содержать 0.

Size - размер буфера в байтах. Подозреваю, что данная DLL работает только с 32-битными битмапами, поэтому

Код:
$Size = $tDIB.bmWidth * $tDIB.bmHeight * 4
 

Yura

Знающий
Сообщения
36
Репутация
7
Спасибо за объяснение! Попытка №2:
Код:
$hDIB = _WinAPI_CopyBitmap($hBMP)
$tDIB = DllStructCreate($tagDIBSECTION)
_WinAPI_GetObject($hDIB, DllStructGetSize($tDIB), DllStructGetPtr($tDIB))
_WinAPI_DeleteObject($hDIB)

$Size = $tDIB.bmWidth * $tDIB.bmHeight * 4

$Ret = _ImageLoadFromMemory($tDIB.bmBits, $Size)
;$Ret = _ImageLoadFromMemory($tDIB, $Size)
If @error Then
    ConsoleWrite("An error occurred."&@CR)
Else
    ConsoleWrite($Ret&@CR)
EndIf

;-------------------------------------------------------------
Func _ImageLoadFromMemory($Mem, $Size) ;обертка для вызова длл
    $Result = DllCall('FindPixel.dll', 'int', 'ImageLoadMem', 'int*', $Mem, 'int', $Size) ;  "int*" правильно?? 
    If @error Then Return SetError(1)

    Return $Result[0]
EndFunc


Если работает с 32 битными битмапами, то я еще $hBMP перед кодом выше переделал и добавил 4й байт на альфа канал (может это лишнее или неправильно?):
Код:
$hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hBMP)
	_WinAPI_DeleteObject($hBMP)
	$hBitmap_clone = _GDIPlus_BitmapCloneArea($hBitmap, 0, 0, $hBMP_width, $hBMP_height,  $GDIP_PXF32PARGB);$GDIP_PXF24RGB)
;_GDIPlus_ImageSaveToFile($hBitmap_clone, "!.bmp")
	_GDIPlus_BitmapDispose($hBitmap)
	$hBMP = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hBitmap_clone)
	_GDIPlus_BitmapDispose($hBitmap_clone)


С ерором не вылетает, но возвращает 0- не загрузило. Наверно указатель на буфер с данными битмапа ( 'int*', $Mem) неправильно пишу?
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
Yura [?]
Функция загружает в буфер BMP картинку из памяти. Если выполнено успешно, вернет не 0. Может я туплю, но мне такое описание мало о чем говорит. Нужно описание каждого параметра
Первый параметр - адрес памяти, где находится картинка, а второй, размер области памяти. Картинка должна быть в том же виде как она хранится на диске.
Код функции ImageLoadMem.
Код:
ProcedureDLL ImageLoadMem(*Mem, Size)
  Protected Result = #False
  
  If *Mem And Size>0
    Result = CatchImage(#Image, *Mem, Size)
  EndIf
  
  ProcedureReturn Result
EndProcedure
Описание функции CatchImage

Yashied сказал(а):
Подозреваю, что данная DLL работает только с 32-битными битмапами
По идее должна работать со всеми распространенными форматами.

Добавил новую функцию
Загружает картинку по ее дескриптору.
Заодно выкладываю исходник библиотеки. Может кому-то пригодится.
 

Вложения

  • FindPixel_1.4.zip
    41.4 КБ · Просмотры: 29

Yura

Знающий
Сообщения
36
Репутация
7
По хэндлу удалось загрузить и найти пиксель. Оформил в виде функций:
Код:
Func _FPImageLoadByHandle($hBMP)
    $Result = DllCall('FindPixel.dll', 'int', 'ImageLoadHandle', 'handle', $hBMP)
    If @error Then Return SetError(1)

    Return $Result[0]
EndFunc

Func _FPPixel_Buff($X0, $Y0, $Width, $Height, $Color, $Shade = 0, $Steps = 1)
    $Result = DllCall('FindPixel.dll', 'int', 'FindPixel_Buff', 'int', $X0, 'int', $Y0, 'int', $Width, 'int', $Height, 'int', $Color, 'int', $Shade, 'int', $Steps)
    If @error Then Return SetError(1)
	If $Result[0] == -1 Or $Result[0] == -2 Then Return $Result[0]

	Local $aCoord[2]
	$aCoord[0] = BitAND($Result[0], 0xFFFF)
	$aCoord[1] = BitAND(BitShift($Result[0], 16), 0xFFFF)
    Return $aCoord
EndFunc

Вывел сравнение скорости поиска (только поиска, когда картинка уже в буфере):
FFNearestPixel() => X = 670, Y = 345, Time = 1.49996069625172
FindPixel() => X = 670, Y = 345, Time = 12.138375126319
Либа FastFind написана на плюсах, эта либа на PureBasic, а он вроде как не должен быть медленней, тем более в 7 раз? Пришлось скачать PureBasic и посмотреть исходник. Вы разлагаете цвет на 3 составляющие и каждый раз в цикле проверяете- находится ли каждая составляющая в нужном диапазоне. Это отлично, что есть параметр Shade! Но если Shade=0, а в большинстве случаев так и есть, то делается куча лишних телодвижений. FFNearestPixel без параметра Shade, на этом получается и выигрывает. Можете добавить в начале поиска проверку и если Shade=0, то сравнивать сразу весь цвет без разложения на составляющие? Самому если делать, то надо еще в незнакомый синтаксис вникать. Если что не так понял, извиняйте)

Вы уверенны, что: Color - Задает цвет для поиска, должен быть в формате RGB? Я создал в пейнте черный рисунок с 1 пикселем цвета 0xB5E61D. Цвет смотрю в ControlViewer в формате RGB. Нужный пиксель по нужным координатам FindPixel() нашел. Но когда я задал $Сolor = 0x1DE6B5, то есть в BGR.

В PureBasic вы для доступа к загруженному изображению используете "#Image" (The number by which to identify the loaded image. #PB_Any can be specified to autogenerate this number. ) Я правильно понимаю, что если во все функции добавить еще один параметр- число, которое будет передаваться в #Image, то можно будет использовать буфер под номером=это число, то есть можно было бы использовать несколько буферов? (например для отслеживания динамических изменений на экране).

ImageLoadHandle(hBmp) есть, обратная функция- получение hBmp из буфера, если это возможно, то тоже было бы отлично. Чтоб можно было скриншотить через вашу либу, а когда надо чтоб была возможность его получить не делая еще один скриншот в AutoIt- это трата времени, иногда критическая. По сути если есть 2 такие функции передачи битмепов, то уже есть хорошая стыковка с AutoIt. ImageLoad, ImageSave можно сделать и средствами AutoIt'а имея битмеп.
 
Автор
P

Prog

Продвинутый
Сообщения
592
Репутация
72
Разница может быть из-за различий в алгоритме. Например, в одном случае начинает искать с левого верхнего угла, а в другом случае, в нижнего правого угла. Пиксель по тем же координатам будет найден за разное время.

Внес измерения в библиотеку.
 

Вложения

  • FindPixel.zip
    31.8 КБ · Просмотры: 26

Yura

Знающий
Сообщения
36
Репутация
7
Prog сказал(а):
Разница может быть из-за различий в алгоритме. Например, в одном случае начинает искать с левого верхнего угла, а в другом случае, в нижнего правого угла. Пиксель по тем же координатам будет найден за разное время.
Я учел это при выборе пикселя для поиска- я его находил посредине монитора, так что с какого бы угла не искали... Алгоритм FFNearestPixel мне понятен- он ищет ближайший пиксель к заданной точке. Ищет расширяя область по радиусу, зона поиска- круг. Начальные координаты дал ему (0,0). Так что оба алгоритма, ваш и тот, пока нашли проверили приблизительно одинаковую площадь.

Кстати быстрые поиски нужны чаще всего ботописцам на игрушки, когда каждая милисекунда на счету. Поиск расширяя зону по кругу в этом случае часто имеет одно преимущество- как правило ищется моб или обьект ближайший к игроку (которого делаем точкой начала поиска) из множества других мобов.
 
Верх