/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; namespace UVtools.Core { #region LayerIssue Class public class LayerIssue : IEnumerable { public enum IssueType : byte { Island, ResinTrap, TouchingBound, //HoleSandwich, } /// /// Gets the parent layer /// public Layer Layer { get; } /// /// Gets the issue type associated /// public IssueType Type { get; } /// /// Gets the pixels containing the issue /// public Point[] Pixels { get; } /// /// Gets the bounding rectangle of the pixel area /// public Rectangle BoundingRectangle { get; } /// /// Gets the X coordinate for the first point, -1 if doesn't exists /// public int X => HaveValidPoint ? Pixels[0].X : -1; /// /// Gets the Y coordinate for the first point, -1 if doesn't exists /// public int Y => HaveValidPoint ? Pixels[0].Y : -1; /// /// Gets the XY point for first point /// public Point Point => HaveValidPoint ? Pixels[0] : new Point(-1, -1); /// /// Gets the number of pixels on this issue /// public uint Size { get { if (Type == IssueType.ResinTrap && !BoundingRectangle.IsEmpty) { return (uint) (BoundingRectangle.Width * BoundingRectangle.Height); } if (ReferenceEquals(Pixels, null)) return 0; return (uint) Pixels.Length; } } /// /// Check if this issue have a valid start point to show /// public bool HaveValidPoint => !ReferenceEquals(Pixels, null) && Pixels.Length > 0; public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = new Rectangle()) { Layer = layer; Type = type; Pixels = pixels; BoundingRectangle = boundingRectangle; } public Point this[uint index] => Pixels[index]; public Point this[int index] => Pixels[index]; public IEnumerator GetEnumerator() { return ((IEnumerable)Pixels).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public override string ToString() { return $"{nameof(Type)}: {Type}, Layer: {Layer.Index}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Size)}: {Size}"; } } #endregion #region LayerHollowArea public class LayerHollowArea : IEnumerable { public enum AreaType : byte { Unknown = 0, Trap, Drain } /// /// Gets area pixels /// public Point[] Contour { get; } public System.Drawing.Rectangle BoundingRectangle { get; } public AreaType Type { get; set; } = AreaType.Unknown; public bool Processed { get; set; } #region Indexers public Point this[uint index] { get => index < Contour.Length ? Contour[index] : Point.Empty; set => Contour[index] = value; } public Point this[int index] { get => index < Contour.Length ? Contour[index] : Point.Empty; set => Contour[index] = value; } public Point this[uint x, uint y] { get { for (uint i = 0; i < Contour.Length; i++) { if (Contour[i].X == x && Contour[i].Y == y) return Contour[i]; } return Point.Empty; } } public Point this[int x, int y] => this[(uint) x, (uint)y]; public Point this[Point point] => this[point.X, point.Y]; #endregion public IEnumerator GetEnumerator() { return ((IEnumerable)Contour).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public LayerHollowArea() { } public LayerHollowArea(Point[] contour, System.Drawing.Rectangle boundingRectangle, AreaType type = AreaType.Unknown) { Contour = contour; BoundingRectangle = boundingRectangle; Type = type; } } #endregion #region Layer Class /// /// Represent a Layer /// public class Layer : IEquatable, IEquatable { #region Properties /// /// Gets the parent layer manager /// public LayerManager ParentLayerManager { get; set; } /// /// Gets the layer index /// public uint Index { get; } private byte[] _compressedBytes; /// /// Gets or sets layer image compressed data /// public byte[] CompressedBytes { get => LayerManager.DecompressLayer(_compressedBytes); set { _compressedBytes = LayerManager.CompressLayer(value); IsModified = true; } } /// /// Gets the original filename, null if no filename attached with layer /// public string Filename { get; set; } /// /// Gets if layer has been modified /// public bool IsModified { get; set; } /// /// Gets or sets a new image instance /// public Mat LayerMat { get { Mat mat = new Mat(); CvInvoke.Imdecode(CompressedBytes, ImreadModes.Grayscale, mat); return mat; } set { using (var vector = new VectorOfByte()) { CvInvoke.Imencode(".png", value, vector); CompressedBytes = vector.ToArray(); } } } /// /// Gets a new Brg image instance /// public Mat BrgMat { get { Mat mat = LayerMat; CvInvoke.CvtColor(mat, mat, ColorConversion.Gray2Bgr); return mat; } } #endregion #region Constructor public Layer(uint index, byte[] compressedBytes, string filename = null, LayerManager pararentLayerManager = null) { Index = index; CompressedBytes = compressedBytes; Filename = filename ?? $"Layer{index}.png"; IsModified = false; ParentLayerManager = pararentLayerManager; } public Layer(uint index, Mat layerMat, string filename = null, LayerManager pararentLayerManager = null) : this(index, new byte[0], filename, pararentLayerManager) { LayerMat = layerMat; IsModified = false; } public Layer(uint index, Stream stream, string filename = null, LayerManager pararentLayerManager = null) : this(index, stream.ToArray(), filename, pararentLayerManager) { } #endregion #region Equatables public static bool operator ==(Layer obj1, Layer obj2) { return obj1.Equals(obj2); } public static bool operator !=(Layer obj1, Layer obj2) { return !obj1.Equals(obj2); } public static bool operator >(Layer obj1, Layer obj2) { return obj1.Index > obj2.Index; } public static bool operator <(Layer obj1, Layer obj2) { return obj1.Index < obj2.Index; } public static bool operator >=(Layer obj1, Layer obj2) { return obj1.Index >= obj2.Index; } public static bool operator <=(Layer obj1, Layer obj2) { return obj1.Index <= obj2.Index; } public bool Equals(uint other) { return Index == other; } public bool Equals(Layer other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Equals(_compressedBytes, other._compressedBytes); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((Layer)obj); } public override int GetHashCode() { return (_compressedBytes != null ? _compressedBytes.GetHashCode() : 0); } private sealed class IndexRelationalComparer : IComparer { public int Compare(Layer x, Layer y) { if (ReferenceEquals(x, y)) return 0; if (ReferenceEquals(null, y)) return 1; if (ReferenceEquals(null, x)) return -1; return x.Index.CompareTo(y.Index); } } public static IComparer IndexComparer { get; } = new IndexRelationalComparer(); #endregion #region Formaters public override string ToString() { return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(IsModified)}: {IsModified}"; } #endregion #region Methods public Layer PreviousLayer() { if (ReferenceEquals(ParentLayerManager, null) || Index == 0) return null; return ParentLayerManager[Index - 1]; } public Layer NextLayer() { if (ReferenceEquals(ParentLayerManager, null) || Index >= ParentLayerManager.Count - 1) return null; return ParentLayerManager[Index + 1]; } /// /// Gets all islands start pixel location for this layer /// https://www.geeksforgeeks.org/find-number-of-islands/ /// /// holding all islands coordinates public List GetIssues(uint requiredPixelsToSupportIsland = 5) { if (requiredPixelsToSupportIsland == 0) requiredPixelsToSupportIsland = 1; // These arrays are used to // get row and column numbers // of 8 neighbors of a given cell List result = new List(); List pixels = new List(); var mat = LayerMat; var bytes = mat.GetPixelSpan(); var previousLayerImage = PreviousLayer()?.LayerMat; var previousBytes = previousLayerImage?.GetBytes(); /*var nextLayerImage = NextLayer()?.Image; byte[] nextBytes = null; if (!ReferenceEquals(nextLayerImage, null)) { if (nextLayerImage.TryGetSinglePixelSpan(out var nextPixelSpan)) { nextBytes = MemoryMarshal.AsBytes(nextPixelSpan).ToArray(); } }*/ // Make a bool array to // mark visited cells. // Initially all cells // are unvisited bool[,] visited = new bool[mat.Width, mat.Height]; // Initialize count as 0 and // traverse through the all // cells of given matrix //uint count = 0; // Island checker sbyte[] rowNbr = { -1, -1, -1, 0, 0, 1, 1, 1 }; sbyte[] colNbr = { -1, 0, 1, -1, 1, -1, 0, 1 }; const uint minPixel = 10; const uint minPixelForSupportIsland = 200; int pixelIndex; uint islandSupportingPixels; if (Index > 0) { for (int y = 0; y < mat.Height; y++) { for (int x = 0; x < mat.Width; x++) { pixelIndex = y * mat.Width + x; /*if (bytes[pixelIndex] == 0 && previousBytes?[pixelIndex] == byte.MaxValue && nextBytes?[pixelIndex] == byte.MaxValue) { result.Add(new LayerIssue(this, LayerIssue.IssueType.HoleSandwich, new []{new Point(x, y)})); }*/ if (bytes[pixelIndex] > minPixel && !visited[x, y]) { // If a cell with value 1 is not // visited yet, then new island // found, Visit all cells in this // island and increment island count pixels.Clear(); pixels.Add(new Point(x, y)); islandSupportingPixels = previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; int minX = x; int maxX = x; int minY = y; int maxY = y; int x2; int y2; Queue queue = new Queue(); queue.Enqueue(new Point(x, y)); // Mark this cell as visited visited[x, y] = true; while (queue.Count > 0) { var point = queue.Dequeue(); y2 = point.Y; x2 = point.X; for (byte k = 0; k < 8; k++) { //if (isSafe(y2 + rowNbr[k], x2 + colNbr[k])) var tempy2 = y2 + rowNbr[k]; var tempx2 = x2 + colNbr[k]; pixelIndex = tempy2 * mat.Width + tempx2; if (tempy2 >= 0 && tempy2 < mat.Height && tempx2 >= 0 && tempx2 < mat.Width && bytes[pixelIndex] >= minPixel && !visited[tempx2, tempy2]) { visited[tempx2, tempy2] = true; point = new Point(tempx2, tempy2); pixels.Add(point); queue.Enqueue(point); minX = Math.Min(minX, tempx2); maxX = Math.Max(maxX, tempx2); minY = Math.Min(minY, tempy2); maxY = Math.Max(maxY, tempy2); islandSupportingPixels += previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; } } } //count++; if (islandSupportingPixels >= requiredPixelsToSupportIsland) continue; // Not a island, bounding is strong if (islandSupportingPixels > 0 && pixels.Count < requiredPixelsToSupportIsland && islandSupportingPixels >= Math.Max(1, pixels.Count / 2)) continue; // Not a island result.Add(new LayerIssue(this, LayerIssue.IssueType.Island, pixels.ToArray(), new Rectangle(minX, minY, maxX-minX, maxY-minY))); } } } } pixels.Clear(); // TouchingBounds Checker for (int x = 0; x < mat.Width; x++) // Check Top and Bottom bounds { if (bytes[x] >= 200) // Top { pixels.Add(new Point(x, 0)); } if (bytes[mat.Width * mat.Height - mat.Width + x] >= 200) // Bottom { pixels.Add(new Point(x, mat.Height-1)); } } for (int y = 0; y < mat.Height; y++) // Check Left and Right bounds { if (bytes[y * mat.Width] >= 200) // Left { pixels.Add(new Point(0, y)); } if (bytes[y * mat.Width + mat.Width - 1] >= 200) // Right { pixels.Add(new Point(mat.Width-1, y)); } } if (pixels.Count > 0) { result.Add(new LayerIssue(this, LayerIssue.IssueType.TouchingBound, pixels.ToArray())); } pixels.Clear(); return result; } public void MutateResize(double xScale, double yScale) { using (var mat = LayerMat) { mat.ScaleFromCenter(xScale, yScale); LayerMat = mat; } } public void MutateSolidify() { using (Mat mat = LayerMat) { using (Mat filteredMat = new Mat()) { CvInvoke.Threshold(mat, filteredMat, 254, 255, ThresholdType.Binary); // Clean AA using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) { using (Mat hierarchy = new Mat()) { CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); var arr = hierarchy.GetData(); for (int i = 0; i < contours.Size; i++) { if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1) continue; CvInvoke.DrawContours(mat, contours, i, new MCvScalar(255), -1); } } } } LayerMat = mat; } } public void MutateErode(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if(anchor.IsEmpty) anchor = new Point(-1, -1); if (ReferenceEquals(kernel, null)) { kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); } using (Mat dst = LayerMat) { CvInvoke.Erode(dst, dst, kernel, anchor, iterations, borderType, borderValue); LayerMat = dst; } } public void MutateDilate(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if (anchor.IsEmpty) anchor = new Point(-1, -1); if (ReferenceEquals(kernel, null)) { kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); } using (Mat dst = LayerMat) { CvInvoke.Dilate(dst, dst, kernel, anchor, iterations, borderType, borderValue); LayerMat = dst; } } public void MutateOpen(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if (anchor.IsEmpty) anchor = new Point(-1, -1); if (ReferenceEquals(kernel, null)) { kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); } using (Mat dst = LayerMat) { CvInvoke.MorphologyEx(dst, dst, MorphOp.Open, kernel, anchor, iterations, borderType, borderValue); LayerMat = dst; } } public void MutateClose(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if (anchor.IsEmpty) anchor = new Point(-1, -1); if (ReferenceEquals(kernel, null)) { kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); } using (Mat dst = LayerMat) { CvInvoke.MorphologyEx(dst, dst, MorphOp.Close, kernel, anchor, iterations, borderType, borderValue); LayerMat = dst; } } public void MutateGradient(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if (anchor.IsEmpty) anchor = new Point(-1, -1); if (ReferenceEquals(kernel, null)) { kernel = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), anchor); } using (Mat dst = LayerMat) { CvInvoke.MorphologyEx(dst, dst, MorphOp.Gradient, kernel, anchor, iterations, borderType, borderValue); LayerMat = dst; } } public void MutatePyrDownUp(BorderType borderType = BorderType.Reflect101) { using (Mat dst = LayerMat) { CvInvoke.PyrDown(dst, dst, borderType); CvInvoke.PyrUp(dst, dst, borderType); LayerMat = dst; } } public void MutateMedianBlur(int aperture = 1) { using (Mat dst = LayerMat) { CvInvoke.MedianBlur(dst, dst, aperture); LayerMat = dst; } } public void MutateGaussianBlur(Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101) { if(size.IsEmpty) size = new Size(5, 5); using (Mat dst = LayerMat) { CvInvoke.GaussianBlur(dst, dst, size, sigmaX, sigmaY, borderType); LayerMat = dst; } } public Layer Clone() { return new Layer(Index, CompressedBytes, Filename, ParentLayerManager); } #endregion } #endregion #region LayerManager Class public class LayerManager : IEnumerable { #region Properties /// /// Layers List /// public Layer[] Layers { get; } /// /// Gets the layers count /// public uint Count => (uint) Layers.Length; /// /// Gets if any layer got modified, otherwise false /// public bool IsModified { get { for (uint i = 0; i < Count; i++) { if (Layers[i].IsModified) return true; } return false; } } #endregion #region Constructors public LayerManager(uint layerCount) { Layers = new Layer[layerCount]; } #endregion #region Indexers public Layer this[uint index] { get => Layers[index]; set => AddLayer(index, value); } public Layer this[int index] { get => Layers[index]; set => AddLayer((uint) index, value); } public Layer this[long index] { get => Layers[index]; set => AddLayer((uint) index, value); } #endregion #region Numerators public IEnumerator GetEnumerator() { return ((IEnumerable)Layers).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Static Methods /// /// Compress a layer from a /// /// to compress /// Compressed byte array public static byte[] CompressLayer(Stream input) { return CompressLayer(input.ToArray()); } /// /// Compress a layer from a byte array /// /// byte array to compress /// Compressed byte array public static byte[] CompressLayer(byte[] input) { return input; /*using (MemoryStream output = new MemoryStream()) { using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) { dstream.Write(input, 0, input.Length); } return output.ToArray(); }*/ } /// /// Decompress a layer from a byte array /// /// byte array to decompress /// Decompressed byte array public static byte[] DecompressLayer(byte[] input) { return input; /*using (MemoryStream ms = new MemoryStream(input)) { using (MemoryStream output = new MemoryStream()) { using (DeflateStream dstream = new DeflateStream(ms, CompressionMode.Decompress)) { dstream.CopyTo(output); } return output.ToArray(); } }*/ } #endregion #region Methods /// /// Add a layer /// /// Layer index /// Layer to add public void AddLayer(uint index, Layer layer) { Layers[index] = layer; layer.ParentLayerManager = this; } /// /// Get layer given index /// /// Layer index /// public Layer GetLayer(uint index) { return Layers[index]; } /// /// Resizes layer images in x and y factor, starting at 1 = 100% /// /// Layer index to start /// Layer index to end /// X factor, starts at 1 /// Y factor, starts at 1 /// Fade X/Y towards 100% public void MutateResize(uint startLayerIndex, uint endLayerIndex, double x, double y, bool isFade) { if (x == 1.0 && y == 1.0) return; double xSteps = Math.Abs(x - 1.0) / (endLayerIndex - startLayerIndex); double ySteps = Math.Abs(y - 1.0) / (endLayerIndex - startLayerIndex); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { var newX = x; var newY = y; if (isFade) { if (newX != 1.0) { //maxIteration = Math.Max(iterationsStart, iterationsEnd); newX = (float)(newX < 1.0 ? newX + (layerIndex - startLayerIndex) * xSteps : newX - (layerIndex - startLayerIndex) * xSteps); // constrain //iterations = Math.Min(Math.Max(1, iterations), maxIteration); } if (y != 1.0) { //maxIteration = Math.Max(iterationsStart, iterationsEnd); newY = (float)(newY < 1.0 ? newY + (layerIndex - startLayerIndex) * ySteps : newY - (layerIndex - startLayerIndex) * ySteps); // constrain //iterations = Math.Min(Math.Max(1, iterations), maxIteration); } } if (newX == 1.0 && newY == 1.0) return; this[layerIndex].MutateResize(newX, newY); }); } public void MutateSolidify(uint startLayerIndex, uint endLayerIndex) { Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { this[layerIndex].MutateSolidify(); }); } private void MutateGetVarsIterationFade(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out int iterationSteps, out int maxIteration) { iterationSteps = 0; maxIteration = 0; isFade = isFade && startLayerIndex != endLayerIndex && iterationsStart != iterationsEnd; if (!isFade) return; iterationSteps = (int)Math.Abs((double)(iterationsStart - iterationsEnd) / (endLayerIndex - startLayerIndex)); maxIteration = Math.Max(iterationsStart, iterationsEnd); } private int MutateGetIterationVar(bool isFade, int iterationsStart, int iterationsEnd, int iterationSteps, int maxIteration, uint startLayerIndex, uint layerIndex) { if(!isFade) return iterationsStart; // calculate iterations based on range int iterations = (int)(iterationsStart < iterationsEnd ? iterationsStart + (layerIndex - startLayerIndex) * iterationSteps : iterationsStart - (layerIndex - startLayerIndex) * iterationSteps); // constrain return Math.Min(Math.Max(1, iterations), maxIteration); } public void MutateErode(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { MutateGetVarsIterationFade( startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade, out var iterationSteps, out var maxIteration ); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint) layerIndex); this[layerIndex].MutateErode(iterations, kernel, anchor, borderType, borderValue); }); } public void MutateDilate(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { MutateGetVarsIterationFade( startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade, out var iterationSteps, out var maxIteration ); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); this[layerIndex].MutateDilate(iterations, kernel, anchor, borderType, borderValue); }); } public void MutateOpen(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { MutateGetVarsIterationFade( startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade, out var iterationSteps, out var maxIteration ); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); this[layerIndex].MutateOpen(iterations, kernel, anchor, borderType, borderValue); }); } public void MutateClose(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { MutateGetVarsIterationFade( startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade, out var iterationSteps, out var maxIteration ); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); this[layerIndex].MutateClose(iterations, kernel, anchor, borderType, borderValue); }); } public void MutateGradient(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { MutateGetVarsIterationFade( startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade, out var iterationSteps, out var maxIteration ); Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); this[layerIndex].MutateGradient(iterations, kernel, anchor, borderType, borderValue); }); } public void MutatePyrDownUp(uint startLayerIndex, uint endLayerIndex, BorderType borderType = BorderType.Default) { Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { this[layerIndex].MutatePyrDownUp(borderType); }); } public void MutateMedianBlur(uint startLayerIndex, uint endLayerIndex, int aperture = 1) { Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { this[layerIndex].MutateMedianBlur(aperture); }); } public void MutateGaussianBlur(uint startLayerIndex, uint endLayerIndex, Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101) { Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => { this[layerIndex].MutateGaussianBlur(size, sigmaX, sigmaY, borderType); }); } public ConcurrentDictionary> GetAllIssues() { const byte requiredPixelsToSupportIsland = 10; const byte minIslandPixelToCheck = 10; const byte minIslandSupportPixelColor = 150; const byte minTouchingBondsPixelColor = 200; var result = new ConcurrentDictionary>(); /*Parallel.ForEach(this, layer => { var issues = layer.GetIssues(); if (issues.Count > 0) { if (!result.TryAdd(layer.Index, issues)) { throw new AccessViolationException("Error while trying to add an issue to the dictionary, please try again."); } } });*/ var task1 = new TaskFactory().StartNew(() => { // Detect contours Parallel.ForEach(this, //new ParallelOptions{MaxDegreeOfParallelism = 1}, layer => { using (var image = layer.LayerMat) { int step = image.Step; var span = image.GetPixelSpan(); // TouchingBounds Checker List pixels = new List(); for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds { if (span[x] >= minTouchingBondsPixelColor) // Top { pixels.Add(new Point(x, 0)); } if (span[step * image.Height - step + x] >= minTouchingBondsPixelColor) // Bottom { pixels.Add(new Point(x, image.Height - 1)); } } for (int y = 0; y < image.Height; y++) // Check Left and Right bounds { if (span[y * step] >= minTouchingBondsPixelColor) // Left { pixels.Add(new Point(0, y)); } if (span[y * step + step - 1] >= minTouchingBondsPixelColor) // Right { pixels.Add(new Point(step - 1, y)); } } if (pixels.Count > 0) { result.TryAdd(layer.Index, new List { new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray()) }); } if (layer.Index == 0) return; // No inslands for layer 0 CvInvoke.Threshold(image, image, 1, 255, ThresholdType.Binary); VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); Mat hierarchy = new Mat(); CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); var arr = hierarchy.GetData(); // //hierarchy[i][0]: the index of the next contour of the same level //hierarchy[i][1]: the index of the previous contour of the same level //hierarchy[i][2]: the index of the first child //hierarchy[i][3]: the index of the parent // Mat previousImage = null; Span previousSpan = null; Span imageSpan = null; for (int i = 0; i < contours.Size; i++) { if ((int) arr.GetValue(0, i, 2) == -1 && (int) arr.GetValue(0, i, 3) != -1) continue; if (ReferenceEquals(previousImage, null)) { previousImage = this[layer.Index - 1].LayerMat; previousSpan = previousImage.GetPixelSpan(); imageSpan = image.GetPixelSpan(); } var rect = CvInvoke.BoundingRectangle(contours[i]); List points = new List(); uint pixelsSupportingIsland = 0; using (Mat contourImage = image.CloneBlank()) { CvInvoke.DrawContours(contourImage, contours, i, new MCvScalar(255), -1); var contourImageSpan = contourImage.GetPixelSpan(); for (int y = rect.Y; y < rect.Bottom; y++) { for (int x = rect.X; x < rect.Right; x++) { //int pixel = image.GetPixelPos(x, y); int pixel = step * y + x; if (imageSpan[pixel] < minIslandPixelToCheck) continue; // Low brightness, ignore if (contourImageSpan[pixel] != 255) continue; // Not inside contour, ignore //if (CvInvoke.PointPolygonTest(contours[i], new PointF(x, y), false) < 0) continue; // Out of contour SLOW! //Debug.WriteLine($"Layer: {layer.Index}, Coutour: {i}, X:{x} Y:{y}"); points.Add(new Point(x, y)); if (previousSpan[pixel] >= minIslandSupportPixelColor) { pixelsSupportingIsland++; } } } } if (points.Count == 0) continue; if (pixelsSupportingIsland >= requiredPixelsToSupportIsland) continue; // Not a island, bounding is strong, i think... if (pixelsSupportingIsland > 0 && points.Count < requiredPixelsToSupportIsland && pixelsSupportingIsland >= Math.Max(1, points.Count / 2)) continue; // Not a island, but maybe weak bounding... var issue = new LayerIssue(layer, LayerIssue.IssueType.Island, points.ToArray(), rect); result.AddOrUpdate(layer.Index, new List {issue}, (layerIndex, list) => { list.Add(issue); return list; }); } contours.Dispose(); hierarchy.Dispose(); previousImage?.Dispose(); } }); }); var layerHollowAreas = new ConcurrentDictionary>(); var task2 = new TaskFactory().StartNew(() => { // Detect contours Parallel.ForEach(this, //new ParallelOptions{MaxDegreeOfParallelism = 1}, layer => { using (var image = layer.LayerMat) { CvInvoke.Threshold(image, image, 254, 255, ThresholdType.Binary); var listHollowArea = new List(); using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) { using (Mat hierarchy = new Mat()) { CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); var arr = hierarchy.GetData(); // //hierarchy[i][0]: the index of the next contour of the same level //hierarchy[i][1]: the index of the previous contour of the same level //hierarchy[i][2]: the index of the first child //hierarchy[i][3]: the index of the parent // for (int i = 0; i < contours.Size; i++) { if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1) continue; listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(), CvInvoke.BoundingRectangle(contours[i]), layer.Index == 0 || layer.Index == Count - 1 ? LayerHollowArea.AreaType.Drain : LayerHollowArea.AreaType.Unknown)); if (listHollowArea.Count > 0) layerHollowAreas.TryAdd(layer.Index, listHollowArea); } } } } }); for (uint layerIndex = 1; layerIndex < Count - 1; layerIndex++) // Ignore first and last layers, always drains { if (!layerHollowAreas.TryGetValue(layerIndex, out var areas)) continue; // No hollow areas in this layer, ignore byte areaCount = 0; //foreach (var area in areas) Parallel.ForEach(from t in areas where t.Type == LayerHollowArea.AreaType.Unknown select t, area => { //if (area.Type != LayerHollowArea.AreaType.Unknown) return; // processed, ignore area.Type = LayerHollowArea.AreaType.Trap; areaCount++; List linkedAreas = new List(); for (sbyte dir = 1; dir >= -1 && area.Type != LayerHollowArea.AreaType.Drain; dir -= 2) //Parallel.ForEach(new sbyte[] {1, -1}, new ParallelOptions {MaxDegreeOfParallelism = 2}, dir => { Queue queue = new Queue(); queue.Enqueue(area); area.Processed = false; int nextLayerIndex = (int) layerIndex; while (queue.Count > 0 && area.Type != LayerHollowArea.AreaType.Drain) { LayerHollowArea checkArea = queue.Dequeue(); if (checkArea.Processed) continue; checkArea.Processed = true; nextLayerIndex += dir; Debug.WriteLine($"Area Count: {areaCount} | Layer: {layerIndex} | Next Layer: {nextLayerIndex} | Dir: {dir}"); if (nextLayerIndex < 0 && nextLayerIndex >= Count) break; // Exhausted layers bool haveNextAreas = layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas); List intersectingAreas = new List(); using (var image = this[nextLayerIndex].LayerMat) { var span = image.GetPixelSpan(); using (var emguImage = image.CloneBlank()) { using(var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) { CvInvoke.DrawContours(emguImage, vec, -1, new MCvScalar(255), -1); } if (haveNextAreas) { foreach (var nextArea in nextAreas) { if (!checkArea.BoundingRectangle.IntersectsWith(nextArea.BoundingRectangle)) continue; intersectingAreas.Add(nextArea); /*CvInvoke.DrawContours(emguImage, new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour)), -1, new MCvScalar(intersectingAreas.Count), -1);*/ } if (intersectingAreas.Count == 0) { haveNextAreas = false; } } bool exitPixelLoop = false; uint blackCount = 0; var spanContour = emguImage.GetPixelSpan(); for (int y = checkArea.BoundingRectangle.Y; y <= checkArea.BoundingRectangle.Bottom && area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; y++) { int pixelPos = image.GetPixelPos(checkArea.BoundingRectangle.X, y)-1; for (int x = checkArea.BoundingRectangle.X; x <= checkArea.BoundingRectangle.Right && area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; x++) { pixelPos++; if (spanContour[pixelPos] == 0) continue; // No contour if (span[pixelPos] > 30) continue; // Threshold to ignore white area blackCount++; if (haveNextAreas) // Have areas, can be on same area path or not { /*int i = spanContour[pixelPos] - 1; if (i == -1 || i >= 254) continue; //if(queue.Contains(intersectingAreas[i])) continue; //Debug.WriteLine($"BlackCount: {blackCount}, pixel color: {i}, layerindex: {layerIndex}"); if (intersectingAreas[i].Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query { area.Type = LayerHollowArea.AreaType.Drain; } else { queue.Enqueue(intersectingAreas[i]); } linkedAreas.Add(intersectingAreas[i]); exitPixelLoop = true; break;*/ foreach (var nextAreaCheck in intersectingAreas) { using (var vec = new VectorOfPoint(nextAreaCheck.Contour)) { //Debug.WriteLine(CvInvoke.PointPolygonTest(vec, new PointF(x, y), false)); if (CvInvoke.PointPolygonTest(vec, new PointF(x, y), false) < 0) continue; } if (nextAreaCheck.Type == LayerHollowArea.AreaType.Drain ) // Found a drain, stop query { area.Type = LayerHollowArea.AreaType.Drain; exitPixelLoop = true; } else { queue.Enqueue(area); } linkedAreas.Add(nextAreaCheck); intersectingAreas.Remove(nextAreaCheck); haveNextAreas = intersectingAreas.Count > 0; break; //exitPixelLoop = true; //break; } } else if (blackCount > Math.Min(checkArea.Contour.Length / 2, 10)) // Black pixel without next areas = Drain { area.Type = LayerHollowArea.AreaType.Drain; exitPixelLoop = true; break; } } // X loop } // Y loop if (queue.Count == 0 && blackCount > Math.Min(checkArea.Contour.Length / 2, 10)) { area.Type = LayerHollowArea.AreaType.Drain; } } // Dispose emgu image } // Dispose image } // Areas loop } // Dir layer loop foreach (var linkedArea in linkedAreas) // Update linked areas { linkedArea.Type = area.Type; } }); } }); task1.Wait(); // Islands & bounds task2.Wait(); // Resin trap for (uint layerIndex = 0; layerIndex < Count; layerIndex++) { if (!layerHollowAreas.TryGetValue(layerIndex, out var list)) continue; if (list.Count == 0) continue; foreach (var issue in from area in list where area.Type == LayerHollowArea.AreaType.Trap select new LayerIssue(this[layerIndex], LayerIssue.IssueType.ResinTrap, area.Contour, area.BoundingRectangle)) { result.AddOrUpdate(layerIndex, new List {issue}, (u, listIssues) => { listIssues.Add(issue); return listIssues; }); } } return result; } /// /// Desmodify all layers /// public void Desmodify() { for (uint i = 0; i < Count; i++) { Layers[i].IsModified = false; } } /// /// Clone this object /// /// public LayerManager Clone() { LayerManager layerManager = new LayerManager(Count); foreach (var layer in this) { layerManager[layer.Index] = layer.Clone(); } return layerManager; } #endregion } #endregion }