4
\$\begingroup\$

After referencing http://csharpexamples.com/tag/unsafe-bitmap-access/

I tried to put together one for 1 bit per pixel bitmaps.

If you can review this, it would help me greatly in verifying this is the correct way to do it.

This takes a Format1bppIndexed bitmap and locks it in memory, and directly parses the memory bits to form the bits into a boolean array.

using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace AutoBot
{
    public class BitData
    {
        private Rectangle DataRect { get; }
        private string DataPath { get; }
        private Stream DataStream { get; }
        private Image DataImage { get; }
        private Bitmap DataMap { get; }

        public IEnumerable<bool[]> GetBitDataInMemory()
        {
            if (DataMap == default && DataRect == default && string.IsNullOrEmpty(DataPath) && DataStream == default && DataImage == default)
            {
                return default;
            }

            Bitmap TargetImage;
            if (DataMap != default)
            {
                TargetImage = DataMap;
            }
            else if (DataRect != default)
            {
                TargetImage = NativeMethods.GetBlackWhiteAt(DataRect.Location, DataRect.Size);
            }
            else if (!string.IsNullOrEmpty(DataPath))
            {
                if (File.Exists(DataPath))
                {
                    using (var Image = new Bitmap(DataPath))
                        TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
                }
                else
                {
                    return default;
                }
            }
            else if (DataStream != default)
            {
                using (var Image = new Bitmap(DataStream))
                    TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
            }
            else
            {
                using (var Image = new Bitmap(DataImage))
                    TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
            }

            var Array = InMemGetBooleanArray(TargetImage);
            TargetImage.Dispose();

            return Array;
        }

        public IEnumerable<bool[]> GetBitData()
        {
            if (DataMap == default && DataRect == default && string.IsNullOrEmpty(DataPath) && DataStream == default && DataImage == default)
            {
                return default;
            }

            Bitmap TargetImage;
            if (DataMap != default)
            {
                TargetImage = DataMap;
            }
            else if (DataRect != default)
            {
                TargetImage = NativeMethods.GetBlackWhiteAt(DataRect.Location, DataRect.Size);
            }
            else if (!string.IsNullOrEmpty(DataPath))
            {
                if (File.Exists(DataPath))
                {
                    using (var Image = new Bitmap(DataPath))
                        TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
                }
                else
                {
                    return default;
                }
            }
            else if (DataStream != default)
            {
                using (var Image = new Bitmap(DataStream))
                    TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
            }
            else
            {
                using (var Image = new Bitmap(DataImage))
                    TargetImage = Image.Clone(new Rectangle(new Point(0, 0), new Size(Image.Width, Image.Height)), PixelFormat.Format1bppIndexed);
            }

            var Array = GetBooleanArray(TargetImage);
            TargetImage.Dispose();

            return Array;
        }

        public BitData(Rectangle dataRect = default, string dataPath = default, Stream dataStream = default, Image dataImage = default, Bitmap dataMap = default)
        {
            DataRect = dataRect;
            DataPath = dataPath;
            DataStream = dataStream;
            DataImage = dataImage;
            DataMap = dataMap;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static IEnumerable<bool[]> GetBooleanArray(Bitmap bitmap)
        {
            BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
            bool[][] ba2 = new bool[bitmap.Height][];
            for (int y = 0; y <= bitmap.Height - 1; y++)
            {
                ba2[y] = new bool[bitmap.Width];
                for (int x = 0; x <= bitmap.Width - 1; x++)
                {
                    if (GetIndexedPixel(x, y, data) > 0)
                    {
                        ba2[y][x] = true;
                    }
                }
            }

            bitmap.UnlockBits(data);
            return ba2;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static IEnumerable<bool[]> InMemGetBooleanArray(Bitmap bitmap)
        {
            BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
            bool[][] ba2 = new bool[bitmap.Height][];

            unsafe
            {
                int heightInPixels = data.Height;
                int widthInBits = data.Width-1;
                byte* PtrFirstPixel = (byte*)data.Scan0;

                Parallel.For(0, heightInPixels, y =>
                {
                    ba2[y] = new bool[data.Width];
                    for (int x = 0; x <= widthInBits; x++)
                    {
                        int Index = (y * data.Stride) + (x >> 3);
                        byte ret = *(PtrFirstPixel + Index);
                        ret &= (byte)(0x80 >> (x & 0x7));
                        if (ret > 0)
                        {
                            ba2[y][x] = true;
                        }
                    }
                });
            }
            bitmap.UnlockBits(data);
            return ba2;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int GetIndexedPixel(int x, int y, BitmapData data)
        {
            var index = (y * data.Stride) + (x >> 3);
            var mask = (byte)(0x80 >> (x & 0x7));
            byte ret = Marshal.ReadByte(data.Scan0, index);
            ret &= mask;
            return ret;
        }
    }
}

Search Routine

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AutoBot
{
    public static class ActiveScreenMatch
    {
        public static Point ScreenMatchInMemory(BitData Target = default)
        {
            if (Target == default)
            {
                return default;
            }

            var TargetArea = Target.GetBitDataInMemory();

            int SkippedBlackLines = 0;
            foreach (var bl1 in TargetArea)
            {
                if (bl1.Any(x => x))
                {
                    break;
                }
                else
                {
                    SkippedBlackLines++;
                }
            }

            TargetArea = TargetArea.Skip(SkippedBlackLines).ToArray();

            Bitmap SourceImage = NativeMethods.GetBlackWhiteAt(new Point(0, 0), new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height));

            BitData SourceData = new BitData(dataMap: SourceImage);

            var SourceArea = SourceData.GetBitDataInMemory();

            SourceImage.Dispose();

            var m = TargetArea.Count() - 1;

            Point p = default;

            WaitHandle[] waitHandles = new WaitHandle[]
            {
                new AutoResetEvent(false),
                new AutoResetEvent(false)
            };

            void ThreadForward(object State)
            {
                AutoResetEvent Complete = (AutoResetEvent)State;
                _ = Parallel.ForEach(Enumerable.Range(0, SourceArea.Count() - 1), (line, loopState) =>
                {
                    var Index = SubListIndex(SourceArea.ElementAt(line), 0, TargetArea.ElementAt(0));
                    if (Index != -1 && Index != 0 && line > m && (line + m) < Screen.PrimaryScreen.Bounds.Height)
                    {
                        var SourceLast = SourceArea.ElementAt(line + m).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m).ToArray());
                        var SourceMid = SourceArea.ElementAt(line + (m / 2)).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m / 2).ToArray());
                        if (SourceLast && SourceMid)
                        {
                            p = new Point(Index + (TargetArea.ElementAt(0).Length / 2), line + (TargetArea.ElementAt(0).Length / 2));
                            loopState.Break();
                        }
                    }
                });
                Complete.Set();
            }

            void ThreadBackward(object State)
            {
                AutoResetEvent Complete = (AutoResetEvent)State;
                _ = Parallel.ForEach(Enumerable.Range(0, SourceArea.Count() - 1).Reverse(), (line, loopState) =>
                {
                    var Index = SubListIndex(SourceArea.ElementAt(line), 0, TargetArea.ElementAt(0));
                    if (Index != -1 && Index != 0 && line > m && (line + m) < Screen.PrimaryScreen.Bounds.Height)
                    {
                        var SourceLast = SourceArea.ElementAt(line + m).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m).ToArray());
                        var SourceMid = SourceArea.ElementAt(line + (m / 2)).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m / 2).ToArray());
                        if (SourceLast && SourceMid)
                        {
                            p = new Point(Index + (TargetArea.ElementAt(0).Length / 2), line + (TargetArea.ElementAt(0).Length / 2));
                            loopState.Break();
                        }
                    }
                });
                Complete.Set();
            }

            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadForward), waitHandles[0]);
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadBackward), waitHandles[1]);
            WaitHandle.WaitAny(waitHandles);
            return p;
        }
        public static Point ScreenMatch(BitData Target = default)
        {
            if (Target == default)
            {
                return default;
            }

            var TargetArea = Target.GetBitData();

            int SkippedBlackLines = 0;
            foreach (var bl1 in TargetArea)
            {
                if (bl1.Any(x => x))
                {
                    break;
                }
                else
                {
                    SkippedBlackLines++;
                }
            }

            TargetArea = TargetArea.Skip(SkippedBlackLines).ToArray();

            Bitmap SourceImage = NativeMethods.GetBlackWhiteAt(new Point(0, 0), new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height));

            BitData SourceData = new BitData(dataMap: SourceImage);

            var SourceArea = SourceData.GetBitData();

            SourceImage.Dispose();

            var m = TargetArea.Count() - 1;

            Point p = default;

            WaitHandle[] waitHandles = new WaitHandle[]
            {
                new AutoResetEvent(false),
                new AutoResetEvent(false)
            };

            void ThreadForward(object State)
            {
                AutoResetEvent Complete = (AutoResetEvent)State;
                _ = Parallel.ForEach(Enumerable.Range(0, SourceArea.Count() - 1), (line, loopState) =>
                {
                    var Index = SubListIndex(SourceArea.ElementAt(line), 0, TargetArea.ElementAt(0));
                    if (Index != -1 && Index != 0 && line > m && (line + m) < Screen.PrimaryScreen.Bounds.Height)
                    {
                        var SourceLast = SourceArea.ElementAt(line + m).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m).ToArray());
                        var SourceMid = SourceArea.ElementAt(line + (m / 2)).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m / 2).ToArray());
                        if (SourceLast && SourceMid)
                        {
                            p = new Point(Index + (TargetArea.ElementAt(0).Length / 2), line + (TargetArea.ElementAt(0).Length / 2));
                            loopState.Break();
                        }
                    }
                });
                Complete.Set();
            }

            void ThreadBackward(object State)
            {
                AutoResetEvent Complete = (AutoResetEvent)State;
                _ = Parallel.ForEach(Enumerable.Range(0, SourceArea.Count() - 1).Reverse(), (line, loopState) =>
                {
                    var Index = SubListIndex(SourceArea.ElementAt(line), 0, TargetArea.ElementAt(0));
                    if (Index != -1 && Index != 0 && line > m && (line + m) < Screen.PrimaryScreen.Bounds.Height)
                    {
                        var SourceLast = SourceArea.ElementAt(line + m).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m).ToArray());
                        var SourceMid = SourceArea.ElementAt(line + (m / 2)).Skip(Index).Take(TargetArea.ElementAt(0).Length).SequenceEqual(TargetArea.ElementAt(m / 2).ToArray());
                        if (SourceLast && SourceMid)
                        {
                            p = new Point(Index + (TargetArea.ElementAt(0).Length / 2), line + (TargetArea.ElementAt(0).Length / 2));
                            loopState.Break();
                        }
                    }
                });
                Complete.Set();
            }

            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadForward), waitHandles[0]);
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadBackward), waitHandles[1]);
            WaitHandle.WaitAny(waitHandles);
            return p;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int SubListIndex(IEnumerable<bool> list, int start, IEnumerable<bool> sublist)
        {
            for (int listIndex = start; listIndex < list.Count() - sublist.Count() + 1; listIndex++)
            {
                int count = 0;
                while (count < sublist.Count() && sublist.ElementAt(count).Equals(list.ElementAt(listIndex + count)))
                    count++;
                if (count == sublist.Count())
                    return listIndex;
            }
            return -1;
        }
    }
}

Example usage

I targeted my clock in the system tray.

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        var x1 = new BitData(new Rectangle(1805, 1043, 16, 16));
        var p1 = ActiveScreenMatch.ScreenMatchInMemory(x1);
        stopwatch.Stop();            
        Console.WriteLine(stopwatch.Elapsed);
        stopwatch.Reset();
        stopwatch.Start();
        var x2 = new BitData(new Rectangle(1805, 1043, 16, 16));
        Point p2 = ActiveScreenMatch.ScreenMatch(x2);
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);

        Console.WriteLine(p1.X + " " + p1.Y);
        Console.WriteLine(p2.X + " " + p2.Y);
\$\endgroup\$
  • 2
    \$\begingroup\$ While trying your code it's not quite clear, what you're trying to achieve. I assume, that you are trying to convert a monochrome bitmap into a matrix of boolean values. If that's the case it's not working as intended (review the for-loop). If I'm wrong please explain in more details what the outcome should be - e.g. provide a small example \$\endgroup\$ – Henrik Hansen Feb 9 at 9:40
  • \$\begingroup\$ Thank you for pointing out that bug with for loop, that was not intended. :) \$\endgroup\$ – BanMe Feb 9 at 11:01
  • 1
    \$\begingroup\$ I'm sorry to say, but still can't make it work. Could you include the image, you're using as test case? \$\endgroup\$ – Henrik Hansen Feb 9 at 13:14
  • 1
    \$\begingroup\$ It was not a useful edition you made of the for-loop, so take a look again (you'll have to consider the repetitive calls to BitTest(x, y)) . And you also have to consider this: int widthInBytes = data.Width / PixelsPerByte; for images with a width < 8 pixels. It's not a bad post, and I look forward to see it updated with working code, before I vote for close :-) \$\endgroup\$ – Henrik Hansen Feb 9 at 17:44
  • \$\begingroup\$ Sorry, Henrik, was up really late and this idea came to me while reading that article. After debugging and verifying equality of output this could be correct. \$\endgroup\$ – BanMe Feb 9 at 17:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.