Pixel Search Function – AHK

Forums Pixel Search Function – AHK

This topic contains 0 replies, has 1 voice, and was last updated by Spitt 1:28 am on 23 July, 2018.

  • Author
    Posts
  • #44325

    Spitt
    1. #include “stdafx.h”
    2. #include <Windows.h>
    3. #include <iostream>
    4.  
    5. COLORREF rgb_to_bgr(DWORD aRGB)
    6. // Fancier methods seem prone to problems due to byte alignment or compiler issues.
    7. {
    8. return RGB(GetBValue(aRGB), GetGValue(aRGB), GetRValue(aRGB));
    9. }
    10.  
    11. LPCOLORREF getbits(HBITMAP ahImage, HDC hdc, LONG &aWidth, LONG &aHeight, bool &aIs16Bit, int aMinColorDepth = 8)
    12. // Helper function used by PixelSearch below.
    13. // Returns an array of pixels to the caller, which it must free when done. Returns NULL on failure,
    14. // in which case the contents of the output parameters is indeterminate.
    15. {
    16. HDC tdc = CreateCompatibleDC(hdc);
    17. if (!tdc)
    18. return NULL;
    19.  
    20. // From this point on, “goto end” will assume tdc is non-NULL, but that the below
    21. // might still be NULL. Therefore, all of the following must be initialized so that the “end”
    22. // label can detect them:
    23. HGDIOBJ tdc_orig_select = NULL;
    24. LPCOLORREF image_pixel = NULL;
    25. bool success = false;
    26.  
    27. // Confirmed:
    28. // Needs extra memory to prevent buffer overflow due to: “A bottom-up DIB is specified by setting
    29. // the height to a positive number, while a top-down DIB is specified by setting the height to a
    30. // negative number. THE BITMAP COLOR TABLE WILL BE APPENDED to the BITMAPINFO structure.”
    31. // Maybe this applies only to negative height, in which case the second call to GetDIBits()
    32. // below uses one.
    33. struct BITMAPINFO3
    34. {
    35. BITMAPINFOHEADER bmiHeader;
    36. RGBQUAD bmiColors[260]; // v1.0.40.10: 260 vs. 3 to allow room for color table when color depth is 8-bit or less.
    37. } bmi;
    38.  
    39. // Initialize variables so VS2017 wont complain
    40. int image_pixel_count;
    41. bool is_8bit;
    42.  
    43. bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    44. bmi.bmiHeader.biBitCount = 0; // i.e. “query bitmap attributes” only.
    45. if (!GetDIBits(tdc, ahImage, 0, 0, NULL, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS)
    46. || bmi.bmiHeader.biBitCount < aMinColorDepth) // Relies on short-circuit boolean order.
    47. goto end;
    48.  
    49. // Set output parameters for caller:
    50. aIs16Bit = (bmi.bmiHeader.biBitCount == 16);
    51. aWidth = bmi.bmiHeader.biWidth;
    52. aHeight = bmi.bmiHeader.biHeight;
    53.  
    54. image_pixel_count = aWidth * aHeight;
    55. if (!(image_pixel = (LPCOLORREF)malloc(image_pixel_count * sizeof(COLORREF))))
    56. goto end;
    57.  
    58. // v1.0.40.10: To preserve compatibility with callers who check for transparency in icons, don’t do any
    59. // of the extra color table handling for 1-bpp images. Update: For code simplification, support only
    60. // 8-bpp images. If ever support lower color depths, use something like “bmi.bmiHeader.biBitCount > 1
    61. // && bmi.bmiHeader.biBitCount < 9″;
    62. is_8bit = (bmi.bmiHeader.biBitCount == 8);
    63. if (!is_8bit)
    64. bmi.bmiHeader.biBitCount = 32;
    65. bmi.bmiHeader.biHeight = bmi.bmiHeader.biHeight; // Storing a negative inside the bmiHeader struct is a signal for GetDIBits().
    66.  
    67. // Must be done only after GetDIBits() because: “The bitmap identified by the hbmp parameter
    68. // must not be selected into a device context when the application calls GetDIBits().”
    69. // (Although testing shows it works anyway, perhaps because GetDIBits() above is being
    70. // called in its informational mode only).
    71. // Note that this seems to return NULL sometimes even though everything still works.
    72. // Perhaps that is normal.
    73. tdc_orig_select = SelectObject(tdc, ahImage); // Returns NULL when we’re called the second time?
    74.  
    75. // Apparently there is no need to specify DIB_PAL_COLORS below when color depth is 8-bit because
    76. // DIB_RGB_COLORS also retrieves the color indices.
    77. if (!(GetDIBits(tdc, ahImage, 0, aHeight, image_pixel, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS)))
    78. goto end;
    79.  
    80. if (is_8bit) // This section added in v1.0.40.10.
    81. {
    82. // Convert the color indices to RGB colors by going through the array in reverse order.
    83. // Reverse order allows an in-place conversion of each 8-bit color index to its corresponding
    84. // 32-bit RGB color.
    85. LPDWORD palette = (LPDWORD)_alloca(256 * sizeof(PALETTEENTRY));
    86. GetSystemPaletteEntries(tdc, 0, 256, (LPPALETTEENTRY)palette); // Even if failure can realistically happen, consequences of using uninitialized palette seem acceptable.
    87. // Above: GetSystemPaletteEntries() is the only approach that provided the correct palette.
    88. // The following other approaches didn’t give the right one:
    89. // GetDIBits(): The palette it stores in bmi.bmiColors seems completely wrong.
    90. // GetPaletteEntries()+GetCurrentObject(hdc, OBJ_PAL): Returned only 20 entries rather than the expected 256.
    91. // GetDIBColorTable(): I think same as above or maybe it returns 0.
    92.  
    93. // The following section is necessary because apparently each new row in the region starts on
    94. // a DWORD boundary. So if the number of pixels in each row isn’t an exact multiple of 4, there
    95. // are between 1 and 3 zero-bytes at the end of each row.
    96. int remainder = aWidth % 4;
    97. int empty_bytes_at_end_of_each_row = remainder ? (4 remainder) : 0;
    98.  
    99. // Start at the last RGB slot and the last color index slot:
    100. BYTE *byte = (BYTE *)image_pixel + image_pixel_count 1 + (aHeight * empty_bytes_at_end_of_each_row); // Pointer to 8-bit color indices.
    101. DWORD *pixel = image_pixel + image_pixel_count 1; // Pointer to 32-bit RGB entries.
    102.  
    103. int row, col;
    104. for (row = 0; row < aHeight; ++row) // For each row.
    105. {
    106. byte -= empty_bytes_at_end_of_each_row;
    107. for (col = 0; col < aWidth; ++col) // For each column.
    108. *pixel = rgb_to_bgr(palette[*byte–]); // Caller always wants RGB vs. BGR format.
    109. }
    110. }
    111.  
    112. // Since above didn’t “goto end”, indicate success:
    113. success = true;
    114.  
    115. end:
    116. if (tdc_orig_select) // i.e. the original call to SelectObject() didn’t fail.
    117. SelectObject(tdc, tdc_orig_select); // Probably necessary to prevent memory leak.
    118. DeleteDC(tdc);
    119. if (!success && image_pixel)
    120. {
    121. free(image_pixel);
    122. image_pixel = NULL;
    123. }
    124. return image_pixel;
    125. }
    126.  
    127. // { -1, -1 } = Not Found
    128. // { -2, -2 } = Error
    129. POINT PixelSearch(int aLeft, int aTop, int aRight, int aBottom, COLORREF aColorRGB, int aVariation)
    130. // Caller has ensured that aColor is in BGR format unless caller passed true for aUseRGB, in which case
    131. // it’s in RGB format.
    132. // Author: The fast-mode PixelSearch was created by Aurelian Maga.
    133. {
    134. // For maintainability, get options and RGB/BGR conversion out of the way early.
    135. COLORREF aColorBGR = rgb_to_bgr(aColorRGB); // rgb_to_bgr() also converts in the reverse direction, i.e. bgr_to_rgb().
    136.  
    137. // Many of the following sections are similar to those in ImageSearch(), so they should be
    138. // maintained together.
    139.  
    140. if (aVariation < 0)
    141. aVariation = 0;
    142. if (aVariation > 255)
    143. aVariation = 255;
    144.  
    145. // Allow colors to vary within the spectrum of intensity, rather than having them
    146. // wrap around (which doesn’t seem to make much sense). For example, if the user specified
    147. // a variation of 5, but the red component of aColorBGR is only 0x01, we don’t want red_low to go
    148. // below zero, which would cause it to wrap around to a very intense red color:
    149. COLORREF pixel; // Used much further down.
    150. BYTE red, green, blue; // Used much further down.
    151. BYTE search_red, search_green, search_blue;
    152. BYTE red_low, green_low, blue_low, red_high, green_high, blue_high;
    153. if (aVariation > 0)
    154. {
    155. search_red = GetRValue(aColorBGR);
    156. search_green = GetGValue(aColorBGR);
    157. search_blue = GetBValue(aColorBGR);
    158. }
    159. //else leave uninitialized since they won’t be used.
    160.  
    161. HDC hdc = GetDC(NULL);
    162. if (!hdc)
    163. return { 2, 2 };
    164.  
    165. bool found = false; // Must init here for use by “goto fast_end” and for use by both fast and slow modes.
    166.  
    167.  
    168. // From this point on, “goto fast_end” will assume hdc is non-NULL but that the below might still be NULL.
    169. // Therefore, all of the following must be initialized so that the “fast_end” label can detect them:
    170. HDC sdc = NULL;
    171. HBITMAP hbitmap_screen = NULL;
    172. LPCOLORREF screen_pixel = NULL;
    173. HGDIOBJ sdc_orig_select = NULL;
    174.  
    175. // Some explanation for the method below is contained in this quote from the newsgroups:
    176. // “you shouldn’t really be getting the current bitmap from the GetDC DC. This might
    177. // have weird effects like returning the entire screen or not working. Create yourself
    178. // a memory DC first of the correct size. Then BitBlt into it and then GetDIBits on
    179. // that instead. This way, the provider of the DC (the video driver) can make sure that
    180. // the correct pixels are copied across.”
    181.  
    182. // Initialize screen_pixel_count so VS2017 wont complain
    183. LONG screen_pixel_count;
    184.  
    185. // Create an empty bitmap to hold all the pixels currently visible on the screen (within the search area):
    186. int search_width = aRight aLeft + 1;
    187. int search_height = aBottom aTop + 1;
    188. if (!(sdc = CreateCompatibleDC(hdc)) || !(hbitmap_screen = CreateCompatibleBitmap(hdc, search_width, search_height)))
    189. goto fast_end;
    190.  
    191. if (!(sdc_orig_select = SelectObject(sdc, hbitmap_screen)))
    192. goto fast_end;
    193.  
    194. // Copy the pixels in the search-area of the screen into the DC to be searched:
    195. if (!(BitBlt(sdc, 0, 0, search_width, search_height, hdc, aLeft, aTop, SRCCOPY)))
    196. goto fast_end;
    197.  
    198. LONG screen_width, screen_height;
    199. bool screen_is_16bit;
    200. if (!(screen_pixel = getbits(hbitmap_screen, sdc, screen_width, screen_height, screen_is_16bit)))
    201. goto fast_end;
    202.  
    203. // Concerning 0xF8F8F8F8: “On 16bit and 15 bit color the first 5 bits in each byte are valid
    204. // (in 16bit there is an extra bit but i forgot for which color). And this will explain the
    205. // second problem [in the test script], since GetPixel even in 16bit will return some “valid”
    206. // data in the last 3bits of each byte.”
    207. register int i;
    208. screen_pixel_count = screen_width * screen_height;
    209. if (screen_is_16bit)
    210. for (i = 0; i < screen_pixel_count; ++i)
    211. screen_pixel[i] &= 0xF8F8F8F8;
    212.  
    213. if (aVariation < 1) // Caller wants an exact match on one particular color.
    214. {
    215. if (screen_is_16bit)
    216. aColorRGB &= 0xF8F8F8F8;
    217. for (i = 0; i < screen_pixel_count; ++i)
    218. {
    219. // Note that screen pixels sometimes have a non-zero high-order byte. That’s why
    220. // bit-and with 0x00FFFFFF is done. Otherwise, reddish/orangish colors are not properly
    221. // found:
    222. if ((screen_pixel[i] & 0x00FFFFFF) == aColorRGB)
    223. {
    224. found = true;
    225. break;
    226. }
    227. }
    228. }
    229. else
    230. {
    231. // It seems more appropriate to do the 16-bit conversion prior to SET_COLOR_RANGE,
    232. // rather than applying 0xF8 to each of the high/low values individually.
    233. if (screen_is_16bit)
    234. {
    235. search_red &= 0xF8;
    236. search_green &= 0xF8;
    237. search_blue &= 0xF8;
    238. }
    239.  
    240. #define SET_COLOR_RANGE \
    241. {\
    242. red_low = (aVariation > search_red) ? 0 : search_red aVariation;\
    243. green_low = (aVariation > search_green) ? 0 : search_green aVariation;\
    244. blue_low = (aVariation > search_blue) ? 0 : search_blue aVariation;\
    245. red_high = (aVariation > 0xFF search_red) ? 0xFF : search_red + aVariation;\
    246. green_high = (aVariation > 0xFF search_green) ? 0xFF : search_green + aVariation;\
    247. blue_high = (aVariation > 0xFF search_blue) ? 0xFF : search_blue + aVariation;\
    248. }
    249.  
    250. SET_COLOR_RANGE
    251.  
    252. for (i = 0; i < screen_pixel_count; ++i)
    253. {
    254. // Note that screen pixels sometimes have a non-zero high-order byte. But it doesn’t
    255. // matter with the below approach, since that byte is not checked in the comparison.
    256. pixel = screen_pixel[i];
    257. red = GetBValue(pixel); // Because pixel is in RGB vs. BGR format, red is retrieved with
    258. green = GetGValue(pixel); // GetBValue() and blue is retrieved with GetRValue().
    259. blue = GetRValue(pixel);
    260. if (red >= red_low && red <= red_high
    261. && green >= green_low && green <= green_high
    262. && blue >= blue_low && blue <= blue_high)
    263. {
    264. found = true;
    265. break;
    266. }
    267. }
    268. }
    269.  
    270. fast_end:
    271. // If found==false when execution reaches here, ErrorLevel is already set to the right value, so just
    272. // clean up then return.
    273. ReleaseDC(NULL, hdc);
    274. if (sdc)
    275. {
    276. if (sdc_orig_select) // i.e. the original call to SelectObject() didn’t fail.
    277. SelectObject(sdc, sdc_orig_select); // Probably necessary to prevent memory leak.
    278. DeleteDC(sdc);
    279. }
    280. if (hbitmap_screen)
    281. DeleteObject(hbitmap_screen);
    282. if (screen_pixel)
    283. free(screen_pixel);
    284. else // One of the GDI calls failed and the search wasn’t carried out.
    285. return { 2, 2 };
    286.  
    287. // Otherwise, success. Calculate xpos and ypos of where the match was found and adjust
    288. // coords to make them relative to the position of the target window (rect will contain
    289. // zeroes if this doesn’t need to be done):
    290. if (found)
    291. return{ aLeft + i % screen_width , aTop + i / screen_width };
    292.  
    293. return{ 1,-1 };
    294. }
    295.  
    296. int main()
    297. {
    298. for (int i = 0; i < 1000; i++) {
    299. POINT p = PixelSearch(0, 0, 1920, 1080, RGB(255, 255, 255), 0);
    300. std::cout << “X:” << p.x << ” Y:” << p.y << std::endl;
    301. }
    302. system(“pause”);
    303. return 0;
    304. }
    305.  

You must be logged in to reply to this topic.

s2Member®