windowcapture
исходный код / Detection/Detector.cs

Detector.cs

948 строк · 34,762 байт · модуль Detection
  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}