public static void AreEqual(Bitmap expected, Bitmap actual, Action <Bitmap> onFail)
        {
            if (expected.Size != actual.Size)
            {
                if (Debugger.IsAttached)
                {
                    ImageDiffWindow.Show(expected, actual);
                }

                onFail(actual);
                throw AssertException.Create(
                          "Sizes did not match\r\n" +
                          $"Expected: {expected.Size}\r\n" +
                          $"Actual:   {actual.Size}");
            }

            for (var x = 0; x < expected.Size.Width; x++)
            {
                for (var y = 0; y < expected.Size.Height; y++)
                {
                    if (expected.GetPixel(x, y).Name == actual.GetPixel(x, y).Name)
                    {
                        continue;
                    }

                    if (Debugger.IsAttached)
                    {
                        ImageDiffWindow.Show(expected, actual);
                    }

                    onFail(actual);
                    throw AssertException.Create("Images do not match.");
                }
            }
        }
        /// <summary>
        /// Assert that <paramref name="expected"/> is equal to <paramref name="element"/> by comparing pixels.
        /// </summary>
        /// <param name="expected">The expected <see cref="Bitmap"/>.</param>
        /// <param name="element">The actual <see cref="UiElement"/>.</param>
        public static void AreEqual(Bitmap expected, UiElement element)
        {
            if (expected is null)
            {
                throw new ArgumentNullException(nameof(expected));
            }

            if (element is null)
            {
                throw new ArgumentNullException(nameof(element));
            }

            if (Equal(expected, element, RetryTime))
            {
                return;
            }

            using var actual = Capture.Rectangle(element.Bounds);
            if (Debugger.IsAttached)
            {
                ImageDiffWindow.Show(expected, actual);
            }

            throw ImageMatchException(expected, actual, null);
        }
        /// <summary>
        /// Compare Capture.Rectangle(element.Bounds) to the expected image.
        /// </summary>
        /// <param name="fileName">Can be a full file name relative filename or the name of a resource.</param>
        /// <param name="element">The automation element.</param>
        /// <param name="onFail">Useful for saving the actual image on error for example.</param>
        public static void AreEqual(string fileName, UiElement element, OnImageAssertFail onFail)
        {
            if (fileName is null)
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            if (element is null)
            {
                throw new ArgumentNullException(nameof(element));
            }

            if (onFail is null)
            {
                throw new ArgumentNullException(nameof(onFail));
            }

            if (TryGetStream(fileName, Assembly.GetCallingAssembly(), out var stream))
            {
                using (stream)
                {
                    using var expected = (Bitmap)Image.FromStream(stream);
                    if (Equal(expected, element, RetryTime))
                    {
                        return;
                    }

                    using var actual = Capture.Rectangle(element.Bounds);
                    if (Debugger.IsAttached)
                    {
                        ImageDiffWindow.Show(expected, actual);
                    }

                    onFail(expected, actual, fileName);
                    throw ImageMatchException(expected, actual, fileName);
                }
            }
            else
            {
                using var actual = Capture.Rectangle(element.Bounds);
                if (Debugger.IsAttached)
                {
                    ImageDiffWindow.Show(null, actual);
                }

                onFail(null, actual, fileName);
                throw MissingResourceException(actual, fileName);
            }
        }
        public static void Show(Bitmap?expected, Bitmap actual)
        {
            if (actual is null)
            {
                throw new System.ArgumentNullException(nameof(actual));
            }

            var dispatcher = WpfDispatcher.Create();

            dispatcher !.Invoke(() =>
            {
                var window = new ImageDiffWindow(expected, actual);
                _          = window.ShowDialog();
            });

            dispatcher.InvokeShutdown();
            _ = dispatcher.Thread.Join(1000);
        }
        /// <summary>
        /// Assert that <paramref name="expected"/> is equal to <paramref name="actual"/> by comparing pixels.
        /// </summary>
        /// <param name="expected">The expected <see cref="Bitmap"/>.</param>
        /// <param name="actual">The actual <see cref="Bitmap"/>.</param>
        public static void AreEqual(Bitmap expected, Bitmap actual)
        {
            if (expected is null)
            {
                throw new ArgumentNullException(nameof(expected));
            }

            if (actual is null)
            {
                throw new ArgumentNullException(nameof(actual));
            }

            if (!Equal(expected, actual))
            {
                if (Debugger.IsAttached)
                {
                    ImageDiffWindow.Show(expected, actual);
                }

                throw ImageMatchException(expected, actual, null);
            }
        }
        public static void Show(Bitmap expected, Bitmap actual)
        {
            using (var startedEvent = new ManualResetEventSlim(initialState: false))
            {
                System.Windows.Threading.Dispatcher dispatcher = null;
                var uiThread = new Thread(() =>
                {
                    // Create and install a new dispatcher context
                    SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
                    dispatcher = Dispatcher.CurrentDispatcher;

                    // Signal that it is initialized
                    // ReSharper disable once AccessToDisposedClosure
                    startedEvent.Set();

                    // Start the dispatcher processing
                    Dispatcher.Run();
                });

                // Set the apartment state
                uiThread.SetApartmentState(ApartmentState.STA);

                // Make the thread a background thread
                uiThread.IsBackground = true;

                // Start the thread
                uiThread.Start();
                startedEvent.Wait();
                dispatcher.Invoke(() =>
                {
                    var window = new ImageDiffWindow(expected, actual);
                    window.ShowDialog().IgnoreReturnValue();
                });

                dispatcher.InvokeShutdown();
                uiThread.Join(1000).IgnoreReturnValue();
            }
        }