205 lines
7.3 KiB
C#
205 lines
7.3 KiB
C#
namespace OcrDaemon;
|
|
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Runtime.InteropServices;
|
|
|
|
class EdgeCropHandler
|
|
{
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct POINT { public int X, Y; }
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool GetCursorPos(out POINT lpPoint);
|
|
|
|
public (Bitmap cropped, Bitmap fullCapture, RegionRect region)? EdgeCrop(Request req, EdgeCropParams p)
|
|
{
|
|
int cursorX, cursorY;
|
|
if (req.CursorX.HasValue && req.CursorY.HasValue)
|
|
{
|
|
cursorX = req.CursorX.Value;
|
|
cursorY = req.CursorY.Value;
|
|
}
|
|
else
|
|
{
|
|
GetCursorPos(out var pt);
|
|
cursorX = pt.X;
|
|
cursorY = pt.Y;
|
|
}
|
|
|
|
var fullCapture = ScreenCapture.CaptureOrLoad(req.File, null);
|
|
int w = fullCapture.Width;
|
|
int h = fullCapture.Height;
|
|
|
|
var bmpData = fullCapture.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
|
byte[] px = new byte[bmpData.Stride * h];
|
|
Marshal.Copy(bmpData.Scan0, px, 0, px.Length);
|
|
fullCapture.UnlockBits(bmpData);
|
|
int stride = bmpData.Stride;
|
|
|
|
int darkThresh = p.DarkThresh;
|
|
int colGap = p.RunGapTolerance;
|
|
int maxGap = p.MaxGap;
|
|
|
|
// ── Phase 1: Per-row horizontal extent ──
|
|
// Scan left/right from cursorX per row. Gap tolerance bridges through text.
|
|
// Percentile-based filtering for robustness.
|
|
int bandHalf = p.MinDarkRun; // repurpose: half-height of horizontal scan band
|
|
int bandTop = Math.Max(0, cursorY - bandHalf);
|
|
int bandBot = Math.Min(h - 1, cursorY + bandHalf);
|
|
|
|
var leftExtents = new List<int>();
|
|
var rightExtents = new List<int>();
|
|
|
|
for (int y = bandTop; y <= bandBot; y++)
|
|
{
|
|
int rowOff = y * stride;
|
|
int ci = rowOff + cursorX * 4;
|
|
int cBright = (px[ci] + px[ci + 1] + px[ci + 2]) / 3;
|
|
if (cBright >= darkThresh) continue;
|
|
|
|
int leftEdge = cursorX;
|
|
int gap = 0;
|
|
for (int x = cursorX - 1; x >= 0; x--)
|
|
{
|
|
int i = rowOff + x * 4;
|
|
int brightness = (px[i] + px[i + 1] + px[i + 2]) / 3;
|
|
if (brightness < darkThresh) { leftEdge = x; gap = 0; }
|
|
else if (++gap > colGap) break;
|
|
}
|
|
|
|
int rightEdge = cursorX;
|
|
gap = 0;
|
|
for (int x = cursorX + 1; x < w; x++)
|
|
{
|
|
int i = rowOff + x * 4;
|
|
int brightness = (px[i] + px[i + 1] + px[i + 2]) / 3;
|
|
if (brightness < darkThresh) { rightEdge = x; gap = 0; }
|
|
else if (++gap > colGap) break;
|
|
}
|
|
|
|
leftExtents.Add(leftEdge);
|
|
rightExtents.Add(rightEdge);
|
|
}
|
|
|
|
if (leftExtents.Count < 10)
|
|
{
|
|
Console.Error.WriteLine($" edge-crop: too few dark rows ({leftExtents.Count})");
|
|
fullCapture.Dispose();
|
|
return null;
|
|
}
|
|
|
|
leftExtents.Sort();
|
|
rightExtents.Sort();
|
|
|
|
// Use RowThreshDiv/ColThreshDiv as percentile denominators
|
|
// e.g., RowThreshDiv=4 → 25th percentile for left, ColThreshDiv=4 → 75th for right
|
|
int leftPctIdx = leftExtents.Count / p.RowThreshDiv;
|
|
int rightPctIdx = rightExtents.Count * (p.ColThreshDiv - 1) / p.ColThreshDiv;
|
|
leftPctIdx = Math.Clamp(leftPctIdx, 0, leftExtents.Count - 1);
|
|
rightPctIdx = Math.Clamp(rightPctIdx, 0, rightExtents.Count - 1);
|
|
|
|
int bestColStart = leftExtents[leftPctIdx];
|
|
int bestColEnd = rightExtents[rightPctIdx];
|
|
|
|
Console.Error.WriteLine($" edge-crop: horizontal: left={bestColStart} right={bestColEnd} ({bestColEnd - bestColStart + 1}px) samples={leftExtents.Count} pctL={leftPctIdx}/{leftExtents.Count} pctR={rightPctIdx}/{rightExtents.Count}");
|
|
|
|
if (bestColEnd - bestColStart + 1 < 50)
|
|
{
|
|
Console.Error.WriteLine($" edge-crop: horizontal extent too small");
|
|
fullCapture.Dispose();
|
|
return null;
|
|
}
|
|
|
|
// ── Phase 2: Per-column vertical extent ──
|
|
int colBandHalf = (bestColEnd - bestColStart + 1) / 3;
|
|
int colBandLeft = Math.Max(bestColStart, cursorX - colBandHalf);
|
|
int colBandRight = Math.Min(bestColEnd, cursorX + colBandHalf);
|
|
|
|
var topExtents = new List<int>();
|
|
var bottomExtents = new List<int>();
|
|
|
|
// Asymmetric gap: larger upward to bridge header decorations (~30-40px bright)
|
|
int maxGapUp = maxGap * 3;
|
|
|
|
for (int x = colBandLeft; x <= colBandRight; x++)
|
|
{
|
|
int ci = cursorY * stride + x * 4;
|
|
int cBright = (px[ci] + px[ci + 1] + px[ci + 2]) / 3;
|
|
if (cBright >= darkThresh) continue;
|
|
|
|
int topEdge = cursorY;
|
|
int gap = 0;
|
|
for (int y = cursorY - 1; y >= 0; y--)
|
|
{
|
|
int i = y * stride + x * 4;
|
|
int brightness = (px[i] + px[i + 1] + px[i + 2]) / 3;
|
|
if (brightness < darkThresh) { topEdge = y; gap = 0; }
|
|
else if (++gap > maxGapUp) break;
|
|
}
|
|
|
|
int bottomEdge = cursorY;
|
|
gap = 0;
|
|
for (int y = cursorY + 1; y < h; y++)
|
|
{
|
|
int i = y * stride + x * 4;
|
|
int brightness = (px[i] + px[i + 1] + px[i + 2]) / 3;
|
|
if (brightness < darkThresh) { bottomEdge = y; gap = 0; }
|
|
else if (++gap > maxGap) break;
|
|
}
|
|
|
|
topExtents.Add(topEdge);
|
|
bottomExtents.Add(bottomEdge);
|
|
}
|
|
|
|
if (topExtents.Count < 10)
|
|
{
|
|
Console.Error.WriteLine($" edge-crop: too few dark columns ({topExtents.Count})");
|
|
fullCapture.Dispose();
|
|
return null;
|
|
}
|
|
|
|
topExtents.Sort();
|
|
bottomExtents.Sort();
|
|
|
|
int topPctIdx = topExtents.Count / p.RowThreshDiv;
|
|
int botPctIdx = topExtents.Count * (p.ColThreshDiv - 1) / p.ColThreshDiv;
|
|
topPctIdx = Math.Clamp(topPctIdx, 0, topExtents.Count - 1);
|
|
botPctIdx = Math.Clamp(botPctIdx, 0, bottomExtents.Count - 1);
|
|
|
|
int bestRowStart = topExtents[topPctIdx];
|
|
int bestRowEnd = bottomExtents[botPctIdx];
|
|
|
|
Console.Error.WriteLine($" edge-crop: vertical: top={bestRowStart} bottom={bestRowEnd} ({bestRowEnd - bestRowStart + 1}px) samples={topExtents.Count}");
|
|
|
|
if (bestRowEnd - bestRowStart + 1 < 50)
|
|
{
|
|
Console.Error.WriteLine($" edge-crop: vertical extent too small");
|
|
fullCapture.Dispose();
|
|
return null;
|
|
}
|
|
|
|
int minX = bestColStart;
|
|
int minY = bestRowStart;
|
|
int maxX = bestColEnd;
|
|
int maxY = bestRowEnd;
|
|
|
|
int rw = maxX - minX + 1;
|
|
int rh = maxY - minY + 1;
|
|
|
|
Console.Error.WriteLine($" edge-crop: result ({minX},{minY}) {rw}x{rh}");
|
|
|
|
if (rw < 50 || rh < 50)
|
|
{
|
|
Console.Error.WriteLine($" edge-crop: region too small ({rw}x{rh})");
|
|
fullCapture.Dispose();
|
|
return null;
|
|
}
|
|
|
|
var cropRect = new Rectangle(minX, minY, rw, rh);
|
|
var cropped = fullCapture.Clone(cropRect, PixelFormat.Format32bppArgb);
|
|
var region = new RegionRect { X = minX, Y = minY, Width = rw, Height = rh };
|
|
|
|
return (cropped, fullCapture, region);
|
|
}
|
|
}
|