public static bool TraceLine(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen, FastPixelMatch colorMatch, out Point posHit, bool bDebugMode = false) { if (bDebugMode) { Logger.WriteLine("TraceLine [" + posX + ", " + posY + "] -> [" + (posX + (incX * traceLen)) + ", " + (posY + (incY * traceLen)) + "]"); } for (int stepIdx = 0; stepIdx < traceLen; stepIdx++) { int scanX = posX + (stepIdx * incX); int scanY = posY + (stepIdx * incY); FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY); bool bIsMatching = colorMatch.IsMatching(testPx); if (bDebugMode) { Logger.WriteLine(" [" + scanX + ", " + scanY + "] " + testPx + " => match:" + bIsMatching); } if (bIsMatching) { posHit = new Point(scanX, scanY); return(true); } } if (bDebugMode) { Logger.WriteLine(" >> failed"); } posHit = new Point(posX + (traceLen * incX), posY + (traceLen * incY)); return(false); }
public static FastBitmapHSV ConvertToFastBitmap(Bitmap image) { FastBitmapHSV result = new FastBitmapHSV(); result.Width = image.Width; result.Height = image.Height; result.Pixels = new FastPixelHSV[image.Width * image.Height]; unsafe { BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); int bytesPerPixel = Image.GetPixelFormatSize(image.PixelFormat) / 8; int bytesPerRow = bitmapData.Width * bytesPerPixel; for (int IdxY = 0; IdxY < image.Height; IdxY++) { byte *pixels = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride); int IdxPixel = IdxY * image.Width; for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel) { result.Pixels[IdxPixel] = new FastPixelHSV(pixels[IdxByte + 2], pixels[IdxByte + 1], pixels[IdxByte]); IdxPixel++; } } image.UnlockBits(bitmapData); } return(result); }
public static Bitmap ConvertToBitmap(FastBitmapHSV bitmap) { Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height); unsafe { BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat); int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8; int bytesPerRow = bitmapData.Width * bytesPerPixel; for (int IdxY = 0; IdxY < bmp.Height; IdxY++) { byte *pixels = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride); for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel) { FastPixelHSV writePx = bitmap.GetPixel(IdxByte / bytesPerPixel, IdxY); Color writeColor = Color.FromArgb(writePx.Monochrome, writePx.Monochrome, writePx.Monochrome); pixels[IdxByte + 3] = writeColor.A; pixels[IdxByte + 2] = writeColor.R; pixels[IdxByte + 1] = writeColor.G; pixels[IdxByte + 0] = writeColor.B; } } bmp.UnlockBits(bitmapData); } return(bmp); }
public virtual bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode) { this.debugMode = debugMode; debugShapes.Clear(); debugHashes.Clear(); return(false); }
private Rectangle FindCactpotCoords(FastBitmapHSV bitmap) { Rectangle rect = new Rectangle(); // find number fields // // scan method similar to triple triad board with matching top left corner and tile size int minCellSize = bitmap.Width * 2 / 100; int maxCellSize = bitmap.Width * 6 / 100; int maxScanX = bitmap.Width - (maxCellSize * 3) - 20; int maxScanY = bitmap.Height - (maxCellSize * 2) - 20; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); bool[] highlights = new bool[9]; bool bScanning = true; for (int IdxY = 0; IdxY < maxScanY && bScanning; IdxY++) { int ScanY = (bitmap.Height / 2) + ((IdxY / 2) * ((IdxY % 2 == 0) ? 1 : -1)); for (int IdxX = 10; IdxX < maxScanX && bScanning; IdxX++) { bool debugGridMatch = false; //debugGridMatch = (IdxX == 585) && (ScanY == 495); if (HasCactpotCircleEdgeV(bitmap, IdxX, ScanY, -1, debugGridMatch)) { for (int cellSize = minCellSize; cellSize < maxCellSize && bScanning; cellSize++) { if (HasCactpotCellMatch(bitmap, IdxX, ScanY, cellSize, out int CellPosX, out int CellPosY, out int offsetIn, debugGridMatch)) { if (HasCactpotCircleMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch)) { if (HasCactpotMatch(bitmap, CellPosX, CellPosY, cellSize, offsetIn, debugGridMatch)) { rect = new Rectangle(CellPosX, CellPosY, cellSize * 3, cellSize * 3); cachedOffsetIn = offsetIn; bScanning = false; break; } } } } } } } stopwatch.Stop(); if (debugMode) { Logger.WriteLine("FindCactpotCoords: " + stopwatch.ElapsedMilliseconds + "ms"); } return(rect); }
public static Bitmap CreateBitmapWithShapes(FastBitmapHSV bitmap, List <Rectangle> listBounds, List <FastBitmapHash> listHashes) { Bitmap bmp = new Bitmap(bitmap.Width, bitmap.Height); unsafe { BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat); int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8; int bytesPerRow = bitmapData.Width * bytesPerPixel; for (int IdxY = 0; IdxY < bmp.Height; IdxY++) { byte *pixels = (byte *)bitmapData.Scan0 + (IdxY * bitmapData.Stride); for (int IdxByte = 0; IdxByte < bytesPerRow; IdxByte += bytesPerPixel) { FastPixelHSV writePx = bitmap.GetPixel(IdxByte / bytesPerPixel, IdxY); Color writeColor = Color.FromArgb(writePx.Monochrome, writePx.Monochrome, writePx.Monochrome); pixels[IdxByte + 3] = writeColor.A; pixels[IdxByte + 2] = writeColor.R; pixels[IdxByte + 1] = writeColor.G; pixels[IdxByte + 0] = writeColor.B; } } bmp.UnlockBits(bitmapData); } using (Graphics gBmp = Graphics.FromImage(bmp)) { if (listBounds.Count > 0) { Pen boundsPen = new Pen(Color.Cyan); gBmp.DrawRectangles(boundsPen, listBounds.ToArray()); } Pen hashPen = new Pen(Color.Magenta); foreach (FastBitmapHash hashData in listHashes) { gBmp.DrawRectangle(hashPen, hashData.SourceBounds); if (hashData.Pixels != null) { for (int HashX = 0; HashX < hashData.Width; HashX++) { for (int HashY = 0; HashY < hashData.Height; HashY++) { byte writeValue = hashData.Pixels[HashX + (HashY * hashData.Width)]; int writeValueInt = Math.Min(255, writeValue * 16); Color writeColor = Color.FromArgb(writeValueInt, 0, 0); bmp.SetPixel(HashX + hashData.DrawPos.X, HashY + hashData.DrawPos.Y, writeColor); } } } } } return(bmp); }
public static List <Point> TraceSpansH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int minSize, bool bDebugMode = false) { List <Point> result = new List <Point>(); int lastX = -1; bool bHasMatch = false; for (int IdxX = box.Left; IdxX <= box.Right; IdxX++) { bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (lastX == -1 && bHasMatch) { lastX = IdxX; } else if (lastX >= 0 && !bHasMatch) { int spanSize = IdxX - lastX; if (spanSize > minSize) { if (bDebugMode) { Logger.WriteLine(">> adding span: " + lastX + ", size:" + spanSize); } result.Add(new Point(lastX, spanSize)); } lastX = -1; } } if (lastX >= 0 && bHasMatch) { int spanSize = box.Right - lastX + 1; if (spanSize > minSize) { if (bDebugMode) { Logger.WriteLine(">> adding span: " + lastX + ", size:" + spanSize); } result.Add(new Point(lastX, spanSize)); } } return(result); }
public static void SaveBitmapWithShapes(FastBitmapHSV bitmap, List <Rectangle> listBounds, List <FastBitmapHash> listHashes, string fileName) { if (File.Exists(fileName)) { File.Delete(fileName); } using (Bitmap bmp = CreateBitmapWithShapes(bitmap, listBounds, listHashes)) { bmp.Save(AssetManager.Get().CreateFilePath(fileName), ImageFormat.Png); } }
public static void FindColorRange(FastBitmapHSV bitmap, Rectangle box, out int minMono, out int maxMono) { minMono = 255; maxMono = 0; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { for (int IdxX = box.Left; IdxX <= box.Right; IdxX++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); minMono = Math.Min(minMono, testPx.Monochrome); maxMono = Math.Max(maxMono, testPx.Monochrome); } } }
public static float CountFillPct(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch) { int totalPixels = (box.Width + 1) * (box.Height + 1); int matchPixels = 0; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { for (int IdxX = box.Left; IdxX <= box.Right; IdxX++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); matchPixels += colorMatch.IsMatching(testPx) ? 1 : 0; } } return((float)matchPixels / totalPixels); }
private bool HasCactpotMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false) { if (bDebugDetection) { Logger.WriteLine("HasCactpotMatch[" + posX + ", " + posY + "]? testing..."); } // circle at (posX, posY) already matched, test other 8 bool bHasMatch = true; bool bHasMatch01 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY, cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch01; bool bHasMatch02 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY, cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch02; bool bHasMatch10 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + cellSize, cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch10; bool bHasMatch11 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + cellSize, cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch11; bool bHasMatch12 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + cellSize, cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch12; bool bHasMatch20 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch20; bool bHasMatch21 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + cellSize, posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch21; bool bHasMatch22 = (bHasMatch || bDebugDetection) && HasCactpotCircleMatch(bitmap, posX + (cellSize * 2), posY + (cellSize * 2), cellSize, offsetIn, bDebugDetection); bHasMatch = bHasMatch && bHasMatch22; if (bDebugDetection) { Logger.WriteLine(">> [#" + (bHasMatch01 ? "#" : ".") + (bHasMatch02 ? "#" : ".") + "][" + (bHasMatch10 ? "#" : ".") + (bHasMatch11 ? "#" : ".") + (bHasMatch12 ? "#" : ".") + "][" + (bHasMatch20 ? "#" : ".") + (bHasMatch21 ? "#" : ".") + (bHasMatch22 ? "#" : ".") + "] => " + (bHasMatch ? "match" : "nope")); } return(bHasMatch); }
public override bool DoWork(FastBitmapHSV bitmap, int scannerFlags, Stopwatch perfTimer, bool debugMode) { base.DoWork(bitmap, scannerFlags, perfTimer, debugMode); perfTimer.Restart(); if (!HasValidCache(bitmap, scannerFlags)) { cachedCircles = null; cachedBoard = FindCactpotCoords(bitmap); if (cachedBoard.Width > 0) { screenAnalyzer.currentScanArea = cachedBoard; cachedCircles = FindCactpotCircleCoords(cachedBoard); } } cachedGameState = null; cachedGameStateBase = null; if (cachedCircles != null) { screenAnalyzer.ClearKnownHashes(); cachedGameState = new GameState(); cachedGameStateBase = cachedGameState; for (int Idx = 0; Idx < cachedGameState.board.Length; Idx++) { cachedGameState.board[Idx] = ParseCactpotCircle(bitmap, cachedCircles[Idx], "board" + Idx); cachedGameState.numRevealed += (cachedGameState.board[Idx] != 0) ? 1 : 0; } } perfTimer.Stop(); if (debugMode) { Logger.WriteLine("Parse cactpot board: " + perfTimer.ElapsedMilliseconds + "ms"); } return(cachedGameState != null); }
private int ParseCactpotCircle(FastBitmapHSV bitmap, Rectangle circleBox, string debugName) { float[] values = ExtractCirclePattern(bitmap, circleBox); int result; ImageHashData pattern = new ImageHashData() { type = EImageHashType.Cactpot, previewBounds = circleBox, isKnown = true }; pattern.CalculateHash(values); // allow overwrites in case there is a user defined value, must be exact match ImageHashData overridePattern = ImageHashDB.Get().FindExactMatch(pattern); if (overridePattern != null) { result = (int)overridePattern.ownerOb; } else { result = classifierCactpot.Calculate(values, out float DummyPct); pattern.isAuto = true; } pattern.ownerOb = result; screenAnalyzer.AddImageHash(pattern); Rectangle previewBounds = new Rectangle(circleBox.X + circleBox.Width, circleBox.Y, 8, 16); debugHashes.Add(new ImageUtils.HashPreview() { hashValues = values, bounds = previewBounds }); debugShapes.Add(GetCirclePatternBounds(circleBox)); return(result); }
public static FastPixelHSV GetAverageColor(FastBitmapHSV bitmap, Rectangle bounds) { float scale = 1.0f / (bounds.Width * bounds.Height); float accR = 0.0f; float accG = 0.0f; float accB = 0.0f; for (int idxY = 0; idxY < bounds.Height; idxY++) { for (int idxX = 0; idxX < bounds.Width; idxX++) { FastPixelHSV testPx = bitmap.GetPixelRaw(bounds.X + idxX, bounds.Y + idxY); accR += testPx.RawR; accG += testPx.RawG; accB += testPx.RawB; } } FastPixelHSV avgPx = new FastPixelHSV((byte)(accR * scale), (byte)(accG * scale), (byte)(accB * scale)); avgPx.ExpandHSV(); return(avgPx); }
private bool HasCactpotCellMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, out int cellPosX, out int cellPosY, out int offsetIn, bool bDebugDetection = false) { cellPosX = 0; cellPosY = 0; offsetIn = 0; bool bHasEdgeV2 = HasCactpotCircleEdgeV(bitmap, posX + cellSize, posY, -1); if (bDebugDetection) { Logger.WriteLine("HasCactpotCellMatch[" + posX + ", " + posY + "], cellSize:" + cellSize + " => " + (bHasEdgeV2 ? "found V2" : "nope")); } if (bHasEdgeV2) { for (int Idx = 5; Idx < 20; Idx++) { int invPosX = posX + cellSize - Idx; bool bHasEdgeV3 = HasCactpotCircleEdgeV(bitmap, invPosX, posY, 1); if (bHasEdgeV3) { cellPosX = posX - (Idx / 2); cellPosY = posY - (cellSize / 2); offsetIn = Idx; if (bDebugDetection) { Logger.WriteLine(">> spacing check: V3 at X:{0}, offsetIn:{1}", invPosX, offsetIn); } return(true); } } } return(false); }
public static float[] ExtractImageFeaturesScaled(FastBitmapHSV bitmap, Rectangle bounds, int destWidth, int destHeight, Func <FastPixelHSV, float> pxFunc) { float[] values = new float[destWidth * destHeight]; // scale to requested size float scaleX = (float)destWidth / bounds.Width; float scaleY = (float)destHeight / bounds.Height; float endY = 0.0f; for (int hashY = 0; hashY < destHeight; hashY++) { float startY = endY; endY = (hashY + 1) / scaleY; if (endY >= bounds.Height) { endY = bounds.Height - 0.00001f; } float endX = 0.0f; for (int hashX = 0; hashX < destWidth; hashX++) { float startX = endX; endX = (hashX + 1) / scaleX; if (endX >= bounds.Width) { endX = bounds.Width - 0.00001f; } float sum = 0.0f; float sumDiv = 0.00001f; for (int srcY = (int)startY; srcY <= (int)endY; srcY++) { float partY = 1.0f; if (srcY == (int)startY) { partY -= startY - srcY; } if (srcY == (int)endY) { partY -= srcY + 1 - endY; } for (int srcX = (int)startX; srcX <= (int)endX; srcX++) { float partX = 1.0f; if (srcX == (int)startX) { partX -= startX - srcX; } if (srcX == (int)endX) { partX -= srcX + 1 - endX; } FastPixelHSV testPx = bitmap.GetPixel(bounds.Left + srcX, bounds.Top + srcY); float testPxValue = pxFunc(testPx); sum += testPxValue * partY * partX; sumDiv += partY * partX; } } values[hashX + (hashY * destWidth)] = sum / sumDiv; } } return(values); }
public static Point TraceBoundsH(FastBitmapHSV bitmap, Rectangle box, FastPixelMatch colorMatch, int maxGapSize, bool bDebugMode = false) { int boxCenter = (box.Right + box.Left) / 2; int minX = -1; int gapStart = -1; bool bPrevMatch = false; for (int IdxX = box.Left; IdxX < boxCenter; IdxX++) { bool bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (bHasMatch) { int gapSize = IdxX - gapStart; if ((gapSize > maxGapSize && gapStart > 0) || (minX < 0)) { minX = IdxX; gapStart = -1; } if (bDebugMode) { Logger.WriteLine(">> gapSize:" + gapSize + ", gapStart:" + gapStart + ", bPrevMatch:" + bPrevMatch + " => minX:" + minX); } } else { if (bPrevMatch) { gapStart = IdxX; if (bDebugMode) { Logger.WriteLine(">> gapStart:" + gapStart); } } } bPrevMatch = bHasMatch; } if (minX >= 0) { int maxX = -1; gapStart = -1; bPrevMatch = false; for (int IdxX = box.Right; IdxX > boxCenter; IdxX--) { bool bHasMatch = false; for (int IdxY = box.Top; IdxY <= box.Bottom; IdxY++) { FastPixelHSV testPx = bitmap.GetPixel(IdxX, IdxY); bHasMatch = colorMatch.IsMatching(testPx); if (bHasMatch) { if (bDebugMode) { Logger.WriteLine("[" + IdxX + ", " + IdxY + "] " + testPx + " => match!"); } break; } } if (bHasMatch) { int gapSize = gapStart - IdxX; if ((gapSize > maxGapSize && gapStart > 0) || (maxX < 0)) { maxX = IdxX; gapStart = -1; } if (bDebugMode) { Logger.WriteLine(">> gapSize:" + gapSize + ", gapStart:" + gapStart + ", bPrevMatch:" + bPrevMatch + " => maxX:" + maxX); } } else { if (bPrevMatch) { gapStart = IdxX; if (bDebugMode) { Logger.WriteLine(">> gapStart:" + gapStart); } } } bPrevMatch = bHasMatch; } if (maxX > minX) { return(new Point(minX, maxX - minX)); } else { if (bDebugMode) { Logger.WriteLine(">> TraceBoundsH: no match on right side!"); } } } else { if (bDebugMode) { Logger.WriteLine(">> TraceBoundsH: no match on left side!"); } } return(new Point()); }
private bool HasCactpotCircleMatch(FastBitmapHSV bitmap, int posX, int posY, int cellSize, int offsetIn, bool bDebugDetection = false) { int sizeA = cellSize; int offsetB = Math.Max(5, offsetIn - 2); int sizeB = sizeA - (offsetB * 2); Point[] testPoints = new Point[4]; int numHighlights = 0; // 4 points: corners of cell => dark background { testPoints[0].X = posX; testPoints[0].Y = posY; testPoints[1].X = posX + sizeA; testPoints[1].Y = posY; testPoints[2].X = posX; testPoints[2].Y = posY + sizeA; testPoints[3].X = posX + sizeA; testPoints[3].Y = posY + sizeA; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing A[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchBack.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not background: A[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } // 4 points: corners with offset B => dark background { testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + offsetB; testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + offsetB; testPoints[2].X = posX + offsetB; testPoints[2].Y = posY + sizeA - offsetB; testPoints[3].X = posX + sizeA - offsetB; testPoints[3].Y = posY + sizeA - offsetB; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B1[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchBack.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not background: B1[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } Point[] testPointCloser = new Point[4]; // 4 points: midpoints with offset B => yellow/white { testPoints[0].X = posX + offsetB; testPoints[0].Y = posY + (sizeA / 2); testPoints[1].X = posX + sizeA - offsetB; testPoints[1].Y = posY + (sizeA / 2); testPoints[2].X = posX + (sizeA / 2); testPoints[2].Y = posY + offsetB; testPoints[3].X = posX + (sizeA / 2); testPoints[3].Y = posY + sizeA - offsetB; for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B2[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (colorMatchCircleOutH.IsMatching(testPx)) { numHighlights++; } else if (!colorMatchCircleOut.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not circle: B2[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } // 4 points: midpoints of diagonal lines between midpoints B (C) => yellow/white { testPoints[0].X = posX + offsetB + (sizeB / 4); testPoints[0].Y = posY + offsetB + (sizeB / 4); testPoints[1].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[1].Y = posY + offsetB + (sizeB / 4); testPoints[2].X = posX + offsetB + (sizeB / 4); testPoints[2].Y = posY + sizeA - offsetB - (sizeB / 4); testPoints[3].X = posX + sizeA - offsetB - (sizeB / 4); testPoints[3].Y = posY + sizeA - offsetB - (sizeB / 4); for (int Idx = 0; Idx < 4; Idx++) { FastPixelHSV testPx = bitmap.GetPixel(testPoints[Idx].X, testPoints[Idx].Y); //if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] testing B3[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } if (!colorMatchCircleIn.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("[" + posX + ", " + posY + "] failed, not circle: B3[" + testPoints[Idx].X + "," + testPoints[Idx].Y + "]: " + testPx); } return(false); } } } return((numHighlights == 0) || (numHighlights == 4)); }
public static List <int> TraceLineSegments(FastBitmapHSV bitmap, int posX, int posY, int incX, int incY, int traceLen, FastPixelMatch colorMatch, int minSegSize, int segLimit, bool bDebugMode = false) { FastPixelHSV[] streakBuffer = new FastPixelHSV[minSegSize]; int bufferIdx = 0; for (int Idx = 0; Idx < streakBuffer.Length; Idx++) { streakBuffer[Idx] = new FastPixelHSV(255, 255, 255); } List <int> result = new List <int>(); bool bWasMatch = false; if (bDebugMode) { Logger.WriteLine("TraceLineSegments [" + posX + ", " + posY + "] -> [" + (posX + (incX * traceLen)) + ", " + (posY + (incY * traceLen)) + "]"); } for (int stepIdx = 0; stepIdx < traceLen; stepIdx++) { int scanX = posX + (stepIdx * incX); int scanY = posY + (stepIdx * incY); FastPixelHSV testPx = bitmap.GetPixel(scanX, scanY); streakBuffer[bufferIdx] = testPx; bufferIdx = (bufferIdx + 1) % minSegSize; bool bBufferMatching = true; for (int Idx = 0; Idx < streakBuffer.Length; Idx++) { bBufferMatching = bBufferMatching && colorMatch.IsMatching(streakBuffer[Idx]); } if (bDebugMode) { Logger.WriteLine(" [" + scanX + ", " + scanY + "] " + testPx + " => match:" + colorMatch.IsMatching(testPx) + ", buffer:" + bBufferMatching); } if (bBufferMatching != bWasMatch) { bWasMatch = bBufferMatching; int segPos = bBufferMatching ? (incX != 0) ? (scanX - (incX * minSegSize)) : (scanY - (incY * minSegSize)) : (incX != 0) ? scanX : scanY; result.Add(segPos); if (bDebugMode) { Logger.WriteLine(" >> mark segment:" + segPos); } if (result.Count >= segLimit && segLimit > 0) { break; } } } return(result); }
private bool HasCactpotCircleEdgeV(FastBitmapHSV bitmap, int posX, int posY, int sideX, bool bDebugDetection = false) { const int spreadY = 2; const int offsetDeepX = 5; FastPixelHSV testPx = bitmap.GetPixel(posX, posY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge center " + testPx); } return(false); } testPx = bitmap.GetPixel(posX - (sideX * offsetDeepX), posY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: deep center " + testPx); } return(false); } testPx = bitmap.GetPixel(posX, posY - spreadY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge top " + testPx); } return(false); } testPx = bitmap.GetPixel(posX, posY + spreadY); if (!colorMatchCircleOut.IsMatching(testPx) && !colorMatchCircleOutH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge bottom " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge center prev " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY - spreadY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge top prev " + testPx); } return(false); } testPx = bitmap.GetPixel(posX + sideX, posY + spreadY); if (!colorMatchBack.IsMatching(testPx) && !colorMatchCircleFade.IsMatching(testPx) && !colorMatchCircleFadeH.IsMatching(testPx)) { if (bDebugDetection) { Logger.WriteLine("HasCactpotCircleEdgeV[" + posX + "," + posY + "] failed: edge bottom prev " + testPx); } return(false); } return(true); }
private float[] ExtractCirclePattern(FastBitmapHSV bitmap, Rectangle circleBox) { return(ImageUtils.ExtractImageFeaturesScaled(bitmap, GetCirclePatternBounds(circleBox), 8, 16, ImageUtils.GetPixelFeaturesMono)); }
public static int FindPatternMatch(FastBitmapHSV bitmap, Point pos, FastPixelMatch colorMatch, List <ImagePatternDigit> patterns, out ImageDataDigit digitData, bool bDebugMode = false) { int bestValue = 0; int bestValueExact = 0; float totalBestScore = 0; float totalBestScoreExact = 0; const int offsetSize = 5; const int cacheWidth = offsetSize + 10 + offsetSize; const int cacheHeight = offsetSize + 8 + offsetSize; byte[] PixelMatch = new byte[cacheWidth * cacheHeight]; int bestPatternMatchX = 0; int bestPatternExactX = 0; int bestPatternMatchY = 0; int bestPatternExactY = 0; foreach (ImagePatternDigit pattern in patterns) { float bestScore = 0; float bestScoreExact = 0; int bestPosX = 0; int bestPosXExact = 0; int bestPosY = 0; int bestPosYExact = 0; int ByteIdx = 0; for (int IdxY = 0; IdxY < cacheHeight; IdxY++) { for (int IdxX = 0; IdxX < cacheWidth; IdxX++) { FastPixelHSV testPx = bitmap.GetPixel(pos.X + IdxX - offsetSize, pos.Y + IdxY - offsetSize); PixelMatch[ByteIdx] = colorMatch.IsMatching(testPx) ? (byte)1 : (byte)0; ByteIdx++; } } /*if (bDebugMode) * { * bestPosX = 894; * bestPosY = 615; * bestScore = HasPatternMatch(PixelMatch, bestPosX - pos.X + offsetSize, bestPosY - pos.Y + offsetSize, cacheWidth, * pattern, pattern.Value == 3 || pattern.Value == 5 || pattern.Value == 8); * } * else*/ { for (int offsetX = -offsetSize; offsetX <= offsetSize; offsetX++) { for (int offsetY = -offsetSize; offsetY <= offsetSize; offsetY++) { float testScore = HasPatternMatch(PixelMatch, offsetX + offsetSize, offsetY + offsetSize, cacheWidth, false, pattern); if (testScore > bestScore) { bestPosX = pos.X + offsetX; bestPosY = pos.Y + offsetY; bestScore = testScore; } float testScoreExact = HasPatternMatch(PixelMatch, offsetX + offsetSize, offsetY + offsetSize, cacheWidth, true, pattern); if (testScoreExact > bestScoreExact) { bestPosXExact = pos.X + offsetX; bestPosYExact = pos.Y + offsetY; bestScoreExact = testScoreExact; } } } } if (bestScore > totalBestScore) { totalBestScore = bestScore; bestValue = pattern.Value; bestPatternMatchX = bestPosX - pos.X + offsetSize; bestPatternMatchY = bestPosY - pos.Y + offsetSize; } if (bestScoreExact > totalBestScoreExact) { totalBestScoreExact = bestScoreExact; bestValueExact = pattern.Value; bestPatternExactX = bestPosXExact - pos.X + offsetSize; bestPatternExactY = bestPosYExact - pos.Y + offsetSize; } if (bDebugMode) { Logger.WriteLine("FindPatternMatch: " + pattern.Value + " best at (" + bestPosX + ", " + bestPosY + ") = " + (int)(100 * bestScore) + "%," + " exact at (" + bestPosXExact + ", " + bestPosYExact + ") = " + (int)(100 * bestScoreExact) + "%"); } } { byte[] digitDataPixels = new byte[10]; for (int IdxX = 0; IdxX < 10; IdxX++) { int Value = 0; int ByteIdx = (bestPatternMatchY * cacheWidth) + bestPatternMatchX + IdxX; for (int IdxY = 0; IdxY < 8; IdxY++) { Value |= (PixelMatch[ByteIdx + (IdxY * cacheWidth)] != 0) ? (1 << IdxY) : 0; } digitDataPixels[IdxX] = (byte)Value; } digitData = new ImageDataDigit(digitDataPixels); if (bDebugMode) { for (int IdxY = 0; IdxY < 8; IdxY++) { string debugDesc = ""; for (int IdxX = 0; IdxX < 10; IdxX++) { byte ColValue = digitDataPixels[IdxX]; byte RowMask = (byte)(1 << IdxY); debugDesc += ((ColValue & RowMask) != 0 ? "#" : ".") + " "; } Logger.WriteLine(debugDesc); } } } return((totalBestScoreExact > 0.95) ? bestValueExact : (totalBestScore > 0.8) ? bestValue : 0); }
public static bool CreateFloodFillBitmap(FastBitmapHSV srcBitmap, Point floodOrigin, Size floodExtent, FastPixelMatch colorMatch, out FastBitmapHSV floodBitmap, out Rectangle floodBounds, bool bDebugMode = false) { List <Point> floodPoints = new List <Point>(); int minX = floodOrigin.X; int maxX = floodOrigin.X; int minY = floodOrigin.Y; int maxY = floodOrigin.Y; Rectangle boundRect = new Rectangle(floodOrigin.X - floodExtent.Width, floodOrigin.Y - floodExtent.Height, floodExtent.Width * 2, floodExtent.Height * 2); Stack <Point> openList = new Stack <Point>(); openList.Push(floodOrigin); while (openList.Count > 0) { Point testPoint = openList.Pop(); if (floodPoints.Contains(testPoint)) { continue; } FastPixelHSV testPx = srcBitmap.GetPixel(testPoint.X, testPoint.Y); if (bDebugMode) { Logger.WriteLine("[" + testPoint.X + ", " + testPoint.Y + "] " + testPx + ", match:" + colorMatch.IsMatching(testPx) + ", inBounds:" + boundRect.Contains(testPoint)); } if (colorMatch.IsMatching(testPx) && boundRect.Contains(testPoint)) { floodPoints.Add(testPoint); minX = Math.Min(minX, testPoint.X); maxX = Math.Max(maxX, testPoint.X); minY = Math.Min(minY, testPoint.Y); maxY = Math.Max(maxY, testPoint.Y); openList.Push(new Point(testPoint.X - 1, testPoint.Y)); openList.Push(new Point(testPoint.X + 1, testPoint.Y)); openList.Push(new Point(testPoint.X, testPoint.Y - 1)); openList.Push(new Point(testPoint.X, testPoint.Y + 1)); openList.Push(new Point(testPoint.X - 1, testPoint.Y - 1)); openList.Push(new Point(testPoint.X + 1, testPoint.Y - 1)); openList.Push(new Point(testPoint.X - 1, testPoint.Y + 1)); openList.Push(new Point(testPoint.X + 1, testPoint.Y + 1)); } } floodBounds = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); if (floodPoints.Count > 0) { FastPixelHSV[] bitmapPixels = new FastPixelHSV[floodBounds.Width * floodBounds.Height]; for (int Idx = 0; Idx < bitmapPixels.Length; Idx++) { bitmapPixels[Idx] = new FastPixelHSV(false); } foreach (Point p in floodPoints) { int Idx = (p.X - minX) + ((p.Y - minY) * floodBounds.Width); bitmapPixels[Idx] = new FastPixelHSV(true); } floodBitmap = new FastBitmapHSV() { Pixels = bitmapPixels, Width = floodBounds.Width, Height = floodBounds.Height, }; } else { floodBitmap = null; } return(floodBitmap != null); }
public override bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags) { return((cachedBoard.Width > 0) && (cachedCircles != null) && HasCactpotMatch(bitmap, cachedBoard.Left, cachedBoard.Top, cachedBoard.Width / 3, cachedOffsetIn)); }
public virtual bool HasValidCache(FastBitmapHSV bitmap, int scannerFlags) { return(false); }
public static FastBitmapHash CalculateImageHash(FastBitmapHSV bitmap, Rectangle box, Rectangle contextBox, FastPixelMatch colorMatch, int hashWidth, int hashHeight, bool bNormalizeColors = false) { byte[] hashImage = new byte[hashWidth * hashHeight]; // scale to requested size float scaleX = (float)hashWidth / box.Width; float scaleY = (float)hashHeight / box.Height; float endY = 0.0f; for (int hashY = 0; hashY < hashHeight; hashY++) { float startY = endY; endY = (hashY + 1) / scaleY; if (endY >= box.Height) { endY = box.Height - 0.00001f; } float endX = 0.0f; for (int hashX = 0; hashX < hashWidth; hashX++) { float startX = endX; endX = (hashX + 1) / scaleX; if (endX >= box.Width) { endX = box.Width - 0.00001f; } float sum = 0.0f; int sumDivNum = 0; for (int srcY = (int)startY; srcY <= (int)endY; srcY++) { float partY = 1.0f; if (srcY == (int)startY) { partY -= startY - srcY; } if (srcY == (int)endY) { partY -= srcY + 1 - endY; } for (int srcX = (int)startX; srcX <= (int)endX; srcX++) { float partX = 1.0f; if (srcX == (int)startX) { partX -= startX - srcX; } if (srcX == (int)endX) { partX -= srcX + 1 - endX; } FastPixelHSV valuePx = bitmap.GetPixel(box.Left + srcX, box.Top + srcY); float srcValueNorm = colorMatch.IsMatching(valuePx) ? (bNormalizeColors ? 1.0f : (valuePx.Monochrome / 255.0f)) : 0.0f; sum += srcValueNorm * partY * partX; sumDivNum++; } } float storeValueNorm = sum / sumDivNum; hashImage[hashX + (hashY * hashWidth)] = (byte)Math.Min(15, 15 * storeValueNorm); } } TlshBuilder hashBuilder = new TlshBuilder(); hashBuilder.Update(hashImage); TlshHash complexHash = hashBuilder.IsValid(false) ? hashBuilder.GetHash(false) : null; ScanLineHash simpleHash = ScanLineHash.CreateFromImage(hashImage, hashWidth, hashHeight); HashCollection hashCollection = new HashCollection(complexHash, simpleHash); return(new FastBitmapHash { Hash = hashCollection, Height = hashHeight, Width = hashWidth, Pixels = hashImage, SourceBounds = box, ContextBounds = contextBox, DrawPos = new Point(box.Right + 1, box.Top), }); }
public void DoWork(EMode mode = EMode.Default, int scannerFlags = 0) { Stopwatch timerTotal = new Stopwatch(); Stopwatch timerStep = new Stopwatch(); timerTotal.Start(); bool debugMode = (mode & EMode.Debug) != EMode.None; activeScanner = null; currentMode = mode; // load input bool hasInputImage = LoadInputImage(mode, timerStep, scanClipBounds); if (hasInputImage) { // convert to HSL representation timerStep.Restart(); cachedFastBitmap = ImageUtils.ConvertToFastBitmap(screenReader.cachedScreenshot); timerStep.Stop(); if (debugMode) { Logger.WriteLine("Screenshot convert: " + timerStep.ElapsedMilliseconds + "ms"); } if (Logger.IsSuperVerbose()) { Logger.WriteLine("Screenshot: {0}x{1}", screenReader.cachedScreenshot.Width, screenReader.cachedScreenshot.Height); } // reset scanner's intermediate data bool canResetCache = (mode & EMode.NeverResetCache) == EMode.None; if (canResetCache) { bool forceResetCache = (mode & EMode.AlwaysResetCache) != EMode.None; if (forceResetCache) { unknownHashes.Clear(); currentHashMatches.Clear(); } // invalidate scanner's cache if input image size has changed if (forceResetCache || cachedBitmapSize.Width <= 0 || cachedBitmapSize.Width != cachedFastBitmap.Width || cachedBitmapSize.Height != cachedFastBitmap.Height) { cachedBitmapSize = new Size(cachedFastBitmap.Width, cachedFastBitmap.Height); foreach (var kvp in mapScanners) { kvp.Value.InvalidateCache(); } } } currentState = EState.NoScannerMatch; // pass 1: check if cache is still valid for requested scanner foreach (var kvp in mapScanners) { if ((kvp.Key & mode) != EMode.None && kvp.Value.HasValidCache(cachedFastBitmap, scannerFlags)) { bool scanned = false; try { scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode); } catch (Exception ex) { Logger.WriteLine("Failed to scan [1] image! {0}", ex); scanned = false; } if (scanned) { activeScanner = kvp.Value; currentState = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState; if (debugMode) { Logger.WriteLine("Scan [1] successful, type:{0}, state:{1}", kvp.Key, currentState); } } else { currentState = EState.ScannerErrors; } break; } } // pass 2: all requested if (activeScanner == null) { foreach (var kvp in mapScanners) { if ((kvp.Key & mode) != EMode.None) { bool scanned = false; try { scanned = kvp.Value.DoWork(cachedFastBitmap, scannerFlags, timerStep, debugMode); } catch (Exception ex) { Logger.WriteLine("Failed to scan [2] image! {0}", ex); scanned = false; } if (scanned) { activeScanner = kvp.Value; currentState = (currentState == EState.NoScannerMatch) ? EState.NoErrors : currentState; if (debugMode) { Logger.WriteLine("Scan [2] successful, type:{0}, state:{1}", kvp.Key, currentState); } break; } } } } // save debug markup if needed if ((activeScanner != null) && ((mode & EMode.DebugSaveMarkup) != EMode.None)) { List <Rectangle> debugBounds = new List <Rectangle>(); List <ImageUtils.HashPreview> debugHashes = new List <ImageUtils.HashPreview>(); activeScanner.AppendDebugShapes(debugBounds, debugHashes); if (currentScanArea.Width > 0) { debugBounds.Add(currentScanArea); } timerStep.Restart(); string imagePath = GetDefaultScreenshotPath() + "screenshot-markup.png"; if (File.Exists(imagePath)) { File.Delete(imagePath); } using (Bitmap markupBitmap = ImageUtils.ConvertToBitmap(cachedFastBitmap)) { ImageUtils.DrawDebugShapes(markupBitmap, debugBounds); ImageUtils.DrawDebugHashes(markupBitmap, debugHashes); markupBitmap.Save(imagePath, ImageFormat.Png); } timerStep.Stop(); Logger.WriteLine("Screenshot save: " + timerStep.ElapsedMilliseconds + "ms"); } } else { currentState = EState.NoInputImage; } timerTotal.Stop(); if (debugMode) { Logger.WriteLine("Screenshot TOTAL: " + timerTotal.ElapsedMilliseconds + "ms"); } }