/// <summary>
 /// Adds an inner contour to the present object. The inner contour should contain
 /// the points on the inner rim of the region of interest (innermost points that are
 /// still foreground).
 /// </summary>
 /// <param name="inner">The points on the inner rim of the region, surrounding any "holes".</param>
 public void AddInnerContour(PolygonPoints inner)
 {
     Inner.Add(inner ?? throw new ArgumentNullException(nameof(inner)));
     TotalPixels -= inner.VoxelCounts.Total;
 }
        /// <summary>
        /// Extracts polygons by walking the edge of the foreground values of the worker volume.
        /// This method will return closed polygons. Also, the polygons will be filled (any holes removed).
        /// The <paramref name="foundPolygons"/> argument will be updated in place, by marking all voxels
        /// that are found to be inside of a polygon with the index of that polygon.
        /// The first new polygon that is found will be given the number supplied in <paramref name="firstNewPolygon"/>
        /// (must be 1 or higher)
        /// </summary>
        /// <param name="binaryVolume">The volume we are extracting polygons from. Must only contain values 0 and 1.</param>
        /// <param name="foundPolygons">A volume that contains the IDs for all polygons that have been found already. 0 means no polygon found.</param>
        /// <param name="searchInsidePolygon">For walking along edges, limit to the voxels that presently are assigned to
        /// the polygon given here.</param>
        /// <param name="isInnerPolygon">If true, the polygon will walk along boundaries around regions with
        /// voxel value 0 (background), but keep the polyon points on voxel values 1, counterclockwises.</param>
        /// <param name="firstNewPolygon">The ID for the first polygon that is found.</param>
        /// <returns>A collection of polygons and there respective sizes (number of foreground points in each polygon)</returns>
        private static Dictionary <ushort, PolygonPoints> ExtractPolygons(
            Volume2D <byte> binaryVolume,
            ushort[] foundPolygons,
            ushort searchInsidePolygon,
            bool isInnerPolygon,
            ushort firstNewPolygon)
        {
            if (binaryVolume == null)
            {
                throw new ArgumentNullException(nameof(binaryVolume));
            }

            if (foundPolygons == null)
            {
                throw new ArgumentNullException(nameof(foundPolygons));
            }

            if (firstNewPolygon < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(firstNewPolygon), "Polygon index 0 is reserved for 'not assigned'");
            }

            var foregroundId = isInnerPolygon ? (byte)0 : (byte)1;
            var polygons     = new Dictionary <ushort, PolygonPoints>();
            var dimX         = binaryVolume.DimX;
            var dimY         = binaryVolume.DimY;
            var volumeArray  = binaryVolume.Array;

            for (var y = 0; y < dimY; y++)
            {
                // Manually computing index, rather than relying on GetIndex, brings substantial speedup.
                var offsetY = y * dimX;
                for (var x = 0; x < dimX; x++)
                {
                    var pixelIndex = x + offsetY;

                    // Starting point of a new polygon is where we see the desired foreground in the original
                    // volume, and have either not found any polygon yet (searchInsidePolygon == 0)
                    // or have found a polygon already and now search for the holes inside it.
                    if (volumeArray[pixelIndex] == foregroundId && foundPolygons[pixelIndex] == searchInsidePolygon)
                    {
                        PointInt startPoint;
                        if (isInnerPolygon)
                        {
                            Debug.Assert(y >= 1, "When searching for innner polygons (holes), expecting that there is foreground in the row above.");
                            startPoint = new PointInt(x, y - 1);
                        }
                        else
                        {
                            startPoint = new PointInt(x, y);
                        }

                        VoxelCounts voxelCounts;
                        PointInt[]  contourPoints;
                        if (isInnerPolygon)
                        {
                            var innerPoints = FindPolygon(
                                binaryVolume,
                                foundPolygons,
                                searchInsidePolygon,
                                new PointInt(x, y),
                                backgroundId: 1,
                                searchClockwise: true);
                            voxelCounts = FillPolygon.FillPolygonAndCount(
                                innerPoints,
                                foundPolygons,
                                firstNewPolygon,
                                binaryVolume,
                                foregroundId: 0);
                            contourPoints = FindPolygon(
                                binaryVolume,
                                foundPolygons,
                                searchInsidePolygon,
                                startPoint,
                                backgroundId: 0,
                                searchClockwise: false);
                        }
                        else
                        {
                            contourPoints = FindPolygon(
                                binaryVolume,
                                foundPolygons,
                                searchInsidePolygon,
                                startPoint,
                                backgroundId: 0,
                                searchClockwise: true);
                            voxelCounts = FillPolygon.FillPolygonAndCount(
                                contourPoints,
                                foundPolygons,
                                firstNewPolygon,
                                binaryVolume,
                                foregroundId);
                        }

                        var polygon = new PolygonPoints(
                            contourPoints,
                            voxelCounts,
                            searchInsidePolygon,
                            isInside: isInnerPolygon,
                            startPointMinimumY: startPoint);
                        polygons.Add(firstNewPolygon, polygon);
                        firstNewPolygon++;
                    }
                }
            }

            return(polygons);
        }
 /// <summary>
 /// Creates a new instance of the class.
 /// </summary>
 /// <param name="outer">The points that make up the outer rim of the region, traversed clockwise.</param>
 public InnerOuterPolygon(PolygonPoints outer)
 {
     Outer       = outer ?? throw new ArgumentNullException(nameof(outer));
     Inner       = new List <PolygonPoints>();
     TotalPixels = outer.VoxelCounts.Total;
 }