using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BetterGenshinImpact.GameTask.Model.GameUI
{
///
/// 具有行号列号的单元格
/// ColNum和RowNum也是0-based的
/// 不仅方便编程,ClusterToCells方法也需要一个引用类型
///
///
public class GridCell(Rect rect)
{
public Rect Rect = rect;
public int ColNum;
public int RowNum;
///
/// 表示该单元格并非CV方法识别得到,而是通过算法推测出的
///
public bool IsPhantom;
///
/// 把检出的矩形聚簇成类似Excel的单元格集合
///
///
///
///
public static IEnumerable ClusterToCells(IEnumerable rects, int threshold)
{
return ClusterToCells(rects.Select(r => Tuple.Create(0, r)), threshold).Select(t => t.Item2).ToArray();
}
///
/// 把检出的矩形聚簇成类似Excel的单元格集合
///
///
///
///
public static IEnumerable> ClusterToCells(IEnumerable> rects, int threshold)
{
if (!rects.Any())
{
return [];
}
var result = rects.Select(r => new Tuple(r.Item1, new GridCell(r.Item2)));
result = result.ToArray(); // 必需,不然引用会丢掉。。
var orderByX = result.OrderBy(t => t.Item2.Rect.Left).ToArray();
int col = 0;
int? lastX = null;
int avgWidth = (int)rects.Average(r => r.Item2.Width);
for (int i = 0; i < orderByX.Length; i++)
{
if (lastX != null && orderByX[i].Item2.Rect.X - lastX > threshold)
{
col += (int)Math.Round((float)(orderByX[i].Item2.Rect.X - lastX.Value) / (avgWidth + threshold));
}
orderByX[i].Item2.ColNum = col;
lastX = orderByX[i].Item2.Rect.X;
}
var orderByY = result.OrderBy(t => t.Item2.Rect.Top).ToArray();
int row = 0;
int? lastY = null;
int avgHeight = (int)rects.Average(r => r.Item2.Height);
for (int i = 0; i < orderByY.Length; i++)
{
if (lastY != null && orderByY[i].Item2.Rect.Y - lastY > threshold)
{
row += (int)Math.Round((float)(orderByY[i].Item2.Rect.Y - lastY.Value) / (avgHeight + threshold)); // 估算隔了多少行
}
orderByY[i].Item2.RowNum = row;
lastY = orderByY[i].Item2.Rect.Y;
}
return result;
}
///
/// 遍历方阵,补上缺的Cell
///
///
public static void FillMissingGridCells(ref List cells)
{
if (cells.Count <= 0)
{
return;
}
double avgWidth = cells.Average(c => c.Rect.Width);
double avgHeight = cells.Average(c => c.Rect.Height);
double avgColSpacing;
double avgRowSpace;
{
int count = 0;
int sum = 0;
foreach (var row in cells.GroupBy(t => t.RowNum))
{
for (int i = 0; i < row.Max(r => r.ColNum); i++)
{
var x1 = row.SingleOrDefault(r => r.ColNum == i);
var x2 = row.SingleOrDefault(r => r.ColNum == i + 1);
if (x1 == null || x2 == null)
{
continue;
}
sum += x2.Rect.X - x1.Rect.X - x1.Rect.Width;
count++;
}
}
avgColSpacing = count == 0 ? 0 : ((double)sum) / count;
}
{
int count = 0;
int sum = 0;
foreach (var col in cells.GroupBy(t => t.ColNum))
{
for (int i = 0; i < col.Max(r => r.RowNum); i++)
{
var y1 = col.SingleOrDefault(r => r.RowNum == i);
var y2 = col.SingleOrDefault(r => r.RowNum == i + 1);
if (y1 == null || y2 == null)
{
continue;
}
sum += y2.Rect.Y - y1.Rect.Y - y1.Rect.Height;
count++;
}
}
avgRowSpace = count == 0 ? 0 : ((double)sum) / count;
}
double avgLeft = cells.Average(c => c.Rect.X - (avgWidth + avgColSpacing) * c.ColNum);
double avgTop = cells.Average(c => c.Rect.Y - (avgHeight + avgRowSpace) * c.RowNum);
for (int i = 0; i < cells.Max(r => r.ColNum) + 1; i++)
{
for (int j = 0; j < cells.Max(r => r.RowNum) + 1; j++)
{
if (cells.Any(c => c.ColNum == i && c.RowNum == j))
{
continue;
}
int x = (int)Math.Round(avgLeft + (avgWidth + avgColSpacing) * i, MidpointRounding.AwayFromZero);
int y = (int)Math.Round(avgTop + (avgHeight + avgRowSpace) * j, MidpointRounding.AwayFromZero);
int width = (int)Math.Round(avgWidth, MidpointRounding.AwayFromZero);
int height = (int)Math.Round(avgHeight, MidpointRounding.AwayFromZero);
GridCell cell = new GridCell(new Rect(x, y, width, height))
{
ColNum = i,
RowNum = j,
IsPhantom = true
};
cells.Add(cell);
}
}
}
}
}