public DroidCvPattern(Stream Stream, bool MakeMask)
        {
            var buffer = new byte[Stream.Length];

            using var ms = new MemoryStream(buffer);
            Stream.CopyTo(ms);

            using var raw = new DisposableMat(new MatOfByte(buffer));

            if (MakeMask)
            {
                using var rgbaMat = new DisposableMat(Imgcodecs.Imdecode(raw.Mat, Imgcodecs.CvLoadImageUnchanged));

                Mat = new Mat();
                Imgproc.CvtColor(rgbaMat.Mat, Mat, Imgproc.ColorRgba2gray);

                Mask = new Mat();
                // Extract alpha channel
                Core.ExtractChannel(rgbaMat.Mat, Mask, 3);
                // Mask containing 0 or 255
                Imgproc.Threshold(Mask, Mask, 0, 255, Imgproc.ThreshBinary);
            }
            else
            {
                Mat = Imgcodecs.Imdecode(raw.Mat, Imgcodecs.CvLoadImageGrayscale);
            }
        }
        public IEnumerable <Match> FindMatches(IPattern Template, double Similarity)
        {
            using var result = new DisposableMat();

            // max is used for tmccoeff
            Imgproc.MatchTemplate(Mat, (Template as DroidCvPattern)?.Mat, result.Mat, Imgproc.TmCcoeffNormed);

            Imgproc.Threshold(result.Mat, result.Mat, 0.1, 1, Imgproc.ThreshTozero);

            while (true)
            {
                using var minMaxLocResult = Core.MinMaxLoc(result.Mat);
                var score = minMaxLocResult.MaxVal;

                if (score >= Similarity)
                {
                    var loc    = minMaxLocResult.MaxLoc;
                    var region = new Region((int)loc.X, (int)loc.Y, Template.Width, Template.Height);

                    yield return(new Match(region, score));

                    using var mask = new DisposableMat();
                    // Flood fill eliminates the problem of nearby points to a high similarity point also having high similarity
                    const double floodFillDiff = 0.05;
                    Imgproc.FloodFill(result.Mat, mask.Mat, loc, new Scalar(0),
                                      new Rect(),
                                      new Scalar(floodFillDiff), new Scalar(floodFillDiff),
                                      0);
                }
                else
                {
                    break;
                }
            }
        }
        public bool IsMatch(IPattern Template, double Similarity)
        {
            using var result = new DisposableMat();

            Imgproc.MatchTemplate(Mat, (Template as DroidCvPattern)?.Mat, result.Mat, Imgproc.TmCcoeffNormed);

            using var minMaxLocResult = Core.MinMaxLoc(result.Mat);

            return(minMaxLocResult.MaxVal >= Similarity);
        }
        public DroidCvPattern(Stream Stream)
        {
            var buffer = new byte[Stream.Length];

            using var ms = new MemoryStream(buffer);
            Stream.CopyTo(ms);

            using var raw = new DisposableMat(new MatOfByte(buffer));

            Mat = Imgcodecs.Imdecode(raw.Mat, Imgcodecs.CvLoadImageGrayscale);
        }
        DisposableMat Match(DroidCvPattern Template)
        {
            var result = new DisposableMat();

            if (Template.Mask != null)
            {
                Imgproc.MatchTemplate(Mat, Template.Mat, result.Mat, Imgproc.TmCcorrNormed, Template.Mask);
            }
            else
            {
                Imgproc.MatchTemplate(Mat, Template.Mat, result.Mat, Imgproc.TmCcoeffNormed);
            }

            return(result);
        }