1using System; 2using System.Collections.Generic; 3using System.Drawing; 4using System.Drawing.Drawing2D; 5using System.Drawing.Imaging; 6using System.Runtime.InteropServices; 7 8namespace WindowCapture.Detection 9{ 10 public class Detector 11 { 12 // Static instance for convenience 13 private static Detector _instance; 14 15 /// <summary> 16 /// Compute edge map from a bitmap (static wrapper for FrozenOverlay) 17 /// </summary> 18 public static byte[,] ComputeEdgeMap(Bitmap bmp) 19 { 20 if (_instance == null) _instance = new Detector(); 21 _instance.SetScreen(bmp); 22 23 // Use internal method and convert to byte[,] 24 int w = bmp.Width; 25 int h = bmp.Height; 26 var result = new byte[w, h]; 27 28 unsafe 29 { 30 var data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 31 try 32 { 33 byte* ptr = (byte*)data.Scan0; 34 int stride = data.Stride; 35 var edges = _instance.ComputeEdgeMapInternal(ptr, stride, w, h); 36 37 for (int x = 0; x < w; x++) 38 for (int y = 0; y < h; y++) 39 result[x, y] = (byte)Math.Min(255, edges[x, y]); 40 } 41 finally 42 { 43 bmp.UnlockBits(data); 44 } 45 } 46 47 return result; 48 } 49 50 /// <summary> 51 /// Detect region at point using edge map (static wrapper for FrozenOverlay) 52 /// </summary> 53 public static GraphicsPath DetectRegionAt(byte[,] edgeMap, Point pt, int width, int height) 54 { 55 if (_instance == null) _instance = new Detector(); 56 if (_instance.screen == null) return null; 57 58 return _instance.Detect(pt, 0); 59 } 60 61 Bitmap screen; 62 int w, h; 63 int[,] cachedEdgeMap; 64 byte[] cachedPixels; 65 int cachedStride; 66 List<Rectangle> detectedBounds = new List<Rectangle>(); 67 bool[,] floodVisited; // reused across flood-fills to avoid an 8MB alloc per mouse move on 4K 68 69 public void SetScreen(Bitmap bmp) 70 { 71 screen = bmp; 72 w = bmp.Width; 73 h = bmp.Height; 74 detectedBounds.Clear(); 75 cachedEdgeMap = null; 76 cachedPixels = null; 77 floodVisited = new bool[w, h]; // sized once per screen; flood-fills clear and reuse it 78 } 79 80 public unsafe GraphicsPath Detect(Point cur, int expandLevel) 81 { 82 if (screen == null || cur.X < 0 || cur.X >= w || cur.Y < 0 || cur.Y >= h) return null; 83 84 var data = screen.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 85 86 try 87 { 88 byte* ptr = (byte*)data.Scan0; 89 int stride = data.Stride; 90 91 if (cachedEdgeMap == null) 92 { 93 cachedEdgeMap = ComputeEdgeMap(ptr, stride); 94 cachedStride = stride; 95 cachedPixels = new byte[h * stride]; 96 Marshal.Copy(data.Scan0, cachedPixels, 0, cachedPixels.Length); 97 } 98 99 var result = DetectRegion(ptr, stride, cachedEdgeMap, cur, expandLevel); 100 return result; 101 } 102 finally { screen.UnlockBits(data); } 103 } 104 105 unsafe GraphicsPath DetectRegion(byte* ptr, int stride, int[,] edgeMap, Point cur, int expandLevel) 106 { 107 int minY = h, maxY = 0; 108 var rowLeft = new int[h]; 109 var rowRight = new int[h]; 110 for (int i = 0; i < h; i++) { rowLeft[i] = w; rowRight[i] = -1; } 111 112 int sR, sG, sB; 113 GetMedianColor(ptr, stride, cur.X, cur.Y, 2, out sR, out sG, out sB); 114 115 if (edgeMap[cur.X, cur.Y] > 20) 116 { 117 Point interior = FindTrueInterior(ptr, stride, edgeMap, cur, sR, sG, sB); 118 if (interior.X >= 0) 119 { 120 cur = interior; 121 GetMedianColor(ptr, stride, cur.X, cur.Y, 2, out sR, out sG, out sB); 122 } 123 } 124 125 int colorThr = AnalyzeRegionThreshold(ptr, stride, edgeMap, cur.X, cur.Y, sR, sG, sB); 126 127 var visited = floodVisited; Array.Clear(visited, 0, visited.Length); 128 var queue = new Queue<Point>(); 129 queue.Enqueue(cur); 130 visited[cur.X, cur.Y] = true; 131 132 int[] dx = { -1, 1, 0, 0 }; 133 int[] dy = { 0, 0, -1, 1 }; 134 135 while (queue.Count > 0) 136 { 137 var p = queue.Dequeue(); 138 139 if (p.X < rowLeft[p.Y]) rowLeft[p.Y] = p.X; 140 if (p.X > rowRight[p.Y]) rowRight[p.Y] = p.X; 141 if (p.Y < minY) minY = p.Y; 142 if (p.Y > maxY) maxY = p.Y; 143 144 for (int i = 0; i < 4; i++) 145 { 146 int nx = p.X + dx[i], ny = p.Y + dy[i]; 147 if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue; 148 if (visited[nx, ny]) continue; 149 150 visited[nx, ny] = true; 151 152 if (IsSmartBoundary(ptr, stride, edgeMap, nx, ny, sR, sG, sB, colorThr)) 153 continue; 154 155 queue.Enqueue(new Point(nx, ny)); 156 } 157 } 158 159 if (maxY - minY < 5) return null; 160 161 int minX = w, maxX = 0; 162 for (int y = minY; y <= maxY; y++) 163 { 164 if (rowRight[y] >= rowLeft[y]) 165 { 166 if (rowLeft[y] < minX) minX = rowLeft[y]; 167 if (rowRight[y] > maxX) maxX = rowRight[y]; 168 } 169 } 170 if (maxX - minX < 5) return null; 171 172 var currentBounds = new Rectangle(minX, minY, maxX - minX, maxY - minY); 173 174 if (expandLevel > 0) 175 { 176 return ExpandToParent(ptr, stride, edgeMap, currentBounds, expandLevel, rowLeft, rowRight, minY, maxY); 177 } 178 179 var polygon = BuildOrthogonalPolygon(rowLeft, rowRight, minY, maxY); 180 if (polygon.Count < 4) return null; 181 182 var path = new GraphicsPath(); 183 path.AddPolygon(polygon.ToArray()); 184 return path; 185 } 186 187 unsafe GraphicsPath ExpandToParent(byte* ptr, int stride, int[,] edgeMap, Rectangle innerBounds, int expandLevel, int[] baseRowLeft, int[] baseRowRight, int baseMinY, int baseMaxY) 188 { 189 var allCandidates = new List<Rectangle>(); 190 191 var rectCandidates = FindEnclosingRectangles(ptr, stride, edgeMap, innerBounds); 192 allCandidates.AddRange(rectCandidates); 193 194 var floodCandidates = FindContainersByFloodFill(ptr, stride, edgeMap, innerBounds); 195 allCandidates.AddRange(floodCandidates); 196 197 var windowCandidates = FindWindowBounds(edgeMap, innerBounds); 198 allCandidates.AddRange(windowCandidates); 199 200 var unique = RemoveDuplicateRects(allCandidates); 201 unique.Sort((a, b) => (a.Width * a.Height).CompareTo(b.Width * b.Height)); 202 unique = unique.FindAll(r => r.Width > innerBounds.Width + 3 || r.Height > innerBounds.Height + 3); 203 204 int index = Math.Min(expandLevel - 1, unique.Count - 1); 205 if (index < 0 || unique.Count == 0) 206 { 207 var polygon = BuildOrthogonalPolygon(baseRowLeft, baseRowRight, baseMinY, baseMaxY); 208 if (polygon.Count >= 4) 209 { 210 var p = new GraphicsPath(); 211 p.AddPolygon(polygon.ToArray()); 212 return p; 213 } 214 var fallback = new GraphicsPath(); 215 fallback.AddRectangle(innerBounds); 216 return fallback; 217 } 218 219 Rectangle selected = unique[index]; 220 var path = new GraphicsPath(); 221 path.AddRectangle(selected); 222 return path; 223 } 224 225 unsafe List<Rectangle> FindContainersByFloodFill(byte* ptr, int stride, int[,] edgeMap, Rectangle inner) 226 { 227 var results = new List<Rectangle>(); 228 229 Point[] startPoints = new Point[] { 230 new Point(inner.Left - 5, inner.Top + inner.Height / 2), 231 new Point(inner.Right + 5, inner.Top + inner.Height / 2), 232 new Point(inner.Left + inner.Width / 2, inner.Top - 5), 233 new Point(inner.Left + inner.Width / 2, inner.Bottom + 5) 234 }; 235 236 foreach (var start in startPoints) 237 { 238 if (start.X < 2 || start.X >= w - 2 || start.Y < 2 || start.Y >= h - 2) continue; 239 if (edgeMap[start.X, start.Y] > 25) continue; 240 241 var bounds = QuickFloodBounds(ptr, stride, edgeMap, start, inner); 242 if (bounds.Width > inner.Width && bounds.Height > inner.Height && bounds.Contains(inner)) 243 { 244 results.Add(bounds); 245 } 246 } 247 248 return results; 249 } 250 251 unsafe Rectangle QuickFloodBounds(byte* ptr, int stride, int[,] edgeMap, Point start, Rectangle mustContain) 252 { 253 int minX = start.X, maxX = start.X, minY = start.Y, maxY = start.Y; 254 255 int sR, sG, sB; 256 GetMedianColor(ptr, stride, start.X, start.Y, 2, out sR, out sG, out sB); 257 258 var visited = floodVisited; Array.Clear(visited, 0, visited.Length); 259 var queue = new Queue<Point>(); 260 queue.Enqueue(start); 261 visited[start.X, start.Y] = true; 262 263 int[] dx = { -1, 1, 0, 0 }; 264 int[] dy = { 0, 0, -1, 1 }; 265 int count = 0; 266 int maxCount = 500000; 267 268 while (queue.Count > 0 && count < maxCount) 269 { 270 var p = queue.Dequeue(); 271 count++; 272 273 if (p.X < minX) minX = p.X; 274 if (p.X > maxX) maxX = p.X; 275 if (p.Y < minY) minY = p.Y; 276 if (p.Y > maxY) maxY = p.Y; 277 278 for (int i = 0; i < 4; i++) 279 { 280 int nx = p.X + dx[i], ny = p.Y + dy[i]; 281 if (nx < 1 || nx >= w - 1 || ny < 1 || ny >= h - 1) continue; 282 if (visited[nx, ny]) continue; 283 284 visited[nx, ny] = true; 285 286 if (edgeMap[nx, ny] > 30) continue; 287 288 int o = ny * stride + nx * 4; 289 int diff = Math.Abs(ptr[o + 2] - sR) + Math.Abs(ptr[o + 1] - sG) + Math.Abs(ptr[o] - sB); 290 if (diff > 60) continue; 291 292 queue.Enqueue(new Point(nx, ny)); 293 } 294 } 295 296 return new Rectangle(minX, minY, maxX - minX, maxY - minY); 297 } 298 299 List<Rectangle> FindWindowBounds(int[,] edgeMap, Rectangle inner) 300 { 301 var results = new List<Rectangle>(); 302 303 for (int y = inner.Top - 5; y >= Math.Max(0, inner.Top - 100); y--) 304 { 305 int lineLen = 0; 306 int lineStart = -1; 307 308 for (int x = inner.Left - 50; x <= inner.Right + 50 && x < w; x++) 309 { 310 if (x < 0) continue; 311 if (edgeMap[x, y] > 15) 312 { 313 if (lineStart < 0) lineStart = x; 314 lineLen++; 315 } 316 else if (lineLen > 0 && edgeMap[x, y] <= 15) 317 { 318 if (lineLen > inner.Width * 0.9) 319 { 320 Rectangle windowRect = FindWindowFrame(edgeMap, lineStart, y, lineStart + lineLen); 321 if (windowRect.Width > 0 && windowRect.Contains(inner)) 322 { 323 results.Add(windowRect); 324 } 325 } 326 lineLen = 0; 327 lineStart = -1; 328 } 329 } 330 } 331 332 return results; 333 } 334 335 Rectangle FindWindowFrame(int[,] edgeMap, int xStart, int yTop, int xEnd) 336 { 337 int left = xStart; 338 for (int x = xStart; x >= Math.Max(0, xStart - 20); x--) 339 { 340 int vertCount = 0; 341 for (int y = yTop; y < Math.Min(h, yTop + 100); y++) 342 { 343 if (edgeMap[x, y] > 15) vertCount++; 344 } 345 if (vertCount > 50) { left = x; break; } 346 } 347 348 int right = xEnd; 349 for (int x = xEnd; x < Math.Min(w, xEnd + 20); x++) 350 { 351 int vertCount = 0; 352 for (int y = yTop; y < Math.Min(h, yTop + 100); y++) 353 { 354 if (edgeMap[x, y] > 15) vertCount++; 355 } 356 if (vertCount > 50) { right = x; break; } 357 } 358 359 int bottom = yTop + 100; 360 for (int y = yTop + 50; y < Math.Min(h, yTop + 800); y++) 361 { 362 int horCount = 0; 363 for (int x = left; x <= right; x += 3) 364 { 365 if (edgeMap[x, y] > 15) horCount++; 366 } 367 if (horCount > (right - left) / 3 * 0.7) 368 { 369 bottom = y; 370 break; 371 } 372 } 373 374 if (right - left > 50 && bottom - yTop > 50) 375 { 376 return new Rectangle(left, yTop, right - left, bottom - yTop); 377 } 378 return Rectangle.Empty; 379 } 380 381 List<Rectangle> RemoveDuplicateRects(List<Rectangle> rects) 382 { 383 var unique = new List<Rectangle>(); 384 foreach (var r in rects) 385 { 386 if (r.Width < 10 || r.Height < 10) continue; 387 388 bool isDupe = false; 389 foreach (var u in unique) 390 { 391 if (Math.Abs(r.X - u.X) < 8 && Math.Abs(r.Y - u.Y) < 8 && 392 Math.Abs(r.Width - u.Width) < 15 && Math.Abs(r.Height - u.Height) < 15) 393 { 394 isDupe = true; 395 break; 396 } 397 } 398 if (!isDupe) unique.Add(r); 399 } 400 return unique; 401 } 402 403 unsafe List<Rectangle> FindEnclosingRectangles(byte* ptr, int stride, int[,] edgeMap, Rectangle inner) 404 { 405 var results = new List<Rectangle>(); 406 int maxSearch = 300; 407 408 var horizontalLines = new List<int[]>(); 409 410 for (int y = inner.Top - 1; y >= Math.Max(0, inner.Top - maxSearch); y--) 411 { 412 var line = FindHorizontalLine(edgeMap, y, inner.Left - 50, inner.Right + 50); 413 if (line != null && line[2] - line[1] >= inner.Width * 0.8) 414 { 415 horizontalLines.Add(new int[] { y, line[1], line[2], 1 }); 416 } 417 } 418 419 for (int y = inner.Bottom + 1; y < Math.Min(h, inner.Bottom + maxSearch); y++) 420 { 421 var line = FindHorizontalLine(edgeMap, y, inner.Left - 50, inner.Right + 50); 422 if (line != null && line[2] - line[1] >= inner.Width * 0.8) 423 { 424 horizontalLines.Add(new int[] { y, line[1], line[2], -1 }); 425 } 426 } 427 428 var verticalLines = new List<int[]>(); 429 430 for (int x = inner.Left - 1; x >= Math.Max(0, inner.Left - maxSearch); x--) 431 { 432 var line = FindVerticalLine(edgeMap, x, inner.Top - 50, inner.Bottom + 50); 433 if (line != null && line[2] - line[1] >= inner.Height * 0.8) 434 { 435 verticalLines.Add(new int[] { x, line[1], line[2], 1 }); 436 } 437 } 438 439 for (int x = inner.Right + 1; x < Math.Min(w, inner.Right + maxSearch); x++) 440 { 441 var line = FindVerticalLine(edgeMap, x, inner.Top - 50, inner.Bottom + 50); 442 if (line != null && line[2] - line[1] >= inner.Height * 0.8) 443 { 444 verticalLines.Add(new int[] { x, line[1], line[2], -1 }); 445 } 446 } 447 448 foreach (var top in horizontalLines.FindAll(l => l[3] == 1)) 449 { 450 foreach (var bottom in horizontalLines.FindAll(l => l[3] == -1)) 451 { 452 if (bottom[0] <= top[0]) continue; 453 454 foreach (var left in verticalLines.FindAll(l => l[3] == 1)) 455 { 456 foreach (var right in verticalLines.FindAll(l => l[3] == -1)) 457 { 458 if (right[0] <= left[0]) continue; 459 460 bool validRect = true; 461 462 if (top[1] > left[0] + 10 || top[2] < right[0] - 10) validRect = false; 463 if (bottom[1] > left[0] + 10 || bottom[2] < right[0] - 10) validRect = false; 464 if (left[1] > top[0] + 10 || left[2] < bottom[0] - 10) validRect = false; 465 if (right[1] > top[0] + 10 || right[2] < bottom[0] - 10) validRect = false; 466 467 if (validRect) 468 { 469 var rect = new Rectangle(left[0], top[0], right[0] - left[0], bottom[0] - top[0]); 470 471 if (rect.Contains(inner) && rect.Width > inner.Width + 5 && rect.Height > inner.Height + 5) 472 { 473 if (rect.Width < inner.Width * 4 && rect.Height < inner.Height * 4) 474 { 475 results.Add(rect); 476 } 477 } 478 } 479 } 480 } 481 } 482 } 483 484 int closestTop = FindClosestEdge(edgeMap, inner, 0, -1); 485 int closestBottom = FindClosestEdge(edgeMap, inner, 0, 1); 486 int closestLeft = FindClosestEdge(edgeMap, inner, -1, 0); 487 int closestRight = FindClosestEdge(edgeMap, inner, 1, 0); 488 489 if (closestTop != inner.Top || closestBottom != inner.Bottom || 490 closestLeft != inner.Left || closestRight != inner.Right) 491 { 492 var fallbackRect = new Rectangle(closestLeft, closestTop, 493 closestRight - closestLeft, closestBottom - closestTop); 494 if (fallbackRect.Width > inner.Width + 3 || fallbackRect.Height > inner.Height + 3) 495 { 496 results.Add(fallbackRect); 497 } 498 } 499 500 var unique = new List<Rectangle>(); 501 foreach (var r in results) 502 { 503 bool isDupe = false; 504 foreach (var u in unique) 505 { 506 if (Math.Abs(r.X - u.X) < 5 && Math.Abs(r.Y - u.Y) < 5 && 507 Math.Abs(r.Width - u.Width) < 10 && Math.Abs(r.Height - u.Height) < 10) 508 { 509 isDupe = true; 510 break; 511 } 512 } 513 if (!isDupe) unique.Add(r); 514 } 515 516 return unique; 517 } 518 519 int[] FindHorizontalLine(int[,] edgeMap, int y, int xMin, int xMax) 520 { 521 if (y < 1 || y >= h - 1) return null; 522 xMin = Math.Max(1, xMin); 523 xMax = Math.Min(w - 2, xMax); 524 525 int lineStart = -1, lineEnd = -1; 526 int consecutiveEdge = 0; 527 528 for (int x = xMin; x <= xMax; x++) 529 { 530 bool isEdge = edgeMap[x, y] > 20; 531 532 if (isEdge) 533 { 534 if (lineStart < 0) lineStart = x; 535 lineEnd = x; 536 consecutiveEdge++; 537 } 538 else 539 { 540 if (consecutiveEdge > 0 && lineEnd >= 0 && x - lineEnd > 3) 541 { 542 if (lineEnd - lineStart >= 20) 543 { 544 return new int[] { y, lineStart, lineEnd }; 545 } 546 lineStart = -1; 547 lineEnd = -1; 548 consecutiveEdge = 0; 549 } 550 } 551 } 552 553 if (lineEnd - lineStart >= 20) 554 { 555 return new int[] { y, lineStart, lineEnd }; 556 } 557 return null; 558 } 559 560 int[] FindVerticalLine(int[,] edgeMap, int x, int yMin, int yMax) 561 { 562 if (x < 1 || x >= w - 1) return null; 563 yMin = Math.Max(1, yMin); 564 yMax = Math.Min(h - 2, yMax); 565 566 int lineStart = -1, lineEnd = -1; 567 int consecutiveEdge = 0; 568 569 for (int y = yMin; y <= yMax; y++) 570 { 571 bool isEdge = edgeMap[x, y] > 20; 572 573 if (isEdge) 574 { 575 if (lineStart < 0) lineStart = y; 576 lineEnd = y; 577 consecutiveEdge++; 578 } 579 else 580 { 581 if (consecutiveEdge > 0 && lineEnd >= 0 && y - lineEnd > 3) 582 { 583 if (lineEnd - lineStart >= 20) 584 { 585 return new int[] { x, lineStart, lineEnd }; 586 } 587 lineStart = -1; 588 lineEnd = -1; 589 consecutiveEdge = 0; 590 } 591 } 592 } 593 594 if (lineEnd - lineStart >= 20) 595 { 596 return new int[] { x, lineStart, lineEnd }; 597 } 598 return null; 599 } 600 601 int FindClosestEdge(int[,] edgeMap, Rectangle bounds, int dx, int dy) 602 { 603 int maxDist = 200; 604 605 if (dy < 0) 606 { 607 for (int dist = 1; dist < maxDist; dist++) 608 { 609 int y = bounds.Top - dist; 610 if (y < 1) break; 611 612 int edgeCount = 0; 613 for (int x = bounds.Left; x <= bounds.Right; x += 5) 614 { 615 if (x >= 0 && x < w && edgeMap[x, y] > 20) edgeCount++; 616 } 617 if (edgeCount >= (bounds.Width / 5) * 0.5) 618 return y; 619 } 620 return bounds.Top; 621 } 622 else if (dy > 0) 623 { 624 for (int dist = 1; dist < maxDist; dist++) 625 { 626 int y = bounds.Bottom + dist; 627 if (y >= h - 1) break; 628 629 int edgeCount = 0; 630 for (int x = bounds.Left; x <= bounds.Right; x += 5) 631 { 632 if (x >= 0 && x < w && edgeMap[x, y] > 20) edgeCount++; 633 } 634 if (edgeCount >= (bounds.Width / 5) * 0.5) 635 return y; 636 } 637 return bounds.Bottom; 638 } 639 else if (dx < 0) 640 { 641 for (int dist = 1; dist < maxDist; dist++) 642 { 643 int x = bounds.Left - dist; 644 if (x < 1) break; 645 646 int edgeCount = 0; 647 for (int y = bounds.Top; y <= bounds.Bottom; y += 5) 648 { 649 if (y >= 0 && y < h && edgeMap[x, y] > 20) edgeCount++; 650 } 651 if (edgeCount >= (bounds.Height / 5) * 0.5) 652 return x; 653 } 654 return bounds.Left; 655 } 656 else 657 { 658 for (int dist = 1; dist < maxDist; dist++) 659 { 660 int x = bounds.Right + dist; 661 if (x >= w - 1) break; 662 663 int edgeCount = 0; 664 for (int y = bounds.Top; y <= bounds.Bottom; y += 5) 665 { 666 if (y >= 0 && y < h && edgeMap[x, y] > 20) edgeCount++; 667 } 668 if (edgeCount >= (bounds.Height / 5) * 0.5) 669 return x; 670 } 671 return bounds.Right; 672 } 673 } 674 675 internal unsafe int[,] ComputeEdgeMapInternal(byte* ptr, int stride, int width, int height) 676 { 677 int oldW = w, oldH = h; 678 w = width; h = height; 679 var result = ComputeEdgeMap(ptr, stride); 680 w = oldW; h = oldH; 681 return result; 682 } 683 684 unsafe int[,] ComputeEdgeMap(byte* ptr, int stride) 685 { 686 var edges = new int[w, h]; 687 688 for (int y = 1; y < h - 1; y++) 689 { 690 for (int x = 1; x < w - 1; x++) 691 { 692 int gxR = 0, gyR = 0, gxG = 0, gyG = 0, gxB = 0, gyB = 0; 693 694 for (int j = -1; j <= 1; j++) 695 { 696 for (int i = -1; i <= 1; i++) 697 { 698 int o = (y + j) * stride + (x + i) * 4; 699 int kx = i * (j == 0 ? 2 : 1); 700 int ky = j * (i == 0 ? 2 : 1); 701 gxB += kx * ptr[o]; 702 gxG += kx * ptr[o + 1]; 703 gxR += kx * ptr[o + 2]; 704 gyB += ky * ptr[o]; 705 gyG += ky * ptr[o + 1]; 706 gyR += ky * ptr[o + 2]; 707 } 708 } 709 710 int gR = (int)Math.Sqrt(gxR * gxR + gyR * gyR); 711 int gG = (int)Math.Sqrt(gxG * gxG + gyG * gyG); 712 int gB = (int)Math.Sqrt(gxB * gxB + gyB * gyB); 713 edges[x, y] = Math.Max(gR, Math.Max(gG, gB)); 714 } 715 } 716 717 for (int y = 0; y < h; y++) { edges[0, y] = edges[w - 1, y] = 255; } 718 for (int x = 0; x < w; x++) { edges[x, 0] = edges[x, h - 1] = 255; } 719 720 return edges; 721 } 722 723 unsafe void GetMedianColor(byte* ptr, int stride, int cx, int cy, int radius, out int r, out int g, out int b) 724 { 725 var reds = new List<int>(); 726 var greens = new List<int>(); 727 var blues = new List<int>(); 728 729 for (int dy = -radius; dy <= radius; dy++) 730 { 731 for (int dx = -radius; dx <= radius; dx++) 732 { 733 int px = cx + dx, py = cy + dy; 734 if (px >= 0 && px < w && py >= 0 && py < h) 735 { 736 int o = py * stride + px * 4; 737 blues.Add(ptr[o]); 738 greens.Add(ptr[o + 1]); 739 reds.Add(ptr[o + 2]); 740 } 741 } 742 } 743 744 reds.Sort(); greens.Sort(); blues.Sort(); 745 int mid = reds.Count / 2; 746 r = reds[mid]; g = greens[mid]; b = blues[mid]; 747 } 748 749 unsafe Point FindTrueInterior(byte* ptr, int stride, int[,] edgeMap, Point start, int baseR, int baseG, int baseB) 750 { 751 for (int radius = 2; radius <= 15; radius += 2) 752 { 753 for (int angle = 0; angle < 8; angle++) 754 { 755 double rad = angle * Math.PI / 4; 756 int nx = start.X + (int)(radius * Math.Cos(rad)); 757 int ny = start.Y + (int)(radius * Math.Sin(rad)); 758 759 if (nx < 2 || nx >= w - 2 || ny < 2 || ny >= h - 2) continue; 760 761 if (edgeMap[nx, ny] < 15) 762 { 763 int o = ny * stride + nx * 4; 764 int diff = Math.Abs(ptr[o + 2] - baseR) + Math.Abs(ptr[o + 1] - baseG) + Math.Abs(ptr[o] - baseB); 765 if (diff < 30) 766 return new Point(nx, ny); 767 } 768 } 769 } 770 return new Point(-1, -1); 771 } 772 773 unsafe int AnalyzeRegionThreshold(byte* ptr, int stride, int[,] edgeMap, int cx, int cy, int baseR, int baseG, int baseB) 774 { 775 var diffs = new List<int>(); 776 int sampleRadius = 12; 777 778 for (int dy = -sampleRadius; dy <= sampleRadius; dy += 2) 779 { 780 for (int dx = -sampleRadius; dx <= sampleRadius; dx += 2) 781 { 782 int px = cx + dx, py = cy + dy; 783 if (px < 1 || px >= w - 1 || py < 1 || py >= h - 1) continue; 784 if (edgeMap[px, py] > 25) continue; 785 786 int o = py * stride + px * 4; 787 int diff = Math.Abs(ptr[o + 2] - baseR) + Math.Abs(ptr[o + 1] - baseG) + Math.Abs(ptr[o] - baseB); 788 diffs.Add(diff); 789 } 790 } 791 792 if (diffs.Count < 5) return 18; 793 794 diffs.Sort(); 795 int p80 = diffs[(int)(diffs.Count * 0.80)]; 796 return Math.Max(12, Math.Min(p80 + 3, 40)); 797 } 798 799 unsafe bool IsSmartBoundary(byte* ptr, int stride, int[,] edgeMap, int x, int y, int baseR, int baseG, int baseB, int colorThr) 800 { 801 if (x < 1 || x >= w - 1 || y < 1 || y >= h - 1) return true; 802 803 int edge = edgeMap[x, y]; 804 805 if (edge > 30) return true; 806 807 int o = y * stride + x * 4; 808 int r = ptr[o + 2], g = ptr[o + 1], b = ptr[o]; 809 int colorDiff = Math.Abs(r - baseR) + Math.Abs(g - baseG) + Math.Abs(b - baseB); 810 811 if (edge > 18 && colorDiff > colorThr * 0.5) return true; 812 if (colorDiff > colorThr) return true; 813 814 if (IsThinLine(ptr, stride, x, y, baseR, baseG, baseB)) return true; 815 816 return false; 817 } 818 819 unsafe bool IsThinLine(byte* ptr, int stride, int x, int y, int baseR, int baseG, int baseB) 820 { 821 if (x < 1 || x >= w - 1 || y < 1 || y >= h - 1) return false; 822 823 int o = y * stride + x * 4; 824 int r = ptr[o + 2], g = ptr[o + 1], b = ptr[o]; 825 826 int selfDiff = Math.Abs(r - baseR) + Math.Abs(g - baseG) + Math.Abs(b - baseB); 827 if (selfDiff < 12) return false; 828 829 int oL = y * stride + (x - 1) * 4; 830 int oR = y * stride + (x + 1) * 4; 831 int oU = (y - 1) * stride + x * 4; 832 int oD = (y + 1) * stride + x * 4; 833 834 int diffL = Math.Abs(ptr[oL + 2] - r) + Math.Abs(ptr[oL + 1] - g) + Math.Abs(ptr[oL] - b); 835 int diffR = Math.Abs(ptr[oR + 2] - r) + Math.Abs(ptr[oR + 1] - g) + Math.Abs(ptr[oR] - b); 836 int diffU = Math.Abs(ptr[oU + 2] - r) + Math.Abs(ptr[oU + 1] - g) + Math.Abs(ptr[oU] - b); 837 int diffD = Math.Abs(ptr[oD + 2] - r) + Math.Abs(ptr[oD + 1] - g) + Math.Abs(ptr[oD] - b); 838 839 if (diffL < 12 && diffR < 12 && (diffU > 25 || diffD > 25)) return true; 840 if (diffU < 12 && diffD < 12 && (diffL > 25 || diffR > 25)) return true; 841 842 return false; 843 } 844 845 List<Point> BuildOrthogonalPolygon(int[] rowLeft, int[] rowRight, int minY, int maxY) 846 { 847 int height = maxY - minY; 848 int stripSize = Math.Max(2, height / 50); 849 850 var strips = new List<int[]>(); 851 852 for (int y = minY; y <= maxY; y += stripSize) 853 { 854 int yEnd = Math.Min(y + stripSize - 1, maxY); 855 int left = w, right = -1; 856 857 for (int yy = y; yy <= yEnd; yy++) 858 { 859 if (rowLeft[yy] < left) left = rowLeft[yy]; 860 if (rowRight[yy] > right) right = rowRight[yy]; 861 } 862 863 if (right >= left) 864 strips.Add(new int[] { y, left, right, yEnd }); 865 } 866 867 if (strips.Count == 0) return new List<Point>(); 868 869 var merged = new List<int[]>(); 870 merged.Add(strips[0]); 871 872 for (int i = 1; i < strips.Count; i++) 873 { 874 var prev = merged[merged.Count - 1]; 875 var curr = strips[i]; 876 877 if (Math.Abs(prev[1] - curr[1]) <= 2 && Math.Abs(prev[2] - curr[2]) <= 2) 878 { 879 prev[3] = curr[3]; 880 prev[1] = Math.Min(prev[1], curr[1]); 881 prev[2] = Math.Max(prev[2], curr[2]); 882 } 883 else 884 { 885 merged.Add(curr); 886 } 887 } 888 889 var result = new List<Point>(); 890 result.Add(new Point(merged[0][1], merged[0][0])); 891 892 for (int i = 1; i < merged.Count; i++) 893 { 894 int prevLeft = merged[i - 1][1]; 895 int currLeft = merged[i][1]; 896 int y = merged[i][0]; 897 898 if (currLeft != prevLeft) 899 { 900 result.Add(new Point(prevLeft, y)); 901 result.Add(new Point(currLeft, y)); 902 } 903 } 904 905 var last = merged[merged.Count - 1]; 906 result.Add(new Point(last[1], last[3])); 907 result.Add(new Point(last[2], last[3])); 908 909 for (int i = merged.Count - 2; i >= 0; i--) 910 { 911 int prevRight = merged[i + 1][2]; 912 int currRight = merged[i][2]; 913 int y = merged[i + 1][0]; 914 915 if (currRight != prevRight) 916 { 917 result.Add(new Point(prevRight, y)); 918 result.Add(new Point(currRight, y)); 919 } 920 } 921 922 result.Add(new Point(merged[0][2], merged[0][0])); 923 924 var clean = new List<Point>(); 925 for (int i = 0; i < result.Count; i++) 926 { 927 if (clean.Count == 0 || result[i] != clean[clean.Count - 1]) 928 clean.Add(result[i]); 929 } 930 931 var final = new List<Point>(); 932 for (int i = 0; i < clean.Count; i++) 933 { 934 var prev = clean[(i - 1 + clean.Count) % clean.Count]; 935 var curr = clean[i]; 936 var next = clean[(i + 1) % clean.Count]; 937 938 bool sameH = (prev.Y == curr.Y && curr.Y == next.Y); 939 bool sameV = (prev.X == curr.X && curr.X == next.X); 940 941 if (!sameH && !sameV) 942 final.Add(curr); 943 } 944 945 return final.Count >= 4 ? final : clean; 946 } 947 } 948}