diff options
Diffstat (limited to 'UVtools.Core/Layer/Layer.cs')
-rw-r--r-- | UVtools.Core/Layer/Layer.cs | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs new file mode 100644 index 0000000..1842d73 --- /dev/null +++ b/UVtools.Core/Layer/Layer.cs @@ -0,0 +1,692 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; +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 +{ + /// <summary> + /// Represent a Layer + /// </summary> + public class Layer : IEquatable<Layer>, IEquatable<uint> + { + #region Properties + + /// <summary> + /// Gets the parent layer manager + /// </summary> + public LayerManager ParentLayerManager { get; set; } + + /// <summary> + /// Gets the number of non zero pixels on this layer image + /// </summary> + public uint NonZeroPixelCount { get; private protected set; } + + /// <summary> + /// Gets the bounding rectangle for the image area + /// </summary> + public Rectangle BoundingRectangle { get; private protected set; } = Rectangle.Empty; + + /// <summary> + /// Gets the layer index + /// </summary> + public uint Index { get; } + + /// <summary> + /// Gets or sets the exposure time in seconds + /// </summary> + public float ExposureTime { get; set; } + + /// <summary> + /// Gets or sets the layer position on Z in mm + /// </summary> + public float PositionZ { get; set; } + + private byte[] _compressedBytes; + /// <summary> + /// Gets or sets layer image compressed data + /// </summary> + public byte[] CompressedBytes + { + get => LayerManager.DecompressLayer(_compressedBytes); + set + { + _compressedBytes = LayerManager.CompressLayer(value); + IsModified = true; + if (!ReferenceEquals(ParentLayerManager, null)) + ParentLayerManager.BoundingRectangle = Rectangle.Empty; + } + } + + /// <summary> + /// Gets the original filename, null if no filename attached with layer + /// </summary> + public string Filename { get; set; } + + /// <summary> + /// Gets if layer has been modified + /// </summary> + public bool IsModified { get; set; } + + /// <summary> + /// Gets or sets a new image instance + /// </summary> + 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(); + + GetBoundingRectangle(value, true); + } + } + } + + /// <summary> + /// Gets a new Brg image instance + /// </summary> + 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) + { + ParentLayerManager = pararentLayerManager; + Index = index; + Filename = filename ?? $"Layer{index}.png"; + CompressedBytes = compressedBytes; + IsModified = false; + /*if (compressedBytes.Length > 0) + { + GetBoundingRectangle(); + }*/ + } + + 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<Layer> + { + 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<Layer> 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 Rectangle GetBoundingRectangle(Mat mat = null, bool reCalculate = false) + { + if (NonZeroPixelCount > 0 && !reCalculate) + { + return BoundingRectangle; + } + bool needDispose = false; + if (ReferenceEquals(mat, null)) + { + mat = LayerMat; + needDispose = true; + } + + using (var nonZeroMat = new Mat()) + { + CvInvoke.FindNonZero(mat, nonZeroMat); + NonZeroPixelCount = (uint)nonZeroMat.Rows / 2; + BoundingRectangle = CvInvoke.BoundingRectangle(nonZeroMat); + } + + + if (needDispose) mat.Dispose(); + + return BoundingRectangle; + } + + 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]; + } + + /// <summary> + /// Gets all islands start pixel location for this layer + /// https://www.geeksforgeeks.org/find-number-of-islands/ + /// </summary> + /// <returns><see cref="List{T}"/> holding all islands coordinates</returns> + public List<LayerIssue> 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<LayerIssue> result = new List<LayerIssue>(); + List<Point> pixels = new List<Point>(); + + + + var mat = LayerMat; + var bytes = mat.GetPixelSpan<byte>(); + + + + 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<Point> queue = new Queue<Point>(); + 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 MutateMove(OperationMove move) + { + using (var layer = LayerMat) + { + if (move.ImageWidth == 0) move.ImageWidth = (uint)layer.Width; + if (move.ImageHeight == 0) move.ImageHeight = (uint)layer.Height; + + /*layer.Transform(1.0, 1.0, move.MarginLeft - move.MarginRight, move.MarginTop-move.MarginBottom); + LayerMat = layer;*/ + using (var layerRoi = new Mat(layer, move.SrcRoi)) + { + using (var dstLayer = layer.CloneBlank()) + { + using (var dstRoi = new Mat(dstLayer, move.DstRoi)) + { + layerRoi.CopyTo(dstRoi); + LayerMat = dstLayer; + } + } + } + } + } + + + public void MutateResize(double xScale, double yScale) + { + using (var mat = LayerMat) + { + mat.TransformFromCenter(xScale, yScale); + LayerMat = mat; + } + } + + public void MutateFlip(FlipType flipType, bool makeCopy = true) + { + using (var mat = LayerMat) + { + if (makeCopy) + { + using (Mat dst = new Mat()) + { + CvInvoke.Flip(mat, dst, flipType); + var spanSrc = mat.GetPixelSpan<byte>(); + var spanDst = dst.GetPixelSpan<byte>(); + for (int i = 0; i < spanSrc.Length; i++) + { + if (spanDst[i] == 0) continue; + spanSrc[i] = spanDst[i]; + } + + LayerMat = mat; + + } + } + else + { + CvInvoke.Flip(mat, mat, flipType); + } + + LayerMat = mat; + } + } + + public void MutateRotate(double angle = 90.0, Inter interpolation = Inter.Linear) + { + using (var mat = LayerMat) + { + var halfWidth = mat.Width / 2.0f; + var halfHeight = mat.Height / 2.0f; + using (var translateTransform = new Matrix<double>(2, 3)) + { + CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), angle, 1.0, translateTransform); + /*var rect = new RotatedRect(PointF.Empty, mat.Size, (float) angle).MinAreaRect(); + translateTransform[0, 2] += rect.Width / 2.0 - mat.Cols / 2.0; + translateTransform[0, 2] += rect.Height / 2.0 - mat.Rows / 2.0;*/ + + /* var abs_cos = Math.Abs(translateTransform[0, 0]); + var abs_sin = Math.Abs(translateTransform[0, 1]); + + var bound_w = mat.Height * abs_sin + mat.Width * abs_cos; + var bound_h = mat.Height * abs_cos + mat.Width * abs_sin; + + translateTransform[0, 2] += bound_w / 2 - halfWidth; + translateTransform[1, 2] += bound_h / 2 - halfHeight;*/ + + + CvInvoke.WarpAffine(mat, mat, translateTransform, mat.Size, interpolation); + } + + 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 void ToolPattern(OperationPattern settings) + { + using (var layer = LayerMat) + { + using (var layerRoi = new Mat(layer, settings.SrcRoi)) + { + using (var dstLayer = layer.CloneBlank()) + { + for (ushort col = 0; col < settings.Cols; col++) + { + for (ushort row = 0; row < settings.Rows; row++) + { + using (var dstRoi = new Mat(dstLayer, settings.GetRoi(col, row))) + { + layerRoi.CopyTo(dstRoi); + } + } + } + + LayerMat = dstLayer; + } + } + } + } + + + + public Layer Clone() + { + return new Layer(Index, CompressedBytes, Filename, ParentLayerManager); + } + + #endregion + } +} |