I Rewrote all code, aimed at a variable reduction, and reduced complexity. removed all extraneous code unnecessary to the operation, reduced indexing surfaces, tests all work consistently. Added midpoint matching to search, combined Reader and Searching into a class.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace ActiveScreenMatch
{
public unsafe class ThisScreenSearch
{
internal Bitmap BitmapToMap { get; }
internal BitmapData BitmapToMapData { get; }
private BiDirectionalReader DesktopReader { get; }
/// <summary>
/// Class reads bitmap and stores combined index of top,bot to midpoint and cache the resulting binary lines as Memory<bool>
/// </summary>
internal class BiDirectionalReader
{
internal int SetWidth { get; }
internal int SetHeight { get; }
private byte* Bits { get; }
private int Stride { get; }
internal List<(int TopIndex, int BottomIndex)> BiDirectionalIndex { get; }
internal SortedList<int, Memory<bool>> CachedItems { get; }
public BiDirectionalReader(byte* mapPointer, int mapWidth, int mapHeight, int mapStride)
{
Bits = mapPointer;
SetWidth = mapWidth;
SetHeight = mapHeight;
Stride = mapStride;
CachedItems = new SortedList<int, Memory<bool>>(SetHeight);
BiDirectionalIndex = new List<(int, int)>();
List<byte> Masks = new List<byte>();
// PreProcess mask for line
for (int x = 0; x < SetWidth - 1; x++)
{
Masks.Add((byte)(0x80 >> (x & 0x7)));
}
// iterate forward and back over bitmap to create index and parse bytes to bools
int top = -1, bot = SetHeight;
while (++top <= (SetHeight - 1) / 2 && --bot >= (SetHeight - 1) / 2)
{
// declare array to hold results
var topLine = new bool[SetWidth];
var botLine = new bool[SetWidth];
// iterate over the width of the bitmap on both lines
for (int x = 0; x < SetWidth - 1; x++)
{
var mask = Masks[x];
byte tret = *(Bits + (top * Stride) + (x >> 3));
byte bret = *(Bits + (bot * Stride) + (x >> 3));
tret &= mask;
bret &= mask;
topLine[x] = tret > 0;
botLine[x] = bret > 0;
}
// store index
BiDirectionalIndex.Add((top, bot));
// store lines parse in cache
CachedItems.Add(top, new Memory<bool>(topLine));
CachedItems.Add(bot, new Memory<bool>(botLine));
}
}
}
public Point CompressSearch(Bitmap target)
{
// parse the target bitmap using out reader
var TargetData = target.LockBits(new Rectangle(0, 0, target.Width, target.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
var TScan0 = TargetData.Scan0;
var TargetReader = new BiDirectionalReader((byte*)TScan0, TargetData.Width, TargetData.Height, TargetData.Stride);
// clean up.
target.UnlockBits(TargetData);
int FoundTop = -1;
int FoundBottom = -1;
// Parse the Index to check if top and bottom line contain set bits
// probably not the most optimal solution, but it doesn't use linq
foreach ((int TopIndex, int BottomIndex) in TargetReader.BiDirectionalIndex)
{
(Memory<bool> TopLine, Memory<bool> BottomLine) = (TargetReader.CachedItems[TopIndex], TargetReader.CachedItems[BottomIndex]);
if (FoundTop == -1 && TopLine.Span.IndexOf(true) > -1)
{
FoundTop = TopIndex;
}
if (FoundBottom == -1 && BottomLine.Span.IndexOf(true) > -1)
{
FoundBottom = BottomIndex;
}
if (FoundTop > -1 && FoundBottom > -1)
{
break;
}
}
// if top and bottom are not -1 start searching.
if (FoundTop > -1 && FoundBottom > -1)
{
// get target lines by the index found above
var TargetTop = TargetReader.CachedItems[FoundTop];
var TargetBottom = TargetReader.CachedItems[FoundBottom];
// calculate the offset between top and bottom
var Offset = FoundBottom - FoundTop;
// if offset > 1 locate use the offset /2 as midpoint else return default line(all false)
Memory<bool> TargetMid = Offset > 1 ? TargetReader.CachedItems[FoundTop + (Offset / 2)] : new Memory<bool>(new bool[TargetReader.SetWidth]);
// Iterate over the provided index for the searched bitmap
foreach ((int Top, int Bot) in DesktopReader.BiDirectionalIndex)
{
// check index
// if index > - 1 slice to point and check for match on mid and bottom
var Index = DesktopReader.CachedItems[Top].Span.IndexOf(TargetBottom.Span);
if (Index > -1
&& DesktopReader.CachedItems[Top - (Offset / 2)].Span.Slice(Index).IndexOf(TargetMid.Span) == 0
&& DesktopReader.CachedItems[Top - Offset].Span.Slice(Index).IndexOf(TargetTop.Span) == 0)
{
return new Point(Index + (TargetReader.SetWidth / 2), Top - (TargetReader.SetHeight / 2));
}
Index = DesktopReader.CachedItems[Bot].Span.IndexOf(TargetTop.Span);
if (Index > -1
&& DesktopReader.CachedItems[Bot + (Offset / 2)].Span.Slice(Index).IndexOf(TargetMid.Span) == 0
&& DesktopReader.CachedItems[Bot + Offset].Span.Slice(Index).IndexOf(TargetBottom.Span) == 0)
{
return new Point(Index + (TargetReader.SetWidth / 2), Bot + (TargetReader.SetHeight / 2));
}
}
}
return default;
}
public ThisScreenSearch(Bitmap desktop)
{
BitmapToMap = desktop ?? throw new ArgumentNullException(nameof(desktop));
BitmapToMapData = desktop.LockBits(new Rectangle(0, 0, desktop.Width, desktop.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
var DScan0 = BitmapToMapData.Scan0;
DesktopReader = new BiDirectionalReader((byte*)DScan0, BitmapToMapData.Width, BitmapToMapData.Height, BitmapToMapData.Stride);
BitmapToMap.UnlockBits(BitmapToMapData);
}
}
}
How it can be used.
var p = new ThisScreenSearch(NativeMethods.PrintWindow())
.CompressSearch(NativeMethods.GetBlackWhiteAt(new Point(1815,1044), new Size(16,16)));
It probably won't handle odd-sized bitmaps. But I am happy with the success so far.
Thank you for reading this and bearing with me as I sorted it out.