157 lines
5.4 KiB
C#
157 lines
5.4 KiB
C#
namespace Poe2Trade.Screen;
|
|
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Runtime.InteropServices;
|
|
using Serilog;
|
|
using Region = Poe2Trade.Core.Region;
|
|
|
|
class DetectGridHandler
|
|
{
|
|
public DetectGridResult Detect(Region region, int minCellSize = 20, int maxCellSize = 70,
|
|
string? file = null, bool debug = false)
|
|
{
|
|
Bitmap bitmap = ScreenCapture.CaptureOrLoad(file, region);
|
|
int w = bitmap.Width;
|
|
int h = bitmap.Height;
|
|
|
|
var bmpData = bitmap.LockBits(
|
|
new Rectangle(0, 0, w, h),
|
|
ImageLockMode.ReadOnly,
|
|
PixelFormat.Format32bppArgb
|
|
);
|
|
byte[] pixels = new byte[bmpData.Stride * h];
|
|
Marshal.Copy(bmpData.Scan0, pixels, 0, pixels.Length);
|
|
bitmap.UnlockBits(bmpData);
|
|
int stride = bmpData.Stride;
|
|
|
|
byte[] gray = new byte[w * h];
|
|
for (int y = 0; y < h; y++)
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
int i = y * stride + x * 4;
|
|
gray[y * w + x] = (byte)((pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3);
|
|
}
|
|
|
|
bitmap.Dispose();
|
|
|
|
int bandH = 200;
|
|
int bandStep = 40;
|
|
const int veryDarkPixelThresh = 12;
|
|
const double gridSegThresh = 0.25;
|
|
|
|
var candidates = new List<(int bandY, int cellW, double hAc, int hLeft, int hRight)>();
|
|
|
|
for (int by = 0; by + bandH <= h; by += bandStep)
|
|
{
|
|
double[] darkDensity = new double[w];
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
int count = 0;
|
|
for (int y = by; y < by + bandH; y++)
|
|
{
|
|
if (gray[y * w + x] < veryDarkPixelThresh) count++;
|
|
}
|
|
darkDensity[x] = (double)count / bandH;
|
|
}
|
|
|
|
var gridSegs = SignalProcessing.FindDarkDensitySegments(darkDensity, gridSegThresh, 200);
|
|
|
|
foreach (var (segLeft, segRight) in gridSegs)
|
|
{
|
|
int segLen = segRight - segLeft;
|
|
double[] segment = new double[segLen];
|
|
Array.Copy(darkDensity, segLeft, segment, 0, segLen);
|
|
|
|
var (period, acScore) = SignalProcessing.FindPeriodWithScore(segment, minCellSize, maxCellSize);
|
|
if (period <= 0) continue;
|
|
|
|
var (extLeft, extRight) = SignalProcessing.FindGridExtent(segment, period);
|
|
if (extLeft < 0) continue;
|
|
|
|
int absLeft = segLeft + extLeft;
|
|
int absRight = segLeft + extRight;
|
|
int extent = absRight - absLeft;
|
|
|
|
if (extent < period * 8 || extent < 200) continue;
|
|
|
|
candidates.Add((by, period, acScore, absLeft, absRight));
|
|
}
|
|
}
|
|
|
|
candidates.Sort((a, b) =>
|
|
{
|
|
double sa = a.hAc * (a.hRight - a.hLeft);
|
|
double sb = b.hAc * (b.hRight - b.hLeft);
|
|
return sb.CompareTo(sa);
|
|
});
|
|
|
|
// Pass 2: Verify vertical periodicity
|
|
foreach (var cand in candidates.Take(10))
|
|
{
|
|
int colSpan = cand.hRight - cand.hLeft;
|
|
if (colSpan < cand.cellW * 3) continue;
|
|
|
|
double[] rowDensity = new double[h];
|
|
for (int y = 0; y < h; y++)
|
|
{
|
|
int count = 0;
|
|
for (int x = cand.hLeft; x < cand.hRight; x++)
|
|
{
|
|
if (gray[y * w + x] < veryDarkPixelThresh) count++;
|
|
}
|
|
rowDensity[y] = (double)count / colSpan;
|
|
}
|
|
|
|
var vGridSegs = SignalProcessing.FindDarkDensitySegments(rowDensity, gridSegThresh, 100);
|
|
if (vGridSegs.Count == 0) continue;
|
|
|
|
var (vSegTop, vSegBottom) = vGridSegs.OrderByDescending(s => s.end - s.start).First();
|
|
int vSegLen = vSegBottom - vSegTop;
|
|
double[] vSegment = new double[vSegLen];
|
|
Array.Copy(rowDensity, vSegTop, vSegment, 0, vSegLen);
|
|
|
|
var (cellH, vAc) = SignalProcessing.FindPeriodWithScore(vSegment, minCellSize, maxCellSize);
|
|
if (cellH <= 0) continue;
|
|
|
|
var (extTop, extBottom) = SignalProcessing.FindGridExtent(vSegment, cellH);
|
|
if (extTop < 0) continue;
|
|
|
|
int top = vSegTop + extTop;
|
|
int bottom = vSegTop + extBottom;
|
|
int vExtent = bottom - top;
|
|
|
|
if (vExtent < cellH * 3 || vExtent < 100) continue;
|
|
|
|
int gridW = cand.hRight - cand.hLeft;
|
|
int gridH = bottom - top;
|
|
int cols = Math.Max(2, (int)Math.Round((double)gridW / cand.cellW));
|
|
int rows = Math.Max(2, (int)Math.Round((double)gridH / cellH));
|
|
|
|
gridW = cols * cand.cellW;
|
|
gridH = rows * cellH;
|
|
|
|
return new DetectGridResult
|
|
{
|
|
Detected = true,
|
|
Region = new Region(region.X + cand.hLeft, region.Y + top, gridW, gridH),
|
|
Cols = cols,
|
|
Rows = rows,
|
|
CellWidth = Math.Round((double)gridW / cols, 1),
|
|
CellHeight = Math.Round((double)gridH / rows, 1),
|
|
};
|
|
}
|
|
|
|
return new DetectGridResult { Detected = false };
|
|
}
|
|
}
|
|
|
|
public class DetectGridResult
|
|
{
|
|
public bool Detected { get; set; }
|
|
public Region? Region { get; set; }
|
|
public int Cols { get; set; }
|
|
public int Rows { get; set; }
|
|
public double CellWidth { get; set; }
|
|
public double CellHeight { get; set; }
|
|
}
|