| 1 | ResultType Line::ImageSearch(int aLeft, int aTop, int aRight, int aBottom, char *aImageFile) |
|---|
| 2 | // Author: ImageSearch was created by Aurelian Maga. |
|---|
| 3 | { |
|---|
| 4 | // Many of the following sections are similar to those in PixelSearch(), so they should be |
|---|
| 5 | // maintained together. |
|---|
| 6 | Var *output_var_x = ARGVAR1; // Ok if NULL. RAW wouldn't be safe because load-time validation actually |
|---|
| 7 | Var *output_var_y = ARGVAR2; // requires a minimum of zero parameters so that the output-vars can be optional. Also: |
|---|
| 8 | // Load-time validation has ensured that these are valid output variables (e.g. not built-in vars). |
|---|
| 9 | |
|---|
| 10 | // Set default results, both ErrorLevel and output variables, in case of early return: |
|---|
| 11 | g_ErrorLevel->Assign(ERRORLEVEL_ERROR2); // 2 means error other than "image not found". |
|---|
| 12 | if (output_var_x) |
|---|
| 13 | output_var_x->Assign(); // Init to empty string regardless of whether we succeed here. |
|---|
| 14 | if (output_var_y) |
|---|
| 15 | output_var_y->Assign(); // Same. |
|---|
| 16 | |
|---|
| 17 | RECT rect = {0}; // Set default (for CoordMode == "screen"). |
|---|
| 18 | if (!(g->CoordMode & COORD_MODE_PIXEL)) // Using relative vs. screen coordinates. |
|---|
| 19 | { |
|---|
| 20 | if (!GetWindowRect(GetForegroundWindow(), &rect)) |
|---|
| 21 | return OK; // Let ErrorLevel tell the story. |
|---|
| 22 | aLeft += rect.left; |
|---|
| 23 | aTop += rect.top; |
|---|
| 24 | aRight += rect.left; // Add left vs. right because we're adjusting based on the position of the window. |
|---|
| 25 | aBottom += rect.top; // Same. |
|---|
| 26 | } |
|---|
| 27 | |
|---|
| 28 | // Options are done as asterisk+option to permit future expansion. |
|---|
| 29 | // Set defaults to be possibly overridden by any specified options: |
|---|
| 30 | int aVariation = 0; // This is named aVariation vs. variation for use with the SET_COLOR_RANGE macro. |
|---|
| 31 | COLORREF trans_color = CLR_NONE; // The default must be a value that can't occur naturally in an image. |
|---|
| 32 | int icon_number = 0; // Zero means "load icon or bitmap (doesn't matter)". |
|---|
| 33 | int width = 0, height = 0; |
|---|
| 34 | // For icons, override the default to be 16x16 because that is what is sought 99% of the time. |
|---|
| 35 | // This new default can be overridden by explicitly specifying w0 h0: |
|---|
| 36 | char *cp = strrchr(aImageFile, '.'); |
|---|
| 37 | if (cp) |
|---|
| 38 | { |
|---|
| 39 | ++cp; |
|---|
| 40 | if (!(stricmp(cp, "ico") && stricmp(cp, "exe") && stricmp(cp, "dll"))) |
|---|
| 41 | width = GetSystemMetrics(SM_CXSMICON), height = GetSystemMetrics(SM_CYSMICON); |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | char color_name[32], *dp; |
|---|
| 45 | cp = omit_leading_whitespace(aImageFile); // But don't alter aImageFile yet in case it contains literal whitespace we want to retain. |
|---|
| 46 | while (*cp == '*') |
|---|
| 47 | { |
|---|
| 48 | ++cp; |
|---|
| 49 | switch (toupper(*cp)) |
|---|
| 50 | { |
|---|
| 51 | case 'W': width = ATOI(cp + 1); break; |
|---|
| 52 | case 'H': height = ATOI(cp + 1); break; |
|---|
| 53 | default: |
|---|
| 54 | if (!strnicmp(cp, "Icon", 4)) |
|---|
| 55 | { |
|---|
| 56 | cp += 4; // Now it's the character after the word. |
|---|
| 57 | icon_number = ATOI(cp); // LoadPicture() correctly handles any negative value. |
|---|
| 58 | } |
|---|
| 59 | else if (!strnicmp(cp, "Trans", 5)) |
|---|
| 60 | { |
|---|
| 61 | cp += 5; // Now it's the character after the word. |
|---|
| 62 | // Isolate the color name/number for ColorNameToBGR(): |
|---|
| 63 | strlcpy(color_name, cp, sizeof(color_name)); |
|---|
| 64 | if (dp = StrChrAny(color_name, " \t")) // Find space or tab, if any. |
|---|
| 65 | *dp = '\0'; |
|---|
| 66 | // Fix for v1.0.44.10: Treat trans_color as containing an RGB value (not BGR) so that it matches |
|---|
| 67 | // the documented behavior. In older versions, a specified color like "TransYellow" was wrong in |
|---|
| 68 | // every way (inverted) and a specified numeric color like "Trans0xFFFFAA" was treated as BGR vs. RGB. |
|---|
| 69 | trans_color = ColorNameToBGR(color_name); |
|---|
| 70 | if (trans_color == CLR_NONE) // A matching color name was not found, so assume it's in hex format. |
|---|
| 71 | // It seems strtol() automatically handles the optional leading "0x" if present: |
|---|
| 72 | trans_color = strtol(color_name, NULL, 16); |
|---|
| 73 | // if color_name did not contain something hex-numeric, black (0x00) will be assumed, |
|---|
| 74 | // which seems okay given how rare such a problem would be. |
|---|
| 75 | else |
|---|
| 76 | trans_color = bgr_to_rgb(trans_color); // v1.0.44.10: See fix/comment above. |
|---|
| 77 | |
|---|
| 78 | } |
|---|
| 79 | else // Assume it's a number since that's the only other asterisk-option. |
|---|
| 80 | { |
|---|
| 81 | aVariation = ATOI(cp); // Seems okay to support hex via ATOI because the space after the number is documented as being mandatory. |
|---|
| 82 | if (aVariation < 0) |
|---|
| 83 | aVariation = 0; |
|---|
| 84 | if (aVariation > 255) |
|---|
| 85 | aVariation = 255; |
|---|
| 86 | // Note: because it's possible for filenames to start with a space (even though Explorer itself |
|---|
| 87 | // won't let you create them that way), allow exactly one space between end of option and the |
|---|
| 88 | // filename itself: |
|---|
| 89 | } |
|---|
| 90 | } // switch() |
|---|
| 91 | if ( !(cp = StrChrAny(cp, " \t")) ) // Find the first space or tab after the option. |
|---|
| 92 | return OK; // Bad option/format. Let ErrorLevel tell the story. |
|---|
| 93 | // Now it's the space or tab (if there is one) after the option letter. Advance by exactly one character |
|---|
| 94 | // because only one space or tab is considered the delimiter. Any others are considered to be part of the |
|---|
| 95 | // filename (though some or all OSes might simply ignore them or tolerate them as first-try match criteria). |
|---|
| 96 | aImageFile = ++cp; // This should now point to another asterisk or the filename itself. |
|---|
| 97 | // Above also serves to reset the filename to omit the option string whenever at least one asterisk-option is present. |
|---|
| 98 | cp = omit_leading_whitespace(cp); // This is done to make it more tolerant of having more than one space/tab between options. |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | // Update: Transparency is now supported in icons by using the icon's mask. In addition, an attempt |
|---|
| 102 | // is made to support transparency in GIF, PNG, and possibly TIF files via the *Trans option, which |
|---|
| 103 | // assumes that one color in the image is transparent. In GIFs not loaded via GDIPlus, the transparent |
|---|
| 104 | // color might always been seen as pure white, but when GDIPlus is used, it's probably always black |
|---|
| 105 | // like it is in PNG -- however, this will not relied upon, at least not until confirmed. |
|---|
| 106 | // OLDER/OBSOLETE comment kept for background: |
|---|
| 107 | // For now, images that can't be loaded as bitmaps (icons and cursors) are not supported because most |
|---|
| 108 | // icons have a transparent background or color present, which the image search routine here is |
|---|
| 109 | // probably not equipped to handle (since the transparent color, when shown, typically reveals the |
|---|
| 110 | // color of whatever is behind it; thus screen pixel color won't match image's pixel color). |
|---|
| 111 | // So currently, only BMP and GIF seem to work reliably, though some of the other GDIPlus-supported |
|---|
| 112 | // formats might work too. |
|---|
| 113 | int image_type; |
|---|
| 114 | HBITMAP hbitmap_image = LoadPicture(aImageFile, width, height, image_type, icon_number, false); |
|---|
| 115 | // The comment marked OBSOLETE below is no longer true because the elimination of the high-byte via |
|---|
| 116 | // 0x00FFFFFF seems to have fixed it. But "true" is still not passed because that should increase |
|---|
| 117 | // consistency when GIF/BMP/ICO files are used by a script on both Win9x and other OSs (since the |
|---|
| 118 | // same loading method would be used via "false" for these formats across all OSes). |
|---|
| 119 | // OBSOLETE: Must not pass "true" with the above because that causes bitmaps and gifs to be not found |
|---|
| 120 | // by the search. In other words, nothing works. Obsolete comment: Pass "true" so that an attempt |
|---|
| 121 | // will be made to load icons as bitmaps if GDIPlus is available. |
|---|
| 122 | if (!hbitmap_image) |
|---|
| 123 | return OK; // Let ErrorLevel tell the story. |
|---|
| 124 | |
|---|
| 125 | HDC hdc = GetDC(NULL); |
|---|
| 126 | if (!hdc) |
|---|
| 127 | { |
|---|
| 128 | DeleteObject(hbitmap_image); |
|---|
| 129 | return OK; // Let ErrorLevel tell the story. |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | // From this point on, "goto end" will assume hdc and hbitmap_image are non-NULL, but that the below |
|---|
| 133 | // might still be NULL. Therefore, all of the following must be initialized so that the "end" |
|---|
| 134 | // label can detect them: |
|---|
| 135 | HDC sdc = NULL; |
|---|
| 136 | HBITMAP hbitmap_screen = NULL; |
|---|
| 137 | LPCOLORREF image_pixel = NULL, screen_pixel = NULL, image_mask = NULL; |
|---|
| 138 | HGDIOBJ sdc_orig_select = NULL; |
|---|
| 139 | bool found = false; // Must init here for use by "goto end". |
|---|
| 140 | |
|---|
| 141 | bool image_is_16bit; |
|---|
| 142 | LONG image_width, image_height; |
|---|
| 143 | |
|---|
| 144 | if (image_type == IMAGE_ICON) |
|---|
| 145 | { |
|---|
| 146 | // Must be done prior to IconToBitmap() since it deletes (HICON)hbitmap_image: |
|---|
| 147 | ICONINFO ii; |
|---|
| 148 | if (GetIconInfo((HICON)hbitmap_image, &ii)) |
|---|
| 149 | { |
|---|
| 150 | // If the icon is monochrome (black and white), ii.hbmMask will contain twice as many pixels as |
|---|
| 151 | // are actually in the icon. But since the top half of the pixels are the AND-mask, it seems |
|---|
| 152 | // okay to get all the pixels given the rarity of monochrome icons. This scenario should be |
|---|
| 153 | // handled properly because: 1) the variables image_height and image_width will be overridden |
|---|
| 154 | // further below with the correct icon dimensions; 2) Only the first half of the pixels within |
|---|
| 155 | // the image_mask array will actually be referenced by the transparency checker in the loops, |
|---|
| 156 | // and that first half is the AND-mask, which is the transparency part that is needed. The |
|---|
| 157 | // second half, the XOR part, is not needed and thus ignored. Also note that if width/height |
|---|
| 158 | // required the icon to be scaled, LoadPicture() has already done that directly to the icon, |
|---|
| 159 | // so ii.hbmMask should already be scaled to match the size of the bitmap created later below. |
|---|
| 160 | image_mask = getbits(ii.hbmMask, hdc, image_width, image_height, image_is_16bit, 1); |
|---|
| 161 | DeleteObject(ii.hbmColor); // DeleteObject() probably handles NULL okay since few MSDN/other examples ever check for NULL. |
|---|
| 162 | DeleteObject(ii.hbmMask); |
|---|
| 163 | } |
|---|
| 164 | if ( !(hbitmap_image = IconToBitmap((HICON)hbitmap_image, true)) ) |
|---|
| 165 | return OK; // Let ErrorLevel tell the story. |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | if ( !(image_pixel = getbits(hbitmap_image, hdc, image_width, image_height, image_is_16bit)) ) |
|---|
| 169 | goto end; |
|---|
| 170 | |
|---|
| 171 | // Create an empty bitmap to hold all the pixels currently visible on the screen that lie within the search area: |
|---|
| 172 | int search_width = aRight - aLeft + 1; |
|---|
| 173 | int search_height = aBottom - aTop + 1; |
|---|
| 174 | if ( !(sdc = CreateCompatibleDC(hdc)) || !(hbitmap_screen = CreateCompatibleBitmap(hdc, search_width, search_height)) ) |
|---|
| 175 | goto end; |
|---|
| 176 | |
|---|
| 177 | if ( !(sdc_orig_select = SelectObject(sdc, hbitmap_screen)) ) |
|---|
| 178 | goto end; |
|---|
| 179 | |
|---|
| 180 | // Copy the pixels in the search-area of the screen into the DC to be searched: |
|---|
| 181 | if ( !(BitBlt(sdc, 0, 0, search_width, search_height, hdc, aLeft, aTop, SRCCOPY)) ) |
|---|
| 182 | goto end; |
|---|
| 183 | |
|---|
| 184 | LONG screen_width, screen_height; |
|---|
| 185 | bool screen_is_16bit; |
|---|
| 186 | if ( !(screen_pixel = getbits(hbitmap_screen, sdc, screen_width, screen_height, screen_is_16bit)) ) |
|---|
| 187 | goto end; |
|---|
| 188 | |
|---|
| 189 | LONG image_pixel_count = image_width * image_height; |
|---|
| 190 | LONG screen_pixel_count = screen_width * screen_height; |
|---|
| 191 | int i, j, k, x, y; // Declaring as "register" makes no performance difference with current compiler, so let the compiler choose which should be registers. |
|---|
| 192 | |
|---|
| 193 | // If either is 16-bit, convert *both* to the 16-bit-compatible 32-bit format: |
|---|
| 194 | if (image_is_16bit || screen_is_16bit) |
|---|
| 195 | { |
|---|
| 196 | if (trans_color != CLR_NONE) |
|---|
| 197 | trans_color &= 0x00F8F8F8; // Convert indicated trans-color to be compatible with the conversion below. |
|---|
| 198 | for (i = 0; i < screen_pixel_count; ++i) |
|---|
| 199 | screen_pixel[i] &= 0x00F8F8F8; // Highest order byte must be masked to zero for consistency with use of 0x00FFFFFF below. |
|---|
| 200 | for (i = 0; i < image_pixel_count; ++i) |
|---|
| 201 | image_pixel[i] &= 0x00F8F8F8; // Same. |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | // v1.0.44.03: The below is now done even for variation>0 mode so its results are consistent with those of |
|---|
| 205 | // non-variation mode. This is relied upon by variation=0 mode but now also by the following line in the |
|---|
| 206 | // variation>0 section: |
|---|
| 207 | // || image_pixel[j] == trans_color |
|---|
| 208 | // Without this change, there are cases where variation=0 would find a match but a higher variation |
|---|
| 209 | // (for the same search) wouldn't. |
|---|
| 210 | for (i = 0; i < image_pixel_count; ++i) |
|---|
| 211 | image_pixel[i] &= 0x00FFFFFF; |
|---|
| 212 | |
|---|
| 213 | // Search the specified region for the first occurrence of the image: |
|---|
| 214 | if (aVariation < 1) // Caller wants an exact match. |
|---|
| 215 | { |
|---|
| 216 | // Concerning the following use of 0x00FFFFFF, the use of 0x00F8F8F8 above is related (both have high order byte 00). |
|---|
| 217 | // The following needs to be done only when shades-of-variation mode isn't in effect because |
|---|
| 218 | // shades-of-variation mode ignores the high-order byte due to its use of macros such as GetRValue(). |
|---|
| 219 | // This transformation incurs about a 15% performance decrease (percentage is fairly constant since |
|---|
| 220 | // it is proportional to the search-region size, which tends to be much larger than the search-image and |
|---|
| 221 | // is therefore the primary determination of how long the loops take). But it definitely helps find images |
|---|
| 222 | // more successfully in some cases. For example, if a PNG file is displayed in a GUI window, this |
|---|
| 223 | // transformation allows certain bitmap search-images to be found via variation==0 when they otherwise |
|---|
| 224 | // would require variation==1 (possibly the variation==1 success is just a side-effect of it |
|---|
| 225 | // ignoring the high-order byte -- maybe a much higher variation would be needed if the high |
|---|
| 226 | // order byte were also subject to the same shades-of-variation analysis as the other three bytes [RGB]). |
|---|
| 227 | for (i = 0; i < screen_pixel_count; ++i) |
|---|
| 228 | screen_pixel[i] &= 0x00FFFFFF; |
|---|
| 229 | |
|---|
| 230 | for (i = 0; i < screen_pixel_count; ++i) |
|---|
| 231 | { |
|---|
| 232 | // Unlike the variation-loop, the following one uses a first-pixel optimization to boost performance |
|---|
| 233 | // by about 10% because it's only 3 extra comparisons and exact-match mode is probably used more often. |
|---|
| 234 | // Before even checking whether the other adjacent pixels in the region match the image, ensure |
|---|
| 235 | // the image does not extend past the right or bottom edges of the current part of the search region. |
|---|
| 236 | // This is done for performance but more importantly to prevent partial matches at the edges of the |
|---|
| 237 | // search region from being considered complete matches. |
|---|
| 238 | // The following check is ordered for short-circuit performance. In addition, image_mask, if |
|---|
| 239 | // non-NULL, is used to determine which pixels are transparent within the image and thus should |
|---|
| 240 | // match any color on the screen. |
|---|
| 241 | if ((screen_pixel[i] == image_pixel[0] // A screen pixel has been found that matches the image's first pixel. |
|---|
| 242 | || image_mask && image_mask[0] // Or: It's an icon's transparent pixel, which matches any color. |
|---|
| 243 | || image_pixel[0] == trans_color) // This should be okay even if trans_color==CLR_NONE, since CLR_NONE should never occur naturally in the image. |
|---|
| 244 | && image_height <= screen_height - i/screen_width // Image is short enough to fit in the remaining rows of the search region. |
|---|
| 245 | && image_width <= screen_width - i%screen_width) // Image is narrow enough not to exceed the right-side boundary of the search region. |
|---|
| 246 | { |
|---|
| 247 | // Check if this candidate region -- which is a subset of the search region whose height and width |
|---|
| 248 | // matches that of the image -- is a pixel-for-pixel match of the image. |
|---|
| 249 | for (found = true, x = 0, y = 0, j = 0, k = i; j < image_pixel_count; ++j) |
|---|
| 250 | { |
|---|
| 251 | if (!(found = (screen_pixel[k] == image_pixel[j] // At least one pixel doesn't match, so this candidate is discarded. |
|---|
| 252 | || image_mask && image_mask[j] // Or: It's an icon's transparent pixel, which matches any color. |
|---|
| 253 | || image_pixel[j] == trans_color))) // This should be okay even if trans_color==CLR_NONE, since CLR none should never occur naturally in the image. |
|---|
| 254 | break; |
|---|
| 255 | if (++x < image_width) // We're still within the same row of the image, so just move on to the next screen pixel. |
|---|
| 256 | ++k; |
|---|
| 257 | else // We're starting a new row of the image. |
|---|
| 258 | { |
|---|
| 259 | x = 0; // Return to the leftmost column of the image. |
|---|
| 260 | ++y; // Move one row downward in the image. |
|---|
| 261 | // Move to the next row within the current-candiate region (not the entire search region). |
|---|
| 262 | // This is done by moving vertically downward from "i" (which is the upper-left pixel of the |
|---|
| 263 | // current-candidate region) by "y" rows. |
|---|
| 264 | k = i + y*screen_width; // Verified correct. |
|---|
| 265 | } |
|---|
| 266 | } |
|---|
| 267 | if (found) // Complete match found. |
|---|
| 268 | break; |
|---|
| 269 | } |
|---|
| 270 | } |
|---|
| 271 | } |
|---|
| 272 | else // Allow colors to vary by aVariation shades; i.e. approximate match is okay. |
|---|
| 273 | { |
|---|
| 274 | // The following section is part of the first-pixel-check optimization that improves performance by |
|---|
| 275 | // 15% or more depending on where and whether a match is found. This section and one the follows |
|---|
| 276 | // later is commented out to reduce code size. |
|---|
| 277 | // Set high/low range for the first pixel of the image since it is the pixel most often checked |
|---|
| 278 | // (i.e. for performance). |
|---|
| 279 | //BYTE search_red1 = GetBValue(image_pixel[0]); // Because it's RGB vs. BGR, the B value is fetched, not R (though it doesn't matter as long as everything is internally consistent here). |
|---|
| 280 | //BYTE search_green1 = GetGValue(image_pixel[0]); |
|---|
| 281 | //BYTE search_blue1 = GetRValue(image_pixel[0]); // Same comment as above. |
|---|
| 282 | //BYTE red_low1 = (aVariation > search_red1) ? 0 : search_red1 - aVariation; |
|---|
| 283 | //BYTE green_low1 = (aVariation > search_green1) ? 0 : search_green1 - aVariation; |
|---|
| 284 | //BYTE blue_low1 = (aVariation > search_blue1) ? 0 : search_blue1 - aVariation; |
|---|
| 285 | //BYTE red_high1 = (aVariation > 0xFF - search_red1) ? 0xFF : search_red1 + aVariation; |
|---|
| 286 | //BYTE green_high1 = (aVariation > 0xFF - search_green1) ? 0xFF : search_green1 + aVariation; |
|---|
| 287 | //BYTE blue_high1 = (aVariation > 0xFF - search_blue1) ? 0xFF : search_blue1 + aVariation; |
|---|
| 288 | // Above relies on the fact that the 16-bit conversion higher above was already done because like |
|---|
| 289 | // in PixelSearch, it seems more appropriate to do the 16-bit conversion prior to setting the range |
|---|
| 290 | // of high and low colors (vs. than applying 0xF8 to each of the high/low values individually). |
|---|
| 291 | |
|---|
| 292 | BYTE red, green, blue; |
|---|
| 293 | BYTE search_red, search_green, search_blue; |
|---|
| 294 | BYTE red_low, green_low, blue_low, red_high, green_high, blue_high; |
|---|
| 295 | |
|---|
| 296 | // The following loop is very similar to its counterpart above that finds an exact match, so maintain |
|---|
| 297 | // them together and see above for more detailed comments about it. |
|---|
| 298 | for (i = 0; i < screen_pixel_count; ++i) |
|---|
| 299 | { |
|---|
| 300 | // The following is commented out to trade code size reduction for performance (see comment above). |
|---|
| 301 | //red = GetBValue(screen_pixel[i]); // Because it's RGB vs. BGR, the B value is fetched, not R (though it doesn't matter as long as everything is internally consistent here). |
|---|
| 302 | //green = GetGValue(screen_pixel[i]); |
|---|
| 303 | //blue = GetRValue(screen_pixel[i]); |
|---|
| 304 | //if ((red >= red_low1 && red <= red_high1 |
|---|
| 305 | // && green >= green_low1 && green <= green_high1 |
|---|
| 306 | // && blue >= blue_low1 && blue <= blue_high1 // All three color components are a match, so this screen pixel matches the image's first pixel. |
|---|
| 307 | // || image_mask && image_mask[0] // Or: It's an icon's transparent pixel, which matches any color. |
|---|
| 308 | // || image_pixel[0] == trans_color) // This should be okay even if trans_color==CLR_NONE, since CLR none should never occur naturally in the image. |
|---|
| 309 | // && image_height <= screen_height - i/screen_width // Image is short enough to fit in the remaining rows of the search region. |
|---|
| 310 | // && image_width <= screen_width - i%screen_width) // Image is narrow enough not to exceed the right-side boundary of the search region. |
|---|
| 311 | |
|---|
| 312 | // Instead of the above, only this abbreviated check is done: |
|---|
| 313 | if (image_height <= screen_height - i/screen_width // Image is short enough to fit in the remaining rows of the search region. |
|---|
| 314 | && image_width <= screen_width - i%screen_width) // Image is narrow enough not to exceed the right-side boundary of the search region. |
|---|
| 315 | { |
|---|
| 316 | // Since the first pixel is a match, check the other pixels. |
|---|
| 317 | for (found = true, x = 0, y = 0, j = 0, k = i; j < image_pixel_count; ++j) |
|---|
| 318 | { |
|---|
| 319 | search_red = GetBValue(image_pixel[j]); |
|---|
| 320 | search_green = GetGValue(image_pixel[j]); |
|---|
| 321 | search_blue = GetRValue(image_pixel[j]); |
|---|
| 322 | SET_COLOR_RANGE |
|---|
| 323 | red = GetBValue(screen_pixel[k]); |
|---|
| 324 | green = GetGValue(screen_pixel[k]); |
|---|
| 325 | blue = GetRValue(screen_pixel[k]); |
|---|
| 326 | |
|---|
| 327 | if (!(found = red >= red_low && red <= red_high |
|---|
| 328 | && green >= green_low && green <= green_high |
|---|
| 329 | && blue >= blue_low && blue <= blue_high |
|---|
| 330 | || image_mask && image_mask[j] // Or: It's an icon's transparent pixel, which matches any color. |
|---|
| 331 | || image_pixel[j] == trans_color)) // This should be okay even if trans_color==CLR_NONE, since CLR_NONE should never occur naturally in the image. |
|---|
| 332 | break; // At least one pixel doesn't match, so this candidate is discarded. |
|---|
| 333 | if (++x < image_width) // We're still within the same row of the image, so just move on to the next screen pixel. |
|---|
| 334 | ++k; |
|---|
| 335 | else // We're starting a new row of the image. |
|---|
| 336 | { |
|---|
| 337 | x = 0; // Return to the leftmost column of the image. |
|---|
| 338 | ++y; // Move one row downward in the image. |
|---|
| 339 | k = i + y*screen_width; // Verified correct. |
|---|
| 340 | } |
|---|
| 341 | } |
|---|
| 342 | if (found) // Complete match found. |
|---|
| 343 | break; |
|---|
| 344 | } |
|---|
| 345 | } |
|---|
| 346 | } |
|---|
| 347 | |
|---|
| 348 | if (!found) // Must override ErrorLevel to its new value prior to the label below. |
|---|
| 349 | g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // "1" indicates search completed okay, but didn't find it. |
|---|
| 350 | |
|---|
| 351 | end: |
|---|
| 352 | // If found==false when execution reaches here, ErrorLevel is already set to the right value, so just |
|---|
| 353 | // clean up then return. |
|---|
| 354 | ReleaseDC(NULL, hdc); |
|---|
| 355 | DeleteObject(hbitmap_image); |
|---|
| 356 | if (sdc) |
|---|
| 357 | { |
|---|
| 358 | if (sdc_orig_select) // i.e. the original call to SelectObject() didn't fail. |
|---|
| 359 | SelectObject(sdc, sdc_orig_select); // Probably necessary to prevent memory leak. |
|---|
| 360 | DeleteDC(sdc); |
|---|
| 361 | } |
|---|
| 362 | if (hbitmap_screen) |
|---|
| 363 | DeleteObject(hbitmap_screen); |
|---|
| 364 | if (image_pixel) |
|---|
| 365 | free(image_pixel); |
|---|
| 366 | if (image_mask) |
|---|
| 367 | free(image_mask); |
|---|
| 368 | if (screen_pixel) |
|---|
| 369 | free(screen_pixel); |
|---|
| 370 | |
|---|
| 371 | if (!found) // Let ErrorLevel, which is either "1" or "2" as set earlier, tell the story. |
|---|
| 372 | return OK; |
|---|
| 373 | |
|---|
| 374 | // Otherwise, success. Calculate xpos and ypos of where the match was found and adjust |
|---|
| 375 | // coords to make them relative to the position of the target window (rect will contain |
|---|
| 376 | // zeroes if this doesn't need to be done): |
|---|
| 377 | if (output_var_x && !output_var_x->Assign((aLeft + i%screen_width) - rect.left)) |
|---|
| 378 | return FAIL; |
|---|
| 379 | if (output_var_y && !output_var_y->Assign((aTop + i/screen_width) - rect.top)) |
|---|
| 380 | return FAIL; |
|---|
| 381 | |
|---|
| 382 | return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success. |
|---|
| 383 | } |
|---|