ImgAssist.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. #include "stdafx.h"
  2. #include "ImgAssist.h"
  3. namespace ImgAssist
  4. {
  5. std::string CaptureGameWnd(HWND hWnd)
  6. {
  7. TCHAR szName[MAX_PATH] = { 0 };
  8. _stprintf_s(szName, _T("%s%p.bmp"), GameGlobal::g_szTempDir, hWnd);
  9. CaptureGameWnd(hWnd, szName);
  10. return std::string(szName);
  11. }
  12. void CaptureGameWnd(HWND hWnd, std::string strSavePath)
  13. {
  14. HDC hDC = ::GetWindowDC(hWnd);
  15. ASSERT(hDC);
  16. HDC hMemDC = ::CreateCompatibleDC(hDC);
  17. ASSERT(hMemDC);
  18. RECT rc;
  19. ::GetWindowRect(hWnd, &rc);
  20. HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top);
  21. ASSERT(hBitmap);
  22. HBITMAP hOldBmp = (HBITMAP)::SelectObject(hMemDC, hBitmap);
  23. BOOL bRet = FALSE;
  24. BOOL bProcessed = FALSE;
  25. // 针对win10 DWM虚拟缩放时的处理
  26. if (GetDeviceCaps(hDC, LOGPIXELSX) != 96)
  27. {
  28. DEVMODE curDevMode;
  29. memset(&curDevMode, 0, sizeof(curDevMode));
  30. curDevMode.dmSize = sizeof(DEVMODE);
  31. BOOL bEnumRet = ::EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &curDevMode);
  32. if (bEnumRet < curDevMode.dmPelsWidth)
  33. {
  34. bProcessed = TRUE;
  35. //::SetStretchBltMode(hMemDC, STRETCH_HALFTONE);
  36. //bRet = ::StretchBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, 0, 0, curDevMode.dmPelsWidth, curDevMode.dmPelsHeight, SRCCOPY | CAPTUREBLT);
  37. }
  38. }
  39. ::PrintWindow(hWnd, hMemDC, 0);
  40. BITMAP bitmap = { 0 };
  41. ::GetObject(hBitmap, sizeof(BITMAP), &bitmap);
  42. BITMAPINFOHEADER bi = { 0 };
  43. BITMAPFILEHEADER bf = { 0 };
  44. CONST int nBitCount = 24;
  45. bi.biSize = sizeof(BITMAPINFOHEADER);
  46. bi.biWidth = bitmap.bmWidth;
  47. bi.biHeight = bitmap.bmHeight;
  48. bi.biPlanes = 1;
  49. bi.biBitCount = nBitCount;
  50. bi.biCompression = BI_RGB;
  51. DWORD dwSize = ((bitmap.bmWidth * nBitCount + 31) / 32) * 4 * bitmap.bmHeight;
  52. HANDLE hDib = GlobalAlloc(GHND, dwSize + sizeof(BITMAPINFOHEADER));
  53. if (hDib == NULL)
  54. return;
  55. LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
  56. //memcpy(lpbi, &bi, sizeof(BITMAPINFOHEADER));
  57. *lpbi = bi;
  58. ::GetDIBits(hMemDC, hBitmap, 0, bitmap.bmHeight, (BYTE*)lpbi + sizeof(BITMAPINFOHEADER), (BITMAPINFO*)lpbi, DIB_RGB_COLORS);
  59. CFile file;
  60. CFileException fep;
  61. if (file.Open(strSavePath.c_str(), CFile::modeCreate | CFile::modeWrite, &fep)) {
  62. bf.bfType = 0x4d42;
  63. dwSize += sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
  64. bf.bfSize = dwSize;
  65. bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
  66. file.Write((BYTE*)&bf, sizeof(BITMAPFILEHEADER));
  67. file.Write((BYTE*)lpbi, dwSize);
  68. file.Close();
  69. }
  70. GlobalUnlock(hDib);
  71. GlobalFree(hDib);
  72. ::SelectObject(hMemDC, hOldBmp);
  73. ::DeleteObject(hBitmap);
  74. ::DeleteObject(hMemDC);
  75. ::ReleaseDC(hWnd, hDC);
  76. Sleep(30);
  77. }
  78. cv::Mat CaptureWnd(HWND hWnd)
  79. {
  80. HDC hdcMemDC = NULL; // 兼容DC;
  81. HDC hdcWindow = NULL; // 窗口DC;
  82. BITMAP bmpWindow = {0}; // BITMAP窗口对象;
  83. HBITMAP hbmpWindow = NULL; // HBITMAP窗口对象;
  84. DWORD dwBmpSize = 0; // BITMAP大小;
  85. char* lpbitmap = NULL; // BITMAP数据;
  86. HANDLE hDIB = NULL;
  87. DWORD dwSizeofDIB = 0;
  88. byte* pImgData = NULL;
  89. // 获取窗口DC;
  90. hdcWindow = ::GetWindowDC(hWnd);
  91. // 创建窗口DC的兼容DC;
  92. hdcMemDC = ::CreateCompatibleDC(hdcWindow);
  93. RECT rc,rcc;
  94. ::GetWindowRect(hWnd, &rc);
  95. ::GetClientRect(hWnd, &rcc);
  96. int width = rcc.right - rcc.left;
  97. int height = rcc.bottom - rcc.top;
  98. int dx = rc.right - rc.left - width;
  99. int dy = rc.bottom - rc.top - height;
  100. // 创建兼容位图;
  101. hbmpWindow = ::CreateCompatibleBitmap(hdcWindow, width, height);
  102. // 将位图选入兼容DC;
  103. SelectObject(hdcMemDC, hbmpWindow);
  104. #if 1
  105. // 将窗口复制到兼容DC中(当窗口被遮挡时,这个函数效果好)
  106. if(!::PrintWindow(hWnd, hdcMemDC, PW_CLIENTONLY))
  107. {
  108. goto done;
  109. }
  110. #else
  111. // 将目标DC的内容复制到兼容DC中;
  112. if (!BitBlt(hdcMemDC, 0, 0, width, height, hdcWindow, 0, 0, SRCCOPY))
  113. {
  114. goto done;
  115. }
  116. #endif
  117. // 通过HBITMAP获取BITMAP
  118. GetObject(hbmpWindow, sizeof(BITMAP), &bmpWindow);
  119. BITMAPFILEHEADER bmfHeader = {0};
  120. BITMAPINFOHEADER bi = {0};
  121. bi.biSize = sizeof(BITMAPINFOHEADER);
  122. bi.biWidth = bmpWindow.bmWidth;
  123. bi.biHeight = bmpWindow.bmHeight;
  124. bi.biPlanes = 1;
  125. bi.biBitCount = 32;
  126. bi.biCompression = BI_RGB;
  127. bi.biSizeImage = 0;
  128. bi.biXPelsPerMeter = 0;
  129. bi.biYPelsPerMeter = 0;
  130. bi.biClrUsed = 0;
  131. bi.biClrImportant = 0;
  132. dwBmpSize = ((bmpWindow.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpWindow.bmHeight;
  133. // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
  134. // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
  135. // have greater overhead than HeapAlloc.
  136. hDIB = GlobalAlloc(GHND, dwBmpSize);
  137. if (hDIB == NULL)
  138. {
  139. goto done;
  140. }
  141. lpbitmap = (char*)GlobalLock(hDIB);
  142. // Gets the "bits" from the bitmap, and copies them into a buffer
  143. // that's pointed to by lpbitmap.
  144. GetDIBits(hdcMemDC, hbmpWindow, 0,
  145. (UINT)bmpWindow.bmHeight,
  146. lpbitmap,
  147. (BITMAPINFO*)&bi, DIB_RGB_COLORS);
  148. // Add the size of the headers to the size of the bitmap to get the total file size.
  149. dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
  150. // Offset to where the actual bitmap bits start.
  151. bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
  152. // Size of the file.
  153. bmfHeader.bfSize = dwSizeofDIB;
  154. // bfType must always be BM for Bitmaps.
  155. bmfHeader.bfType = 0x4D42; // BM.
  156. pImgData = new byte[dwSizeofDIB];
  157. memset(pImgData, 0, dwSizeofDIB);
  158. memcpy(pImgData, &bmfHeader, sizeof(BITMAPFILEHEADER));
  159. memcpy(pImgData + sizeof(BITMAPFILEHEADER), &bi, sizeof(BITMAPINFOHEADER));
  160. memcpy(pImgData + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), lpbitmap, dwBmpSize);
  161. GlobalUnlock(hDIB);
  162. GlobalFree(hDIB);
  163. done:
  164. DeleteObject(hbmpWindow);
  165. DeleteObject(hdcMemDC);
  166. ReleaseDC(hWnd, hdcWindow);
  167. #if 0 // 原始的保留图片做法;
  168. CFile file;
  169. CFileException fep;
  170. if (file.Open("E:\\1.jpg", CFile::modeCreate | CFile::modeWrite, &fep)) {
  171. file.Write(pImgData, dwSizeofDIB);
  172. file.Close();
  173. }
  174. cv::Mat s22 = cv::imread("E:\\1.jpg");
  175. cv::imwrite("E:\\2.jpg",s22);
  176. #endif
  177. //cv::Mat pic;
  178. //int nChannels = bmpWindow.bmBitsPixel == 1 ? 1 : bmpWindow.bmBitsPixel / 8;
  179. //pic.create(cv::Size(width, height), CV_8UC4);
  180. //memcpy(pic.data, lpbitmap, dwBmpSize);
  181. if (pImgData)
  182. {
  183. cv::_InputArray pirArrary(pImgData, dwSizeofDIB);
  184. cv::Mat src = cv::imdecode(pirArrary, cv::IMREAD_COLOR);
  185. delete[]pImgData;
  186. pImgData = NULL;
  187. #ifdef DEBUG
  188. cv::imwrite("E:\\3.bmp", src); // 后缀不一样,得到的大小也不一样;
  189. cv::imwrite("E:\\3.jpg", src);
  190. #endif
  191. return src;
  192. }
  193. #if 0 // 能保存截图,但是被270度镜像;
  194. cv::Mat matPic(height, width, CV_8UC4, pImgData + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
  195. cv::imwrite("E:\\4.jpg", matPic);
  196. #endif
  197. return cv::Mat();
  198. }
  199. BOOL OpenImage(std::string strImag, cv::Mat& img)
  200. {
  201. if (!PathFileExists(strImag.c_str()))
  202. return FALSE;
  203. if (img.data != NULL)
  204. img.release();
  205. img = cv::imread(strImag.c_str(), cv::IMREAD_COLOR);
  206. if (img.data == NULL)
  207. return FALSE;
  208. return TRUE;
  209. }
  210. cv::Mat OpenImage(std::string strImage)
  211. {
  212. if (!PathFileExists(strImage.c_str()))
  213. return cv::Mat();
  214. cv::Mat img = cv::imread(strImage.c_str(), cv::IMREAD_COLOR);
  215. if (img.data == NULL)
  216. return cv::Mat();
  217. return cv::Mat();
  218. }
  219. BOOL SetImgThreshold(cv::Mat& srcImg, cv::Mat& thresholdImg, long nThresholdVal, long nMaxThresholdVal, int type)
  220. {
  221. if (srcImg.data == NULL)
  222. return FALSE;
  223. if (thresholdImg.data && srcImg.data != thresholdImg.data)
  224. thresholdImg.release();
  225. switch (type)
  226. {
  227. case 0://全局阀值;
  228. {
  229. // 转成灰阶图;
  230. cv::cvtColor(srcImg, thresholdImg, cv::COLOR_BGR2GRAY);
  231. // 再高斯模糊处理(滤波);
  232. cv::GaussianBlur(thresholdImg, thresholdImg, cv::Size(5, 5), 0, 0);
  233. // 二值化;//全局化指定的阀值与返回值相等;
  234. double dRetVal = cv::threshold(thresholdImg, thresholdImg, nThresholdVal, nMaxThresholdVal, cv::THRESH_BINARY);
  235. }
  236. break;
  237. case 1://自适应阀值;
  238. {
  239. // 转成灰阶图;
  240. cv::cvtColor(srcImg, thresholdImg, cv::COLOR_BGR2GRAY);
  241. // 再高斯模糊处理(滤波);
  242. cv::GaussianBlur(thresholdImg, thresholdImg, cv::Size(3, 3), 0, 0);
  243. // 自适应阀值;
  244. cv::medianBlur(thresholdImg, thresholdImg, 3);
  245. // 局部二值化;//blocksize一般取3、5、7
  246. cv::adaptiveThreshold(thresholdImg, thresholdImg, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 3, 4.5);
  247. }
  248. break;
  249. case 2:// otsu阀值;
  250. {
  251. cv::cvtColor(srcImg, thresholdImg, cv::COLOR_BGR2GRAY);
  252. // 高斯模糊;
  253. cv::GaussianBlur(thresholdImg, thresholdImg, cv::Size(3, 3), 0, 0);
  254. cv::threshold(thresholdImg, thresholdImg, nThresholdVal, nMaxThresholdVal, cv::THRESH_BINARY | cv::THRESH_OTSU);
  255. }
  256. break;
  257. default:
  258. break;
  259. }
  260. return TRUE;
  261. }
  262. BOOL GetImgMatchtemplate(HWND hWnd, cv::Mat cvTempImg, RECT& rc, double lowestMatchValue)
  263. {
  264. cv::Mat srcImg = CaptureWnd(hWnd);
  265. return GetImgMatchtemplate(srcImg, cvTempImg, rc, lowestMatchValue);
  266. }
  267. BOOL GetImgMatchtemplate(cv::Mat cvSrcImg, cv::Mat cvTempImg, RECT& rc, double lowestMatchValue)
  268. {
  269. if (cvSrcImg.empty() || cvTempImg.empty())
  270. {
  271. if (cvSrcImg.data) cvSrcImg.release();
  272. if (cvTempImg.data) cvTempImg.release();
  273. return FALSE;
  274. }
  275. cv::Mat matchImg;
  276. cv::matchTemplate(cvSrcImg, cvTempImg, matchImg, cv::TM_CCOEFF_NORMED);
  277. // 归一化到0~1
  278. //cv::normalize(matchImg, matchImg, 0, 1, cv::NORM_MINMAX, -1);
  279. double lv_nMinVal = 0.0;
  280. double lv_nMaxVal = 0.0;
  281. cv::Point lv_nMinLoc = cv::Point(0, 0);
  282. cv::Point lv_nMaxLoc = cv::Point(0, 0);
  283. cv::Point lv_MatchLoc = cv::Point(0, 0);
  284. cv::minMaxLoc(matchImg, &lv_nMinVal, &lv_nMaxVal, &lv_nMinLoc, &lv_nMaxLoc);
  285. cv::Rect roi = cv::Rect(lv_nMaxLoc.x, lv_nMaxLoc.y, cvTempImg.cols, cvTempImg.rows);
  286. cv::Mat roiImg = cvSrcImg(roi);
  287. #ifdef DEBUG
  288. cv::imwrite(GameGlobal::BuildImgPath(hWnd, _T("match")), roiImg);
  289. #endif
  290. rc.left = lv_nMaxLoc.x - GameGlobal::frameWidth;
  291. rc.top = lv_nMaxLoc.y - GameGlobal::titleBarHeight;
  292. rc.right = rc.left + cvTempImg.cols;
  293. rc.bottom = rc.top + cvTempImg.rows;
  294. cvSrcImg.release();
  295. cvTempImg.release();
  296. matchImg.release();
  297. roiImg.release();
  298. // 是否大于最低匹配度;
  299. return (lv_nMaxVal >= lowestMatchValue ? TRUE : FALSE);
  300. }
  301. BOOL GetImgMatchtemplate(HWND hWnd, std::string strTempImg, RECT& rc, double lowestMatchValue)
  302. {
  303. cv::Mat tempImg = OpenImage(strTempImg);
  304. return GetImgMatchtemplate(hWnd, tempImg, rc, lowestMatchValue);
  305. }
  306. BOOL GetImgMatchtemplate(std::string strSrcImg, std::string strTempImg, RECT& rc, double lowestMatchValue)
  307. {
  308. cv::Mat srcImg = OpenImage(strSrcImg), tempImg = OpenImage(strTempImg);
  309. return GetImgMatchtemplate(srcImg, tempImg, rc, lowestMatchValue);
  310. }
  311. BOOL IsMatchIcon(cv::Mat cvSrcImg, cv::Mat cvTempImg, RECT srcRect, RECT& matchRect, double lowestMatchValue)
  312. {
  313. if (cvSrcImg.empty() || cvTempImg.empty())
  314. {
  315. if (cvSrcImg.data) cvSrcImg.release();
  316. if (cvTempImg.data) cvTempImg.release();
  317. return FALSE;
  318. }
  319. // 指定源图片区域来做模块比较,提高效率;
  320. cv::Mat imgROI = cvSrcImg(cv::Rect(srcRect.left, srcRect.top, srcRect.right - srcRect.left, srcRect.bottom - srcRect.top));
  321. //cv::imwrite(GameGlobal::BuildImgPath(hWnd, _T("roi")), imgROI);
  322. /*
  323. m_ImgROI = srcImg([&]()-> cv::Rect {
  324. // 注意是x,y,w=cols,h=rows
  325. return cv::Rect(srcRect.left, srcRect.top, srcRect.right - srcRect.left, srcRect.bottom - srcRect.top);
  326. }());
  327. */
  328. cv::Mat matchImg;
  329. cv::matchTemplate(imgROI, cvTempImg, matchImg, cv::TM_CCOEFF_NORMED);
  330. // 归一化到0~1
  331. //cv::normalize(matchImg, matchImg, 0, 1, cv::NORM_MINMAX, -1);
  332. double lv_nMinVal = 0.0;
  333. double lv_nMaxVal = 0.0;
  334. cv::Point lv_nMinLoc = cv::Point(0, 0);
  335. cv::Point lv_nMaxLoc = cv::Point(0, 0);
  336. cv::Point lv_MatchLoc = cv::Point(0, 0);
  337. cv::minMaxLoc(matchImg, &lv_nMinVal, &lv_nMaxVal, &lv_nMinLoc, &lv_nMaxLoc);
  338. cv::Rect roi = cv::Rect(lv_nMaxLoc.x, lv_nMaxLoc.y, cvTempImg.cols, cvTempImg.rows);
  339. cv::Mat roiImg = imgROI(roi);
  340. #ifdef DEBUG
  341. cv::imwrite(GameGlobal::BuildImgPath(hWnd, _T("match")), roiImg);
  342. #endif
  343. matchRect.left = srcRect.left + lv_nMaxLoc.x - GameGlobal::frameWidth;
  344. matchRect.top = srcRect.top + lv_nMaxLoc.y - GameGlobal::titleBarHeight;
  345. matchRect.right = matchRect.left + cvTempImg.cols;
  346. matchRect.bottom = matchRect.top + cvTempImg.rows;
  347. imgROI.release();
  348. cvSrcImg.release();
  349. cvTempImg.release();
  350. matchImg.release();
  351. roiImg.release();
  352. // 是否大于最低匹配度;
  353. return (lv_nMaxVal >= lowestMatchValue ? TRUE : FALSE);
  354. }
  355. BOOL IsMatchIcon(HWND hWnd, std::string strTempImg, RECT srcRect, RECT& matchRect, double lowestMatchValue)
  356. {
  357. cv::Mat srcImg = CaptureWnd(hWnd), tempImg = OpenImage(strTempImg);
  358. return IsMatchIcon(srcImg, tempImg, srcRect, matchRect, lowestMatchValue);
  359. }
  360. BOOL CropPicture(HWND hWnd, CRect rc, const char* szSaveName)
  361. {
  362. cv::Mat imgROI = CropPicture(hWnd, rc);
  363. return cv::imwrite(szSaveName, imgROI);
  364. }
  365. cv::Mat CropPicture(HWND hWnd, CRect rc)
  366. {
  367. cv::Mat srcImg = CaptureWnd(hWnd), tempImg, matchImg;
  368. if (srcImg.empty())
  369. {
  370. if (srcImg.data) srcImg.release();
  371. return cv::Mat();
  372. }
  373. // 指定源图片区域来做模块比较,提高效率;
  374. cv::Mat imgROI;
  375. return srcImg(cv::Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top));
  376. }
  377. BOOL IsSimilarPicture(const char* szp1, const char* szp2, double lowestMatchValue)
  378. {
  379. return IsSimilarPicture(cv::imread(szp1), cv::imread(szp2), lowestMatchValue);
  380. }
  381. BOOL IsSimilarPicture(cv::Mat src1, cv::Mat src2, double lowestMatchValue)
  382. {
  383. cv::Mat matchImg;
  384. cv::matchTemplate(src1, src2, matchImg, cv::TM_CCOEFF_NORMED);
  385. // 归一化到0~1
  386. //cv::normalize(matchImg, matchImg, 0, 1, cv::NORM_MINMAX, -1);
  387. double lv_nMinVal = 0.0;
  388. double lv_nMaxVal = 0.0;
  389. cv::Point lv_nMinLoc = cv::Point(0, 0);
  390. cv::Point lv_nMaxLoc = cv::Point(0, 0);
  391. cv::Point lv_MatchLoc = cv::Point(0, 0);
  392. cv::minMaxLoc(matchImg, &lv_nMinVal, &lv_nMaxVal, &lv_nMinLoc, &lv_nMaxLoc);
  393. src1.release();
  394. matchImg.release();
  395. src2.release();
  396. // 是否大于最低匹配度;
  397. return (lv_nMaxVal >= lowestMatchValue ? TRUE : FALSE);
  398. }
  399. int GetColorOccupiedLength(std::string strImg, int r, int g, int b)
  400. {
  401. return GetColorOccupiedLength(cv::imread(strImg.c_str()), r, g,b);
  402. }
  403. int GetColorOccupiedLength(cv::Mat src, int r, int g, int b)
  404. {
  405. // 获取图片的矩形大小;
  406. int width = src.cols;
  407. int height = src.rows;
  408. int i = 0;
  409. for (; i < width; ++i)
  410. {
  411. cv::Vec3b pixel = src.at<cv::Vec3b>(height / 2, i); // 读取第 height / 2行 第 i 列像素值;
  412. if (abs(pixel[0] - b) <= 3 && abs(pixel[1] - g) <= 3 && abs(pixel[2] - r) <= 3)
  413. {
  414. continue;
  415. }
  416. break;
  417. }
  418. src.release();
  419. GameGlobal::DebugLog(_T("[GameAssist] 颜色占比=%d,%d,%d"), i, width, i * 100 / width);
  420. return i * 100 / width;
  421. }
  422. };