Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'UVtools.Core/Layer/LayerManager.cs')
-rw-r--r--UVtools.Core/Layer/LayerManager.cs1161
1 files changed, 1161 insertions, 0 deletions
diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs
new file mode 100644
index 0000000..cf21310
--- /dev/null
+++ b/UVtools.Core/Layer/LayerManager.cs
@@ -0,0 +1,1161 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * 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.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;
+using UVtools.Core.Operations;
+
+namespace UVtools.Core
+{
+ public class LayerManager : IEnumerable<Layer>
+ {
+ #region Enums
+ public enum Mutate : byte
+ {
+ Move,
+ Resize,
+ Flip,
+ Rotate,
+ Solidify,
+ //LayerSmash,
+ Erode,
+ Dilate,
+ Opening,
+ Closing,
+ Gradient,
+ TopHat,
+ BlackHat,
+ HitMiss,
+ PyrDownUp,
+ SmoothMedian,
+ SmoothGaussian,
+ }
+ #endregion
+
+ #region Properties
+ /// <summary>
+ /// Layers List
+ /// </summary>
+ public Layer[] Layers { get; }
+
+ private Rectangle _boundingRectangle = Rectangle.Empty;
+ public Rectangle BoundingRectangle
+ {
+ get => GetBoundingRectangle();
+ set => _boundingRectangle = value;
+ }
+
+ /// <summary>
+ /// Gets the layers count
+ /// </summary>
+ public uint Count => (uint) Layers.Length;
+
+ /// <summary>
+ /// Gets if any layer got modified, otherwise false
+ /// </summary>
+ 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<Layer> GetEnumerator()
+ {
+ return ((IEnumerable<Layer>)Layers).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ #endregion
+
+ #region Static Methods
+ /// <summary>
+ /// Compress a layer from a <see cref="Stream"/>
+ /// </summary>
+ /// <param name="input"><see cref="Stream"/> to compress</param>
+ /// <returns>Compressed byte array</returns>
+ public static byte[] CompressLayer(Stream input)
+ {
+ return CompressLayer(input.ToArray());
+ }
+
+ /// <summary>
+ /// Compress a layer from a byte array
+ /// </summary>
+ /// <param name="input">byte array to compress</param>
+ /// <returns>Compressed byte array</returns>
+ 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();
+ }*/
+ }
+
+ /// <summary>
+ /// Decompress a layer from a byte array
+ /// </summary>
+ /// <param name="input">byte array to decompress</param>
+ /// <returns>Decompressed byte array</returns>
+ 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
+
+ public Rectangle GetBoundingRectangle(OperationProgress progress = null)
+ {
+ if (!_boundingRectangle.IsEmpty) return _boundingRectangle;
+ _boundingRectangle = this[0].BoundingRectangle;
+ if (_boundingRectangle.IsEmpty) // Safe checking
+ {
+ progress?.Reset(OperationProgress.StatusOptimizingBounds, Count);
+ Parallel.For(0, Count, layerIndex =>
+ {
+ if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ this[layerIndex].GetBoundingRectangle();
+
+ if (ReferenceEquals(progress, null)) return;
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ _boundingRectangle = this[0].BoundingRectangle;
+
+ if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested)
+ {
+ _boundingRectangle = Rectangle.Empty;
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ }
+
+ progress?.Reset(OperationProgress.StatusCalculatingBounds, Count);
+ for (int i = 1; i < Count; i++)
+ {
+ _boundingRectangle = Rectangle.Union(_boundingRectangle, this[i].BoundingRectangle);
+ if (ReferenceEquals(progress, null)) continue;
+ progress++;
+ }
+
+ return _boundingRectangle;
+ }
+
+ /// <summary>
+ /// Add a layer
+ /// </summary>
+ /// <param name="index">Layer index</param>
+ /// <param name="layer">Layer to add</param>
+ public void AddLayer(uint index, Layer layer)
+ {
+ Layers[index] = layer;
+ layer.ParentLayerManager = this;
+ }
+
+ /// <summary>
+ /// Get layer given index
+ /// </summary>
+ /// <param name="index">Layer index</param>
+ /// <returns></returns>
+ public Layer GetLayer(uint index)
+ {
+ return Layers[index];
+ }
+
+ public void MutateMove(uint startLayerIndex, uint endLayerIndex, OperationMove move, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Moving", endLayerIndex - startLayerIndex + 1);
+
+ if (move.SrcRoi == Rectangle.Empty) move.SrcRoi = GetBoundingRectangle(progress);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+
+ this[layerIndex].MutateMove(move);
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+
+ _boundingRectangle = Rectangle.Empty;
+
+ progress.Token.ThrowIfCancellationRequested();
+
+
+ }
+
+ /// <summary>
+ /// Resizes layer images in x and y factor, starting at 1 = 100%
+ /// </summary>
+ /// <param name="startLayerIndex">Layer index to start</param>
+ /// <param name="endLayerIndex">Layer index to end</param>
+ /// <param name="x">X factor, starts at 1</param>
+ /// <param name="y">Y factor, starts at 1</param>
+ /// <param name="isFade">Fade X/Y towards 100%</param>
+ public void MutateResize(uint startLayerIndex, uint endLayerIndex, double x, double y, bool isFade, OperationProgress progress = null)
+ {
+ if (x == 1.0 && y == 1.0) return;
+
+ if(ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Resizing", endLayerIndex - startLayerIndex + 1);
+
+ double xSteps = Math.Abs(x - 1.0) / (endLayerIndex - startLayerIndex);
+ double ySteps = Math.Abs(y - 1.0) / (endLayerIndex - startLayerIndex);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ 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);
+ }
+ }
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+
+ if (newX == 1.0 && newY == 1.0) return;
+
+ this[layerIndex].MutateResize(newX, newY);
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateFlip(uint startLayerIndex, uint endLayerIndex, FlipType flipType, bool makeCopy = false, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Fliping", endLayerIndex - startLayerIndex + 1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutateFlip(flipType, makeCopy);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateRotate(uint startLayerIndex, uint endLayerIndex, double angle, Inter interpolation = Inter.Linear, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Rotating", endLayerIndex - startLayerIndex+1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutateRotate(angle, interpolation);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateSolidify(uint startLayerIndex, uint endLayerIndex, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Solidifing", endLayerIndex - startLayerIndex+1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutateSolidify();
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ 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, OperationProgress progress = null,
+ 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
+ );
+
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Eroding", endLayerIndex - startLayerIndex+1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint) layerIndex);
+ this[layerIndex].MutateErode(iterations, kernel, anchor, borderType, borderValue);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateDilate(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null,
+ 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
+ );
+
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Dilating", endLayerIndex - startLayerIndex+1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex);
+ this[layerIndex].MutateDilate(iterations, kernel, anchor, borderType, borderValue);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateOpen(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null,
+ 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
+ );
+
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Removing Noise", endLayerIndex - startLayerIndex+1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex);
+ this[layerIndex].MutateOpen(iterations, kernel, anchor, borderType, borderValue);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateClose(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null,
+ 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
+ );
+
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Gap Closing", endLayerIndex - startLayerIndex+1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex);
+ this[layerIndex].MutateClose(iterations, kernel, anchor, borderType, borderValue);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateGradient(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null,
+ 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
+ );
+
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Gradient", endLayerIndex - startLayerIndex+1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex);
+ this[layerIndex].MutateGradient(iterations, kernel, anchor, borderType, borderValue);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutatePyrDownUp(uint startLayerIndex, uint endLayerIndex, BorderType borderType = BorderType.Default, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("PryDownUp", endLayerIndex - startLayerIndex+1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutatePyrDownUp(borderType);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateMedianBlur(uint startLayerIndex, uint endLayerIndex, int aperture = 1, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Bluring", endLayerIndex - startLayerIndex+1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutateMedianBlur(aperture);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void MutateGaussianBlur(uint startLayerIndex, uint endLayerIndex, Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Bluring", endLayerIndex - startLayerIndex+1);
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ this[layerIndex].MutateGaussianBlur(size, sigmaX, sigmaY, borderType);
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public ConcurrentDictionary<uint, List<LayerIssue>> GetAllIssues(
+ IslandDetectionConfiguration islandConfig = null, ResinTrapDetectionConfiguration resinTrapConfig = null,
+ OperationProgress progress = null)
+ {
+ if(ReferenceEquals(islandConfig, null)) islandConfig = new IslandDetectionConfiguration();
+ if(ReferenceEquals(resinTrapConfig, null)) resinTrapConfig = new ResinTrapDetectionConfiguration();
+ if(ReferenceEquals(progress, null)) progress = new OperationProgress();
+
+ const byte minTouchingBondsPixelColor = 200;
+
+ var result = new ConcurrentDictionary<uint, List<LayerIssue>>();
+ var layerHollowAreas = new ConcurrentDictionary<uint, List<LayerHollowArea>>();
+
+ bool islandsFinished = false;
+
+ progress.Reset(OperationProgress.StatusIslands, Count);
+
+ Parallel.Invoke(() =>
+ {
+ if (!islandConfig.Enabled)
+ {
+ islandsFinished = true;
+ return;
+ }
+ // Detect contours
+ Parallel.ForEach(this,
+ //new ParallelOptions{MaxDegreeOfParallelism = 1},
+ layer =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using (var image = layer.LayerMat)
+ {
+ int step = image.Step;
+ var span = image.GetPixelSpan<byte>();
+
+ // TouchingBounds Checker
+ List<Point> pixels = new List<Point>();
+ 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<LayerIssue>
+ {
+ new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray())
+ });
+ }
+
+
+ if (layer.Index == 0) return; // No islands for layer 0
+
+ VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
+ Mat hierarchy = new Mat();
+
+ if (islandConfig.BinaryThreshold > 0)
+ {
+ using (var thresholdImage = new Mat())
+ {
+ CvInvoke.Threshold(image, thresholdImage, 1, 255, ThresholdType.Binary);
+ CvInvoke.FindContours(thresholdImage, contours, hierarchy, RetrType.Ccomp,
+ ChainApproxMethod.ChainApproxSimple);
+ }
+ }
+ else
+ {
+ 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<byte> previousSpan = 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;
+ var rect = CvInvoke.BoundingRectangle(contours[i]);
+ if (rect.GetArea() < islandConfig.RequiredAreaToProcessCheck)
+ continue;
+
+ if (ReferenceEquals(previousImage, null))
+ {
+ previousImage = this[layer.Index - 1].LayerMat;
+ previousSpan = previousImage.GetPixelSpan<byte>();
+ }
+
+ List<Point> points = new List<Point>();
+ uint pixelsSupportingIsland = 0;
+
+ using (Mat contourImage = image.CloneBlank())
+ {
+ CvInvoke.DrawContours(contourImage, contours, i, new MCvScalar(255), -1);
+ var contourImageSpan = contourImage.GetPixelSpan<byte>();
+
+ for (int y = rect.Y; y < rect.Bottom; y++)
+ {
+ for (int x = rect.X; x < rect.Right; x++)
+ {
+ int pixel = step * y + x;
+ if (span[pixel] < islandConfig.RequiredPixelBrightnessToProcessCheck)
+ 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] >= islandConfig.RequiredPixelBrightnessToSupport)
+ {
+ pixelsSupportingIsland++;
+ }
+ }
+ }
+ }
+
+
+ if (points.Count == 0) continue;
+ if (pixelsSupportingIsland >= islandConfig.RequiredPixelsToSupport)
+ continue; // Not a island, bounding is strong, i think...
+ if (pixelsSupportingIsland > 0 && points.Count < islandConfig.RequiredPixelsToSupport &&
+ 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<LayerIssue> {issue},
+ (layerIndex, list) =>
+ {
+ list.Add(issue);
+ return list;
+ });
+ }
+
+ contours.Dispose();
+ hierarchy.Dispose();
+ previousImage?.Dispose();
+ }
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+ islandsFinished = true;
+ }, () =>
+ {
+ if (!resinTrapConfig.Enabled) return;
+ // Detect contours
+ Parallel.ForEach(this,
+ //new ParallelOptions{MaxDegreeOfParallelism = 1},
+ layer =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using (var image = layer.LayerMat)
+ {
+ if (resinTrapConfig.BinaryThreshold > 0)
+ {
+ CvInvoke.Threshold(image, image, resinTrapConfig.BinaryThreshold, 255, ThresholdType.Binary);
+ }
+
+ var listHollowArea = new List<LayerHollowArea>();
+
+ 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;
+ var rect = CvInvoke.BoundingRectangle(contours[i]);
+ if(rect.GetArea() < resinTrapConfig.RequiredAreaToProcessCheck) continue;
+
+ listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(),
+ rect,
+ layer.Index == Count - 1
+ ? LayerHollowArea.AreaType.Drain
+ : LayerHollowArea.AreaType.Unknown));
+
+ if (listHollowArea.Count > 0)
+ layerHollowAreas.TryAdd(layer.Index, listHollowArea);
+ }
+ }
+ }
+ }
+ });
+
+
+ for (uint layerIndex = 0; layerIndex < Count - 1; layerIndex++) // Last layers, always drains
+ {
+ if (progress.Token.IsCancellationRequested) break;
+ 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 (progress.Token.IsCancellationRequested) return;
+ if (area.Type != LayerHollowArea.AreaType.Unknown) return; // processed, ignore
+ area.Type = LayerHollowArea.AreaType.Trap;
+
+ areaCount++;
+
+ List<LayerHollowArea> linkedAreas = new List<LayerHollowArea>();
+
+ 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<LayerHollowArea> queue = new Queue<LayerHollowArea>();
+ queue.Enqueue(area);
+ area.Processed = false;
+ int nextLayerIndex = (int) layerIndex;
+ while (queue.Count > 0 && area.Type != LayerHollowArea.AreaType.Drain)
+ {
+ if (progress.Token.IsCancellationRequested) return;
+
+ LayerHollowArea checkArea = queue.Dequeue();
+ if (checkArea.Processed) continue;
+ checkArea.Processed = true;
+ nextLayerIndex += dir;
+
+ if (nextLayerIndex < 0 || nextLayerIndex >= Count)
+ break; // Exhausted layers
+ bool haveNextAreas = layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas);
+ Dictionary<int, LayerHollowArea> intersectingAreas = new Dictionary<int, LayerHollowArea>();
+
+ if (islandsFinished)
+ {
+ progress.Reset(OperationProgress.StatusResinTraps, Count, (uint) nextLayerIndex);
+ }
+
+ using (var image = this[nextLayerIndex].LayerMat)
+ {
+ var span = image.GetPixelSpan<byte>();
+ using (var emguImage = image.CloneBlank())
+ {
+ using(var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour)))
+ {
+ CvInvoke.DrawContours(emguImage, vec, -1, new MCvScalar(255), -1);
+ }
+
+ using (var intersectingAreasMat = image.CloneBlank())
+ {
+ if (haveNextAreas)
+ {
+ foreach (var nextArea in nextAreas)
+ {
+ if (!checkArea.BoundingRectangle.IntersectsWith(
+ nextArea.BoundingRectangle)) continue;
+ intersectingAreas.Add(intersectingAreas.Count + 1, nextArea);
+ using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour)))
+ {
+ CvInvoke.DrawContours(intersectingAreasMat, vec, -1,
+ new MCvScalar(intersectingAreas.Count), -1);
+ }
+ }
+ }
+
+ //Debug.WriteLine($"Area Count: {areaCount} | Next Areas: {intersectingAreas.Count} | Layer: {layerIndex} | Next Layer: {nextLayerIndex} | Dir: {dir}");
+
+ bool exitPixelLoop = false;
+ uint blackCount = 0;
+
+ var spanContour = emguImage.GetPixelSpan<byte>();
+ var spanIntersect = intersectingAreasMat.GetPixelSpan<byte>();
+ 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] != 255) continue; // No contour
+ if (span[pixelPos] > resinTrapConfig.MaximumPixelBrightnessToDrain) continue; // Threshold to ignore white area
+ blackCount++;
+
+ if (intersectingAreas.Count > 0) // Have areas, can be on same area path or not
+ {
+ byte i = spanIntersect[pixelPos];
+ if (i == 0 || !intersectingAreas.ContainsKey(i)) // Black pixels
+ 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;
+ exitPixelLoop = true;
+ }
+ else
+ {
+ queue.Enqueue(intersectingAreas[i]);
+ }
+
+ linkedAreas.Add(intersectingAreas[i]);
+ intersectingAreas.Remove(i);
+ if (intersectingAreas.Count == 0) // Intersection areas sweep end, quit this path
+ {
+ exitPixelLoop = true;
+ break;
+ }
+
+ //break;
+
+ // Old Way
+
+ /*foreach (var nextAreaCheck in intersectingAreas)
+ {
+ using (var vec = new VectorOfPoint(nextAreaCheck.Value.Contour))
+ {
+ //Debug.WriteLine(CvInvoke.PointPolygonTest(vec, new PointF(x, y), false));
+ if (CvInvoke.PointPolygonTest(vec, new PointF(x, y), false) < 0) continue;
+ }
+
+ if (nextAreaCheck.Value.Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query
+ {
+ area.Type = LayerHollowArea.AreaType.Drain;
+ exitPixelLoop = true;
+ }
+ else
+ {
+ queue.Enqueue(nextAreaCheck.Value);
+ }
+
+ linkedAreas.Add(nextAreaCheck.Value);
+ intersectingAreas.Remove(nextAreaCheck.Key);
+ if (intersectingAreas.Count == 0)
+ {
+ haveNextAreas = false;
+ exitPixelLoop = true;
+ }
+ //exitPixelLoop = true;
+ break;
+
+ }*/
+ }
+ else if (blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) // 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, resinTrapConfig.RequiredBlackPixelsToDrain))
+ {
+ area.Type = LayerHollowArea.AreaType.Drain;
+ }
+
+ } // Dispose intersecting image
+ } // Dispose emgu image
+ } // Dispose image
+ } // Areas loop
+ } // Dir layer loop
+
+ foreach (var linkedArea in linkedAreas) // Update linked areas
+ {
+ linkedArea.Type = area.Type;
+ }
+ });
+ }
+ });
+
+ if (progress.Token.IsCancellationRequested) return result;
+
+ 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<LayerIssue> {issue}, (u, listIssues) =>
+ {
+ listIssues.Add(issue);
+ return listIssues;
+ });
+ }
+ }
+
+ return result;
+ }
+
+ public void RepairLayers(uint layerStart, uint layerEnd, uint closingIterations = 1, uint openingIterations = 1,
+ bool repairIslands = true, bool repairResinTraps = true, Dictionary<uint, List<LayerIssue>> issues = null,
+ OperationProgress progress = null)
+ {
+ if(ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset(OperationProgress.StatusRepairLayers, layerEnd - layerStart + 1);
+ Parallel.For(layerStart, layerEnd + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ Layer layer = this[layerIndex];
+ using (var image = layer.LayerMat)
+ {
+ if (repairResinTraps && !ReferenceEquals(issues, null))
+ {
+ if (issues.TryGetValue((uint)layerIndex, out var issueList))
+ {
+ foreach (var issue in issueList.Where(issue => issue.Type == LayerIssue.IssueType.ResinTrap))
+ {
+ using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)))
+ {
+ CvInvoke.DrawContours(image,
+ vec,
+ -1,
+ new MCvScalar(255),
+ -1);
+ }
+
+ /*CvInvoke.DrawContours(image,
+ new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)),
+ -1,
+ new MCvScalar(255),
+ 2);*/
+ }
+ }
+ }
+
+ if (repairIslands)
+ {
+ using (Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
+ new Point(-1, -1)))
+ {
+ if (closingIterations > 0)
+ {
+ CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1),
+ (int) closingIterations, BorderType.Default, new MCvScalar());
+ }
+
+ if (openingIterations > 0)
+ {
+ CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1),
+ (int) closingIterations, BorderType.Default, new MCvScalar());
+ }
+ }
+ }
+
+ layer.LayerMat = image;
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ }
+ });
+
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ public void ToolPattern(uint startLayerIndex, uint endLayerIndex, OperationPattern settings, OperationProgress progress = null)
+ {
+ if (ReferenceEquals(progress, null)) progress = new OperationProgress();
+ progress.Reset("Pattern", endLayerIndex - startLayerIndex + 1);
+
+ Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+
+ this[layerIndex].ToolPattern(settings);
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+
+ _boundingRectangle = Rectangle.Empty;
+
+ progress.Token.ThrowIfCancellationRequested();
+
+ if (settings.Anchor == Anchor.None) return;
+ MutateMove(startLayerIndex, endLayerIndex, new OperationMove(BoundingRectangle, 0, 0, settings.Anchor), progress);
+
+ }
+
+ /// <summary>
+ /// Desmodify all layers
+ /// </summary>
+ public void Desmodify()
+ {
+ for (uint i = 0; i < Count; i++)
+ {
+ Layers[i].IsModified = false;
+ }
+ }
+
+ /// <summary>
+ /// Clone this object
+ /// </summary>
+ /// <returns></returns>
+ public LayerManager Clone()
+ {
+ LayerManager layerManager = new LayerManager(Count);
+ foreach (var layer in this)
+ {
+ layerManager[layer.Index] = layer.Clone();
+ }
+
+ return layerManager;
+ }
+
+
+ #endregion
+ }
+}