diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-06-06 03:26:59 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-06-06 03:26:59 +0300 |
commit | 98b7c01df8447fae2fa738c74af52de4e6af6f47 (patch) | |
tree | 5f64c900f9f46123d5f4e28b42021face18deb95 /UVtools.Core | |
parent | 0321ec746db70950aa2e253f0a58a7d73e40aeac (diff) |
v2.13.2v2.13.2
- (Upgrade) AvaloniaUI from 0.10.5 to 0.10.6
- (Add) Pixel editor - Text: Allow multi-line text and line alignment modes
Diffstat (limited to 'UVtools.Core')
28 files changed, 931 insertions, 347 deletions
diff --git a/UVtools.Core/Extensions/DrawingExtensions.cs b/UVtools.Core/Extensions/DrawingExtensions.cs index f98529c..b1d8050 100644 --- a/UVtools.Core/Extensions/DrawingExtensions.cs +++ b/UVtools.Core/Extensions/DrawingExtensions.cs @@ -15,7 +15,7 @@ namespace UVtools.Core.Extensions byte g = (byte)(color.G == 0 ? 0 : Math.Min(Math.Max(min, color.G * factor), max)); - + byte b = (byte)(color.B == 0 ? 0 : Math.Min(Math.Max(min, color.B * factor), max)); return Color.FromArgb(r, g, b); diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 4240372..f4c3e9b 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -13,16 +13,113 @@ using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using UVtools.Core.Objects; namespace UVtools.Core.Extensions { public static class EmguExtensions { + #region Constants public static readonly MCvScalar WhiteColor = new(255, 255, 255, 255); public static readonly MCvScalar BlackColor = new(0, 0, 0, 255); //public static readonly MCvScalar TransparentColor = new(); + #endregion + #region Initializers methods + /// <summary> + /// Create a byte array of size of this <see cref="Mat"/> + /// </summary> + /// <param name="mat"></param> + /// <returns>Blank byte array</returns> + public static byte[] CreateBlankByteArray(this Mat mat) + { + return new byte[mat.GetLength()]; + } + + /// <summary> + /// Creates a new empty <see cref="Mat"/> with same size and type of the source + /// </summary> + /// <param name="mat"></param> + /// <returns></returns> + public static Mat New(this Mat mat) + { + return new(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels); + } + + /// <summary> + /// Creates a new empty <see cref="Mat"/> with same size and type of the source + /// </summary> + /// <param name="src"></param> + /// <param name="color"></param> + /// <returns></returns> + public static Mat New(this Mat src, MCvScalar color) + { + Mat mat = new(src.Rows, src.Cols, src.Depth, src.NumberOfChannels); + mat.SetTo(color); + return mat; + } + + /// <summary> + /// Creates a new blanked (All zeros) <see cref="Mat"/> with same size and type of the source + /// </summary> + /// <param name="mat"></param> + /// <returns>Blanked <see cref="Mat"/></returns> + public static Mat NewBlank(this Mat mat) + { + return InitMat(mat.Size, mat.NumberOfChannels, mat.Depth); + } + + /// <summary> + /// Creates a <see cref="Mat"/> with same size and type of the source and set it to a color + /// </summary> + /// <param name="mat"></param> + /// <param name="color"></param> + /// <returns></returns> + public static Mat NewSetTo(this Mat mat, MCvScalar color) + { + return InitMat(mat.Size, color, mat.NumberOfChannels, mat.Depth); + } + + + public static Mat InitMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U) + { + return size.IsEmpty ? new() : Mat.Zeros(size.Height, size.Width, depthType, channels); + } + + public static Mat InitMat(Size size, MCvScalar color, int channels = 1, DepthType depthType = DepthType.Cv8U) + { + if (size.IsEmpty) return new(); + var mat = new Mat(size, depthType, channels); + mat.SetTo(color); + return mat; + } + + /// <summary> + /// Allocates a new array of mat's + /// </summary> + /// <param name="count">Array size</param> + /// <returns></returns> + public static Mat[] InitMats(uint count) => InitMats(count, Size.Empty); + + /// <summary> + /// Allocates a new array of mat 's + /// </summary> + /// <param name="count">Array size</param> + /// <param name="size">Image size to create, use <see cref="Size.Empty"/> to create a empty Mat</param> + /// <returns>New mat array</returns> + public static Mat[] InitMats(uint count, Size size) + { + var layers = new Mat[count]; + for (var i = 0; i < layers.Length; i++) + { + layers[i] = InitMat(size); + } + + return layers; + } + #endregion + + #region Memory accessors public static unsafe byte* GetBytePointer(this Mat mat) { return (byte*)mat.DataPointer.ToPointer(); @@ -34,46 +131,194 @@ namespace UVtools.Core.Extensions /// <typeparam name="T">Pixel type</typeparam> /// <param name="mat"><see cref="Mat"/> Input</param> /// <returns>A <see cref="Span{T}"/> containing all pixels in data memory</returns> - public static unsafe Span<T> GetPixelSpan<T>(this Mat mat) + public static unsafe Span<T> GetDataSpan<T>(this Mat mat) { return new(mat.DataPointer.ToPointer(), mat.GetLength()); } - public static unsafe Span<byte> GetPixelSpanByte(this Mat mat) + public static unsafe Span<byte> GetDataByteSpan(this Mat mat) { return new(mat.DataPointer.ToPointer(), mat.GetLength()); } - public static unsafe Span<T> GetPixelSpan<T>(this Mat mat, int length, int offset = 0) + public static unsafe Span<T> GetDataSpan<T>(this Mat mat, int length, int offset = 0) { return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length); } - public static Span<T> GetSinglePixelSpan<T>(this Mat mat, int x, int y) + public static Span<T> GetPixelSpan<T>(this Mat mat, int x, int y) { - return mat.GetPixelSpan<T>(mat.NumberOfChannels, mat.GetPixelPos(x, y)); + return mat.GetDataSpan<T>(mat.NumberOfChannels, mat.GetPixelPos(x, y)); } - public static Span<T> GetSinglePixelSpan<T>(this Mat mat, int pos) + public static Span<T> GetPixelSpan<T>(this Mat mat, int pos) { - return mat.GetPixelSpan<T>(mat.NumberOfChannels, pos); + return mat.GetDataSpan<T>(mat.NumberOfChannels, pos); } - public static unsafe Span<T> GetPixelRowSpan<T>(this Mat mat, int y, int length = 0, int offset = 0) + public static unsafe Span<T> GetRowSpan<T>(this Mat mat, int y, int length = 0, int offset = 0) { return new(IntPtr.Add(mat.DataPointer, y * mat.Step + offset).ToPointer(), length <= 0 ? mat.Step : length); - //return mat.GetPixelSpan<T>().Slice(offset, mat.Step); } - public static unsafe Span<T> GetPixelColSpan<T>(this Mat mat, int x, int length = 0, int offset = 0) + public static unsafe Span<T> GetColSpan<T>(this Mat mat, int x, int length = 0, int offset = 0) { var colMat = mat.Col(x); return new(IntPtr.Add(colMat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length); } + #endregion + + #region Get/Set methods + /// <summary> + /// Gets the total length of this <see cref="Mat"/></param> + /// </summary> + /// <param name="mat"></param> + /// <returns>The total length of this <see cref="Mat"/></returns> + public static int GetLength(this Mat mat) + { + return mat.Step * mat.Height; + } + + /// <summary> + /// Gets a pixel index position on a span given X and Y + /// </summary> + /// <param name="mat"></param> + /// <param name="x">X coordinate</param> + /// <param name="y">Y coordinate</param> + /// <returns>The pixel index position</returns> + public static int GetPixelPos(this Mat mat, int x, int y) + { + return y * mat.Step + x * mat.NumberOfChannels; + } + + /// <summary> + /// Gets a pixel index position on a span given X and Y + /// </summary> + /// <param name="mat"></param> + /// <param name="point">X and Y Location</param> + /// <returns>The pixel index position</returns> + public static int GetPixelPos(this Mat mat, Point point) + { + return point.Y * mat.Step + point.X * mat.NumberOfChannels; + } /// <summary> - /// Gets if a <see cref="Mat"/> is all zeroed + /// Gets a byte array copy of this <see cref="Mat"/> + /// </summary> + /// <param name="mat"></param> + /// <returns>Byte array </returns> + public static byte[] GetBytes(this Mat mat) + { + var data = new byte[mat.GetLength()]; + Marshal.Copy(mat.DataPointer, data, 0, data.Length); + return data; + } + + public static byte GetByte(this Mat mat, int pos) + { + //return new Span<byte>(IntPtr.Add(mat.DataPointer, pos).ToPointer(), mat.Step)[0]; + var value = new byte[1]; + Marshal.Copy(mat.DataPointer + pos, value, 0, value.Length); + return value[0]; + } + + public static byte GetByte(this Mat mat, int x, int y) => GetByte(mat, mat.GetPixelPos(x, y)); + public static byte GetByte(this Mat mat, Point pos) => GetByte(mat, mat.GetPixelPos(pos.X, pos.Y)); + + + public static void SetByte(this Mat mat, int pixel, byte value) => SetByte(mat, pixel, new[] { value }); + + public static void SetByte(this Mat mat, int pixel, byte[] value) => + Marshal.Copy(value, 0, mat.DataPointer + pixel, value.Length); + + public static void SetByte(this Mat mat, int x, int y, byte value) => + SetByte(mat, x, y, new[] { value }); + + public static void SetByte(this Mat mat, int x, int y, byte[] value) => + SetByte(mat, y * mat.Step + x * mat.NumberOfChannels, value); + + public static void SetBytes(this Mat mat, byte[] value) => + Marshal.Copy(value, 0, mat.DataPointer, value.Length); + + public static byte[] GetPngByes(this Mat mat) + { + using var vector = new VectorOfByte(); + CvInvoke.Imencode(".png", mat, vector); + return vector.ToArray(); + } + #endregion + + #region Copy methods + /// <summary> + /// Copy a <see cref="Mat"/> to center of other <see cref="Mat"/> + /// </summary> + /// <param name="src">Source <see cref="Mat"/> to be copied to</param> + /// <param name="dst">Target <see cref="Mat"/> to paste the <param name="src"></param></param> + public static void CopyToCenter(this Mat src, Mat dst) + { + if (dst.Step > src.Step && dst.Height > src.Height) + { + var dx = Math.Abs(dst.Step - src.Step) / 2; + var dy = Math.Abs(dst.Height - src.Height) / 2; + Mat m = new(dst, new Rectangle(dx, dy, src.Width, src.Height)); + src.CopyTo(m); + } + else if (dst.Step < src.Step && dst.Height < src.Height) + { + var dx = Math.Abs(dst.Step - src.Step) / 2; + var dy = Math.Abs(dst.Height - src.Height) / 2; + Mat m = new(src, new Rectangle(dx, dy, dst.Width, dst.Height)); + m.CopyTo(dst); + } + } + #endregion + + #region Roi methods + + /// <summary> + /// Gets a Roi, but return source when roi is empty or have same size as source + /// </summary> + /// <param name="mat"></param> + /// <param name="roi"></param> + /// <returns></returns> + public static Mat Roi(this Mat mat, Rectangle roi) + { + return roi.IsEmpty || roi.Size == mat.Size ? mat : new Mat(mat, roi); + } + + /// <summary> + /// Gets a new mat obtained from it center at a target size and roi + /// </summary> + /// <param name="mat"></param> + /// <param name="targetSize"></param> + /// <param name="roi"></param> + /// <returns></returns> + public static Mat NewRoiFromCenter(this Mat mat, Size targetSize, Rectangle roi) + { + if (targetSize == mat.Size) return mat.Clone(); + var newMat = InitMat(targetSize); + + var roiMat = new Mat(mat, roi); + + + //int xStart = mat.Width / 2 - targetSize.Width / 2; + //int yStart = mat.Height / 2 - targetSize.Height / 2; + + var newMatRoi = new Mat(newMat, new Rectangle( + targetSize.Width / 2 - roi.Width / 2, + targetSize.Height / 2 - roi.Height / 2, + roi.Width, + roi.Height + )); + roiMat.CopyTo(newMatRoi); + return newMat; + } + #endregion + + #region Is methods + /// <summary> + /// Gets if a <see cref="Mat"/> is all zeroed by a threshold /// </summary> /// <param name="mat"></param> /// <param name="threshold">Pixel brightness threshold</param> @@ -81,14 +326,16 @@ namespace UVtools.Core.Extensions public static unsafe bool IsZeroed(this Mat mat, byte threshold = 0) { var ptr = mat.GetBytePointer(); - for (int i = 0; i < mat.GetLength(); i++) + var length = mat.GetLength(); + for (var i = 0; i < length; i++) { if (ptr[i] > threshold) return false; } return true; } + #endregion - + #region Transform methods public static void Transform(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear) { //var dst = new Mat(src.Size, src.Depth, src.NumberOfChannels); @@ -106,7 +353,8 @@ namespace UVtools.Core.Extensions /// Rotates a Mat by an angle while keeping the image size /// </summary> /// <param name="src"></param> - /// <param name="angle"></param> + /// <param name="angle">Angle in degrees to rotate</param> + /// <param name="newSize"></param> /// <param name="scale"></param> public static void Rotate(this Mat src, double angle, Size newSize = default, double scale = 1.0) => Rotate(src, src, angle, newSize, scale); @@ -194,13 +442,145 @@ namespace UVtools.Core.Extensions xTrans + (src.Width - src.Width * xScale) / 2.0, yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation); } + #endregion + + #region Text methods + public enum PutTextLineAlignment : byte + { + /// <summary> + /// Left aligned without trimming, openCV default call + /// </summary> + None, + + /// <summary> + /// Left aligned and trimmed + /// </summary> + Left, + + /// <summary> + /// Center aligned and trimmed + /// </summary> + Center, + + /// <summary> + /// Right aligned and trimmed + /// </summary> + Right + } + + public static string PutTextLineAlignmentTrim(string line, PutTextLineAlignment lineAlignment) + { + switch (lineAlignment) + { + case PutTextLineAlignment.None: + return line.TrimEnd(); + case PutTextLineAlignment.Left: + case PutTextLineAlignment.Center: + case PutTextLineAlignment.Right: + return line.Trim(); + default: + throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null); + } + } + + public static Size GetTextSizeExtended(string text, FontFace fontFace, double fontScale, int thickness, ref int baseLine, PutTextLineAlignment lineAlignment = default) + { + text = text.TrimEnd('\n', '\r', ' '); + var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None); + var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine); + + if (lines.Length is 0 or 1) return textSize; + + var lineGap = textSize.Height / 3; + var width = 0; + var height = lines.Length * (lineGap + textSize.Height) - lineGap; + + for (var i = 0; i < lines.Length; i++) + { + lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment); + + if (string.IsNullOrWhiteSpace(lines[i])) continue; + int baseLineRef = 0; + var lineSize = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef); + width = Math.Max(width, lineSize.Width); + } + + + return new(width, height); + } + + /// <summary> + /// Extended OpenCV PutText to accepting line breaks and line alignment + /// </summary> + public static void PutTextExtended(this Mat src, string text, Point org, FontFace fontFace, double fontScale, + MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default) + { + text = text.TrimEnd('\n', '\r', ' '); + var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None); + + switch (lines.Length) + { + case 0: + return; + case 1: + CvInvoke.PutText(src, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin); + return; + } + + // Get height of text lines in pixels (height of all lines is the same) + int baseLine = 0; + var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine); + var lineGap = textSize.Height / 3; + var linesSize = new Size[lines.Length]; + int width = 0; + + // Sanitize lines + for (var i = 0; i < lines.Length; i++) + { + lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment); + } + + // If line needs alignment, calculate the size for each line + if (lineAlignment is not PutTextLineAlignment.Left and not PutTextLineAlignment.None) + { + for (var i = 0; i < lines.Length; i++) + { + if (string.IsNullOrWhiteSpace(lines[i])) continue; + int baseLineRef = 0; + linesSize[i] = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef); + width = Math.Max(width, linesSize[i].Width); + } + } + + for (var i = 0; i < lines.Length; i++) + { + if(string.IsNullOrWhiteSpace(lines[i])) continue; + + int x = lineAlignment switch + { + PutTextLineAlignment.None or PutTextLineAlignment.Left => org.X, + PutTextLineAlignment.Center => org.X + (width - linesSize[i].Width) / 2, + PutTextLineAlignment.Right => org.X + width - linesSize[i].Width, + _ => throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null) + }; + + // Find total size of text block before this line + var lineYAdjustment = i * (lineGap + textSize.Height); + // Move text down from original line based on line number + int lineY = !bottomLeftOrigin ? org.Y + lineYAdjustment : org.Y - lineYAdjustment; + CvInvoke.PutText(src, lines[i], new Point(x, lineY), fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin); + } + } + /// <summary> + /// Extended OpenCV PutText to accepting line breaks, line alignment and rotation + /// </summary> public static void PutTextRotated(this Mat src, string text, Point org, FontFace fontFace, double fontScale, MCvScalar color, - int thickness = 1, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, double angle = 0) + int thickness = 1, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default, double angle = 0) { if (angle % 360 == 0) // No rotation needed, cheaper cycle { - CvInvoke.PutText(src, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin); + src.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin, lineAlignment); return; } @@ -209,17 +589,19 @@ namespace UVtools.Core.Extensions var sizeDifference = (rotatedSrc.Size - src.Size).Half(); org.Offset(sizeDifference.ToPoint()); org = org.Rotate(-angle, new Point(rotatedSrc.Size.Width / 2, rotatedSrc.Size.Height / 2)); - CvInvoke.PutText(rotatedSrc, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin); + rotatedSrc.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin, lineAlignment); - using var mask = rotatedSrc.CloneBlank(); - CvInvoke.PutText(mask, text, org, fontFace, fontScale, WhiteColor, thickness, lineType, bottomLeftOrigin); + using var mask = rotatedSrc.NewBlank(); + mask.PutTextExtended(text, org, fontFace, fontScale, WhiteColor, thickness, lineType, bottomLeftOrigin, lineAlignment); rotatedSrc.Rotate(angle, src.Size); mask.Rotate(angle, src.Size); rotatedSrc.CopyTo(src, mask); } + #endregion + #region Utilities methods /// <summary> /// Determine the area (i.e. total number of pixels in the image), /// initialize the output skeletonized image, and construct the @@ -233,7 +615,7 @@ namespace UVtools.Core.Extensions { if (ksize.IsEmpty) ksize = new Size(3, 3); Point anchor = new(-1, -1); - var skeleton = src.CloneBlank(); + var skeleton = src.NewBlank(); using var kernel = CvInvoke.GetStructuringElement(elementShape, ksize, anchor); var image = src; @@ -262,7 +644,7 @@ namespace UVtools.Core.Extensions return skeleton; } - + /// <summary> /// Determine the area (i.e. total number of pixels in the image), /// initialize the output skeletonized image, and construct the @@ -273,220 +655,6 @@ namespace UVtools.Core.Extensions /// <param name="elementShape"></param> public static Mat Skeletonize(this Mat src, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle) => src.Skeletonize(out _, ksize, elementShape); - - /// <summary> - /// Copy a <see cref="Mat"/> to center of other <see cref="Mat"/> - /// </summary> - /// <param name="src">Source <see cref="Mat"/> to be copied to</param> - /// <param name="dst">Target <see cref="Mat"/> to paste the <param name="src"></param></param> - public static void CopyToCenter(this Mat src, Mat dst) - { - if (dst.Step > src.Step && dst.Height > src.Height) - { - var dx = Math.Abs(dst.Step - src.Step) / 2; - var dy = Math.Abs(dst.Height - src.Height) / 2; - Mat m = new(dst, new Rectangle(dx, dy, src.Width, src.Height)); - src.CopyTo(m); - } - else if (dst.Step < src.Step && dst.Height < src.Height) - { - var dx = Math.Abs(dst.Step - src.Step) / 2; - var dy = Math.Abs(dst.Height - src.Height) / 2; - Mat m = new(src, new Rectangle(dx, dy, dst.Width, dst.Height)); - m.CopyTo(dst); - } - } - - /// <summary> - /// Gets the total length of this <see cref="Mat"/></param> - /// </summary> - /// <param name="mat"></param> - /// <returns>The total length of this <see cref="Mat"/></returns> - public static int GetLength(this Mat mat) - { - return mat.Step * mat.Height; - } - - /// <summary> - /// Gets a pixel index position on a span given X and Y - /// </summary> - /// <param name="mat"></param> - /// <param name="x">X coordinate</param> - /// <param name="y">Y coordinate</param> - /// <returns>The pixel index position</returns> - public static int GetPixelPos(this Mat mat, int x, int y) - { - return y * mat.Step + x * mat.NumberOfChannels; - } - - /// <summary> - /// Gets a pixel index position on a span given X and Y - /// </summary> - /// <param name="mat"></param> - /// <param name="point">X and Y Location</param> - /// <returns>The pixel index position</returns> - public static int GetPixelPos(this Mat mat, Point point) - { - return point.Y * mat.Step + point.X * mat.NumberOfChannels; - } - - /// <summary> - /// Gets a byte array copy of this <see cref="Mat"/> - /// </summary> - /// <param name="mat"></param> - /// <returns>Byte array </returns> - public static byte[] GetBytes(this Mat mat) - { - byte[] data = new byte[mat.GetLength()]; - Marshal.Copy(mat.DataPointer, data, 0, data.Length); - return data; - - /*GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); - using (Mat m2 = new Mat(mat.Size, mat.Depth, mat.NumberOfChannels, handle.AddrOfPinnedObject(), - mat.Width * mat.NumberOfChannels)) - { - mat.CopyTo(m2); - } - handle.Free();*/ - - } - - /// <summary> - /// Create a byte array of size of this <see cref="Mat"/> - /// </summary> - /// <param name="mat"></param> - /// <returns>Blank byte array</returns> - public static byte[] CreateByteArray(this Mat mat) - { - return new byte[mat.GetLength()]; - } - - public static Mat New(this Mat mat) - { - return new(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels); - } - - public static Mat New(this Mat src, MCvScalar color) - { - Mat mat = new(src.Rows, src.Cols, src.Depth, src.NumberOfChannels); - mat.SetTo(color); - return mat; - } - - /// <summary> - /// Clone this <see cref="Mat"/> blanked (All zeros) - /// </summary> - /// <param name="mat"></param> - /// <returns>Blanked <see cref="Mat"/></returns> - public static Mat CloneBlank(this Mat mat) - { - return InitMat(mat.Size, mat.NumberOfChannels, mat.Depth); - } - - public static byte GetByte(this Mat mat, int pos) - { - //return new Span<byte>(IntPtr.Add(mat.DataPointer, pos).ToPointer(), mat.Step)[0]; - var value = new byte[1]; - Marshal.Copy(mat.DataPointer + pos, value, 0, value.Length); - return value[0]; - } - - public static byte GetByte(this Mat mat, int x, int y) => GetByte(mat, mat.GetPixelPos(x, y)); - public static byte GetByte(this Mat mat, Point pos) => GetByte(mat, mat.GetPixelPos(pos.X, pos.Y)); - - - public static void SetByte(this Mat mat, int pixel, byte value) => SetByte(mat, pixel, new[] {value}); - - public static void SetByte(this Mat mat, int pixel, byte[] value) => - Marshal.Copy(value, 0, mat.DataPointer + pixel, value.Length); - - public static void SetByte(this Mat mat, int x, int y, byte value) => - SetByte(mat, x, y, new[] {value}); - - public static void SetByte(this Mat mat, int x, int y, byte[] value) => - SetByte(mat, y * mat.Step + x * mat.NumberOfChannels, value); - - public static void SetBytes(this Mat mat, byte[] value) => - Marshal.Copy(value, 0, mat.DataPointer, value.Length); - - public static Mat InitMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U) - { - return Mat.Zeros(size.Height, size.Width, depthType, channels); - /*var mat = new Mat(size, depthType, channels); - switch (channels) - { - case 1: - mat.SetTo(BlackByte); - break; - case 3: - mat.SetTo(Black3Byte); - break; - case 4: - mat.SetTo(Transparent4Byte); - break; - } - - return mat;*/ - } - - public static Mat InitMat(Size size, MCvScalar scalar, int channels = 1, DepthType depthType = DepthType.Cv8U) - { - var mat = new Mat(size, depthType, channels); - mat.SetTo(scalar); - return mat; - } - - public static Mat RoiFromCenter(this Mat mat, Size targetSize, Rectangle roi) - { - if (targetSize == mat.Size) return mat; - var newMat = InitMat(targetSize); - - var roiMat = new Mat(mat, roi); - - - //int xStart = mat.Width / 2 - targetSize.Width / 2; - //int yStart = mat.Height / 2 - targetSize.Height / 2; - - var newMatRoi = new Mat(newMat, new Rectangle( - targetSize.Width / 2 - roi.Width / 2, - targetSize.Height / 2 - roi.Height / 2, - roi.Width, - roi.Height - )); - roiMat.CopyTo(newMatRoi); - return newMat; - } - - /// <summary> - /// Allocates a new array of mat 's - /// </summary> - /// <param name="count">Array size</param> - /// <returns></returns> - public static Mat[] Allocate(uint count) => Allocate(count, Size.Empty); - - /// <summary> - /// Allocates a new array of mat 's - /// </summary> - /// <param name="count">Array size</param> - /// <param name="size">Image size to create, use <see cref="Size.Empty"/> to create a empty Mat</param> - /// <returns>New mat array</returns> - public static Mat[] Allocate(uint count, Size size) - { - var layers = new Mat[count]; - for (var i = 0; i < layers.Length; i++) - { - layers[i] = size == Size.Empty ? new Mat() : InitMat(size); - } - - return layers; - } - - public static byte[] GetPngByes(this Mat mat) - { - using var vector = new VectorOfByte(); - CvInvoke.Imencode(".png", mat, vector); - return vector.ToArray(); - } - + #endregion } } diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index 41cfe5f..2a6da89 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -617,8 +617,8 @@ namespace UVtools.Core.FileFormats using (var mat = layer.LayerMat) using (var matEncode = new Mat(mat.Height, mat.Step / 3, DepthType.Cv8U, 3)) { - var span = mat.GetPixelSpan<byte>(); - var spanEncode = matEncode.GetPixelSpan<byte>(); + var span = mat.GetDataSpan<byte>(); + var spanEncode = matEncode.GetDataSpan<byte>(); for (int i = 0; i < span.Length; i++) { spanEncode[i] = span[i]; @@ -815,8 +815,8 @@ namespace UVtools.Core.FileFormats using Mat mat = new(); CvInvoke.Imdecode(layer.CompressedBytes, ImreadModes.Color, mat); using Mat matDecode = new(mat.Height, mat.Step, DepthType.Cv8U, 1); - var span = mat.GetPixelSpan<byte>(); - var spanDecode = matDecode.GetPixelSpan<byte>(); + var span = mat.GetDataSpan<byte>(); + var spanDecode = matDecode.GetDataSpan<byte>(); for (int i = 0; i < span.Length; i++) { spanDecode[i] = span[i]; diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs index 0c4129e..305cd08 100644 --- a/UVtools.Core/FileFormats/CXDLPFile.cs +++ b/UVtools.Core/FileFormats/CXDLPFile.cs @@ -486,7 +486,7 @@ namespace UVtools.Core.FileFormats { if (progress.Token.IsCancellationRequested) return; if (Thumbnails[previewIndex] is null) return; - var span = Thumbnails[previewIndex].GetPixelSpanByte(); + var span = Thumbnails[previewIndex].GetDataByteSpan(); int index = 0; for (int i = 0; i < span.Length; i += 3) { @@ -531,7 +531,7 @@ namespace UVtools.Core.FileFormats List<LayerLine> layerLines = new(); var layer = this[layerIndex]; using var mat = layer.LayerMat; - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) { @@ -598,7 +598,7 @@ namespace UVtools.Core.FileFormats Parallel.For(0, previews.Length, previewIndex => { var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); int spanIndex = 0; for (int i = 0; i < previews[previewIndex].Length; i += 2) diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs index 57b5ff6..82c8422 100644 --- a/UVtools.Core/FileFormats/FDGFile.cs +++ b/UVtools.Core/FileFormats/FDGFile.cs @@ -555,7 +555,7 @@ namespace UVtools.Core.FileFormats //int pixel = 0; for (int y = 0; y < image.Height; y++) { - var span = image.GetPixelRowSpan<byte>(y); + var span = image.GetRowSpan<byte>(y); for (int x = 0; x < span.Length; x++) { diff --git a/UVtools.Core/FileFormats/GR1File.cs b/UVtools.Core/FileFormats/GR1File.cs index 7a227b1..da26561 100644 --- a/UVtools.Core/FileFormats/GR1File.cs +++ b/UVtools.Core/FileFormats/GR1File.cs @@ -346,7 +346,7 @@ namespace UVtools.Core.FileFormats { if (progress.Token.IsCancellationRequested) return; if (Thumbnails[previewIndex] is null) return; - var span = Thumbnails[previewIndex].GetPixelSpanByte(); + var span = Thumbnails[previewIndex].GetDataByteSpan(); int index = 0; for (int i = 0; i < span.Length; i += 3) { @@ -381,7 +381,7 @@ namespace UVtools.Core.FileFormats List<LayerLine> layerLines = new(); var layer = this[layerIndex]; using var mat = layer.LayerMat; - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) { @@ -451,7 +451,7 @@ namespace UVtools.Core.FileFormats Parallel.For(0, previews.Length, previewIndex => { var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); int spanIndex = 0; for (int i = 0; i < previews[previewIndex].Length; i += 2) diff --git a/UVtools.Core/FileFormats/MDLPFile.cs b/UVtools.Core/FileFormats/MDLPFile.cs index 73af073..0b86e11 100644 --- a/UVtools.Core/FileFormats/MDLPFile.cs +++ b/UVtools.Core/FileFormats/MDLPFile.cs @@ -312,7 +312,7 @@ namespace UVtools.Core.FileFormats { if (progress.Token.IsCancellationRequested) return; if (Thumbnails[previewIndex] is null) return; - var span = Thumbnails[previewIndex].GetPixelSpanByte(); + var span = Thumbnails[previewIndex].GetDataByteSpan(); int index = 0; for (int i = 0; i < span.Length; i += 3) { @@ -347,7 +347,7 @@ namespace UVtools.Core.FileFormats List<LayerLine> layerLines = new(); var layer = this[layerIndex]; using var mat = layer.LayerMat; - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) { @@ -416,7 +416,7 @@ namespace UVtools.Core.FileFormats Parallel.For(0, previews.Length, previewIndex => { var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); - var span = mat.GetPixelSpanByte(); + var span = mat.GetDataByteSpan(); int spanIndex = 0; for (int i = 0; i < previews[previewIndex].Length; i += 2) diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index 6ede707..92cac2b 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -572,7 +572,7 @@ namespace UVtools.Core.FileFormats //int pixel = 0; for (int y = 0; y < image.Height; y++) { - var span = image.GetPixelRowSpan<byte>(y); + var span = image.GetRowSpan<byte>(y); for (int x = 0; x < span.Length; x++) { diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs index c1e2fc9..fb58af7 100644 --- a/UVtools.Core/Layer/Layer.cs +++ b/UVtools.Core/Layer/Layer.cs @@ -644,7 +644,7 @@ namespace UVtools.Core var mat = LayerMat; - var bytes = mat.GetPixelSpan<byte>(); + var bytes = mat.GetDataSpan<byte>(); diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 773e728..054807e 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -997,7 +997,7 @@ namespace UVtools.Core using (var image = layer.LayerMat) { int step = image.Step; - var span = image.GetPixelSpan<byte>(); + var span = image.GetDataSpan<byte>(); if (touchBoundConfig.Enabled) { @@ -1138,7 +1138,7 @@ namespace UVtools.Core //stats[i][3]: Height of Connected Component //stats[i][4]: Total Area (in pixels) in Connected Component - var labelSpan = labels.GetPixelSpan<int>(); + var labelSpan = labels.GetDataSpan<int>(); for (int i = 1; i < numLabels; i++) { @@ -1154,7 +1154,7 @@ namespace UVtools.Core if (previousImage is null) { previousImage = this[layer.Index - 1].LayerMat; - previousSpan = previousImage.GetPixelSpan<byte>(); + previousSpan = previousImage.GetDataSpan<byte>(); } List<Point> points = new(); @@ -1227,7 +1227,7 @@ namespace UVtools.Core anchor, overhangConfig.ErodeIterations, BorderType.Default, new MCvScalar()); - var subtractedSpan = subtractedImage.GetPixelSpan<byte>(); + var subtractedSpan = subtractedImage.GetDataSpan<byte>(); for (int y = 0; y < subtractedImage.Height; y++) for (int x = 0; x < subtractedImage.Step; x++) @@ -1426,8 +1426,8 @@ namespace UVtools.Core using (var image = this[nextLayerIndex].LayerMat) { - var span = image.GetPixelSpan<byte>(); - using (var emguImage = image.CloneBlank()) + var span = image.GetDataSpan<byte>(); + using (var emguImage = image.NewBlank()) { using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) @@ -1435,7 +1435,7 @@ namespace UVtools.Core CvInvoke.DrawContours(emguImage, vec, -1, EmguExtensions.WhiteColor, -1); } - using (var intersectingAreasMat = image.CloneBlank()) + using (var intersectingAreasMat = image.NewBlank()) { if (haveNextAreas) { @@ -1455,8 +1455,8 @@ namespace UVtools.Core bool exitPixelLoop = false; uint blackCount = 0; - var spanContour = emguImage.GetPixelSpan<byte>(); - var spanIntersect = intersectingAreasMat.GetPixelSpan<byte>(); + var spanContour = emguImage.GetDataSpan<byte>(); + var spanIntersect = intersectingAreasMat.GetDataSpan<byte>(); for (int y = checkArea.BoundingRectangle.Y; y < checkArea.BoundingRectangle.Bottom && area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; @@ -1638,7 +1638,7 @@ namespace UVtools.Core var operationText = (PixelText)operation; var mat = modifiedLayers.GetOrAdd(operation.LayerIndex, u => this[operation.LayerIndex].LayerMat); - mat.PutTextRotated(operationText.Text, operationText.Location, operationText.Font, operationText.FontScale, new MCvScalar(operationText.Brightness), operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.Angle); + mat.PutTextRotated(operationText.Text, operationText.Location, operationText.Font, operationText.FontScale, new MCvScalar(operationText.Brightness), operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.LineAlignment, operationText.Angle); } else if (operation.OperationType == PixelOperation.PixelOperationType.Eraser) { @@ -1679,7 +1679,7 @@ namespace UVtools.Core using (Mat matCircleRoi = new(mat, new Rectangle(xStart, yStart, operationSupport.TipDiameter, operationSupport.TipDiameter))) { - using (Mat matCircleMask = matCircleRoi.CloneBlank()) + using (Mat matCircleMask = matCircleRoi.NewBlank()) { CvInvoke.Circle(matCircleMask, new Point(operationSupport.TipDiameter / 2, operationSupport.TipDiameter / 2), operationSupport.TipDiameter / 2, new MCvScalar(operation.PixelBrightness), -1); @@ -1717,7 +1717,7 @@ namespace UVtools.Core using (Mat matCircleRoiInv = new()) { CvInvoke.Threshold(matCircleRoi, matCircleRoiInv, 100, 255, ThresholdType.BinaryInv); - using (Mat matCircleMask = matCircleRoi.CloneBlank()) + using (Mat matCircleMask = matCircleRoi.NewBlank()) { CvInvoke.Circle(matCircleMask, new Point(radius, radius), radius, new MCvScalar(255), -1); CvInvoke.BitwiseAnd(matCircleRoiInv, matCircleMask, matCircleMask); diff --git a/UVtools.Core/Objects/StaticObjects.cs b/UVtools.Core/Objects/StaticObjects.cs index 5f4dbf4..4be5b4e 100644 --- a/UVtools.Core/Objects/StaticObjects.cs +++ b/UVtools.Core/Objects/StaticObjects.cs @@ -13,10 +13,8 @@ namespace UVtools.Core.Objects // Compute the file's hash. public static byte[] GetHashSha256(string filename) { - using (var stream = File.OpenRead(filename)) - { - return Sha256.ComputeHash(stream); - } + using var stream = File.OpenRead(filename); + return Sha256.ComputeHash(stream); } } } diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs index 8903780..2c3bc0a 100644 --- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs +++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs @@ -640,7 +640,7 @@ namespace UVtools.Core.Operations using (var erode = new Mat()) using (var diff = new Mat()) using (var target = new Mat()) - using (var mask = shape.CloneBlank()) + using (var mask = shape.NewBlank()) { mask.SetTo(new MCvScalar(byte.MaxValue-brightness)); CvInvoke.Erode(shape, erode, kernel, anchor, DimmingWallThickness, BorderType.Reflect101, default); diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs index 8430fe5..2ce35f2 100644 --- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs +++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs @@ -1237,7 +1237,7 @@ namespace UVtools.Core.Operations layers[0] = EmguExtensions.InitMat(rect.Size); CvInvoke.Rectangle(layers[0], rect, EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); - layers[1] = layers[0].CloneBlank(); + layers[1] = layers[0].NewBlank(); if (holes.Length > 0) { CvInvoke.Rectangle(layers[1], @@ -1666,7 +1666,7 @@ namespace UVtools.Core.Operations { var newLayer = layer.Clone(); newLayer.ExposureTime = (float)(newLayer.IsBottomLayer ? group.Key.BottomExposure : group.Key.Exposure); - using var newMat = mat.CloneBlank(); + using var newMat = mat.NewBlank(); foreach (var brightness in brightnesses) { ExposureItem item = new(group.Key.LayerHeight, group.Key.BottomExposure, group.Key.Exposure, brightness); diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs index f9f5fa2..058b7c6 100644 --- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs +++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs @@ -314,7 +314,7 @@ namespace UVtools.Core.Operations { var layers = new Mat[LayerCount]; - Slicer slicer = new(SlicerFile.Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); + Slicer.Slicer slicer = new(SlicerFile.Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); Point center = new(SlicerFile.Resolution.Width / 2, SlicerFile.Resolution.Height / 2); uint baseRadius = slicer.PixelsFromMillimeters(_baseDiameter) / 2; uint baseLayers = (ushort) Math.Floor(_baseHeight / _layerHeight); diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs index 01b3ae9..0793da7 100644 --- a/UVtools.Core/Operations/OperationInfill.cs +++ b/UVtools.Core/Operations/OperationInfill.cs @@ -171,7 +171,7 @@ namespace UVtools.Core.Operations or InfillAlgorithm.CubicInterlinked) { using var infillPattern = EmguExtensions.InitMat(new Size(_infillSpacing, _infillSpacing)); - using var matPattern = mat.CloneBlank(); + using var matPattern = mat.NewBlank(); bool firstPattern = true; uint accumulator = 0; bool dynamicCenter = false; diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index 5f10340..035ec2e 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -381,7 +381,7 @@ namespace UVtools.Core.Operations } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.RoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); + using var layerRoi = layer.NewRoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; @@ -396,7 +396,7 @@ namespace UVtools.Core.Operations } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.RoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); + using var layerRoi = layer.NewRoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; } diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs index b5a0bc8..95dc08d 100644 --- a/UVtools.Core/Operations/OperationPattern.cs +++ b/UVtools.Core/Operations/OperationPattern.cs @@ -294,7 +294,7 @@ namespace UVtools.Core.Operations using var mat = SlicerFile[layerIndex].LayerMat; using var layerRoi = new Mat(mat, ROI); - using var dstLayer = mat.CloneBlank(); + using var dstLayer = mat.NewBlank(); for (ushort col = 0; col < Cols; col++) for (ushort row = 0; row < Rows; row++) { diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs index f441064..7e86554 100644 --- a/UVtools.Core/Operations/OperationPixelDimming.cs +++ b/UVtools.Core/Operations/OperationPixelDimming.cs @@ -622,8 +622,8 @@ namespace UVtools.Core.Operations AlternatePattern ??= Pattern; using var blankMat = EmguExtensions.InitMat(SlicerFile.Resolution); - using var matPattern = blankMat.CloneBlank(); - using var matAlternatePattern = blankMat.CloneBlank(); + using var matPattern = blankMat.NewBlank(); + using var matAlternatePattern = blankMat.NewBlank(); var target = GetRoiOrDefault(blankMat); CvInvoke.Repeat(Pattern, target.Rows / Pattern.Rows + 1, target.Cols / Pattern.Cols + 1, matPattern); diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs index 34b30e4..1e25b10 100644 --- a/UVtools.Core/Operations/OperationRedrawModel.cs +++ b/UVtools.Core/Operations/OperationRedrawModel.cs @@ -191,9 +191,9 @@ namespace UVtools.Core.Operations if (contours.Size <= 0) return; using var nextLayerMat = otherFile[layerIndex + 1].LayerMat; using var nextLayerMatRoi = GetRoiOrDefault(nextLayerMat); - var fullSpan = fullMatRoi.GetPixelSpan<byte>(); - var supportsSpan = supportsMat.GetPixelSpan<byte>(); - var nextSpan = nextLayerMatRoi.GetPixelSpan<byte>(); + var fullSpan = fullMatRoi.GetDataSpan<byte>(); + var supportsSpan = supportsMat.GetDataSpan<byte>(); + var nextSpan = nextLayerMatRoi.GetDataSpan<byte>(); for (int i = 0; i < contours.Size; i++) { var foundContour = false; diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs index 3e9c29d..0eb6318 100644 --- a/UVtools.Core/Operations/OperationRepairLayers.cs +++ b/UVtools.Core/Operations/OperationRepairLayers.cs @@ -175,7 +175,7 @@ namespace UVtools.Core.Operations if (progress.Token.IsCancellationRequested) return; Layer layer = SlicerFile[group.Key]; Mat image = layer.LayerMat; - Span<byte> bytes = image.GetPixelSpan<byte>(); + Span<byte> bytes = image.GetDataSpan<byte>(); foreach (var issue in group) { foreach (var issuePixel in issue.Pixels) @@ -225,7 +225,7 @@ namespace UVtools.Core.Operations initImage(); if (bytes == null) - bytes = image.GetPixelSpan<byte>(); + bytes = image.GetDataSpan<byte>(); foreach (var issuePixel in issue.Pixels) { diff --git a/UVtools.Core/PixelEditor/PixelText.cs b/UVtools.Core/PixelEditor/PixelText.cs index 5a420a9..f3179dd 100644 --- a/UVtools.Core/PixelEditor/PixelText.cs +++ b/UVtools.Core/PixelEditor/PixelText.cs @@ -9,6 +9,7 @@ using System; using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; namespace UVtools.Core.PixelEditor { @@ -19,6 +20,7 @@ namespace UVtools.Core.PixelEditor private ushort _thickness = 1; private string _text; private bool _mirror; + private EmguExtensions.PutTextLineAlignment _lineAlignment = EmguExtensions.PutTextLineAlignment.Left; private double _angle; private byte _removePixelBrightness; public override PixelOperationType OperationType => PixelOperationType.Text; @@ -55,6 +57,12 @@ namespace UVtools.Core.PixelEditor set => RaiseAndSetIfChanged(ref _mirror, value); } + public EmguExtensions.PutTextLineAlignment LineAlignment + { + get => _lineAlignment; + set => RaiseAndSetIfChanged(ref _lineAlignment, value); + } + public double Angle { get => _angle; @@ -81,19 +89,20 @@ namespace UVtools.Core.PixelEditor public PixelText(){} - public PixelText(uint layerIndex, Point location, LineType lineType, FontFace font, double fontScale, ushort thickness, string text, bool mirror, double angle, byte removePixelBrightness, byte pixelBrightness, bool isAdd) : base(layerIndex, location, lineType, pixelBrightness) + public PixelText(uint layerIndex, Point location, LineType lineType, FontFace font, double fontScale, ushort thickness, string text, bool mirror, EmguExtensions.PutTextLineAlignment lineAlignment, double angle, byte removePixelBrightness, byte pixelBrightness, bool isAdd) : base(layerIndex, location, lineType, pixelBrightness) { _font = font; _fontScale = fontScale; _thickness = thickness; _text = text; _mirror = mirror; + _lineAlignment = lineAlignment; _angle = angle; IsAdd = isAdd; _removePixelBrightness = removePixelBrightness; int baseLine = 0; - Size = CvInvoke.GetTextSize(text, font, fontScale, thickness, ref baseLine); + Size = EmguExtensions.GetTextSizeExtended(text, font, fontScale, thickness, ref baseLine, lineAlignment); Rectangle = new Rectangle(location, Size); } diff --git a/UVtools.Core/Slicer.cs b/UVtools.Core/Slicer.cs deleted file mode 100644 index 11529d7..0000000 --- a/UVtools.Core/Slicer.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.Drawing; - -namespace UVtools.Core -{ - public class Slicer - { - /// <summary> - /// Gets the size of resolution - /// </summary> - public Size Resolution { get; private set; } - - /// <summary> - /// Gets the size of display - /// </summary> - public SizeF Display { get; private set; } - - /// <summary> - /// Gets the pixels per millimeters - /// </summary> - public SizeF Ppmm { get; private set; } - - public Slicer(Size resolution, SizeF display) - { - Init(resolution, display); - } - - public void Init(Size resolution, SizeF display) - { - Resolution = resolution; - Display = display; - - Ppmm = new SizeF(resolution.Width / display.Width, resolution.Height / display.Height); - } - - public decimal MillimetersFromPixelsX(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Width, 2); - public decimal MillimetersFromPixelsY(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Height, 2); - public decimal MillimetersFromPixels (uint pixels) => (decimal) Math.Round(pixels / Math.Max(Ppmm.Width, Ppmm.Height), 2); - - public static decimal MillimetersFromPixelsX(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Width / display.Width), 2); - public static decimal MillimetersFromPixelsY(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Height / display.Height), 2); - - - - public uint PixelsFromMillimetersX(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Width); - public uint PixelsFromMillimetersY(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Height); - public uint PixelsFromMillimeters (decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Math.Max(Ppmm.Width, Ppmm.Height)); - - public static uint PixelsFromMillimetersX(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Width / display.Width * (double) millimeters); - public static uint PixelsFromMillimetersY(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Height / display.Height * (double) millimeters); - } -} diff --git a/UVtools.Core/Slicer/LinAlgUtils.cs b/UVtools.Core/Slicer/LinAlgUtils.cs new file mode 100644 index 0000000..c6efbcf --- /dev/null +++ b/UVtools.Core/Slicer/LinAlgUtils.cs @@ -0,0 +1,101 @@ +/* + * 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.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using QuantumConcepts.Formats.StereoLithography; + +namespace UVtools.Core.Slicer +{ + public static class LinAlgUtils + { + #region Find Facets + public static List<Facet> FindFacetsIntersectingZIndex(STLDocument stl, float z) + { + return stl.Facets.Where(f => f.Vertices.Any(v => v.Z <= z) && f.Vertices.Any(v => v.Z >= z) && Math.Abs(f.Normal.Z) != 1).ToList(); + } + + public static List<Facet> FindFlatFacetsAtZIndex(STLDocument stl, float z) + { + return stl.Facets.Where(f => f.Vertices.All(v => v.Z == z)).ToList(); + } + + public static List<Facet> FindTopFlatFacetsAtZIndex(STLDocument stl, float z) + { + return stl.Facets.Where(f => f.Vertices.Any(v => v.Z == z) && f.Normal.Z == 1).ToList(); + } + public static List<Facet> FindBottomFlatFacetsAtZIndex(STLDocument stl, float z) + { + return stl.Facets.Where(f => f.Vertices.Any(v => v.Z == z) && f.Normal.Z == -1).ToList(); + } + #endregion + + public static SliceLine CreateLineFromFacetAtZIndex(Facet facet, float z) + { + // This won't work for flat horizontal plane vertices, so throw if that's what we've got + if (Math.Abs(facet.Normal.Z) == 1) + throw new Exception("Cannot create lines for flat horizontal planes"); + var verts = facet.Vertices; + var returnLine = new SliceLine(); + // If any vertices are ON the z-index, just return that line + returnLine.AddRange(verts.Where(v => v.Z == z).Select(v => new PointF(v.X, v.Y))); + // For uniformity, I'm representing a point as a "line" between two of the same point + // It's janky, I'll refactor later + if (returnLine.Count == 1) + { + returnLine.Add(returnLine[0]); + } + if (returnLine.Count == 2) + { + returnLine.Normal = new PointF(facet.Normal.X, facet.Normal.Y); + return returnLine; + } + // If no vertices are ON the z-index, find the two points where they CROSS it + if ((verts[0].Z - z) / (verts[1].Z - z) < 0) + returnLine.Add(CalculateZIntercept(verts[0], verts[1], z)); + if ((verts[2].Z - z) / (verts[1].Z - z) < 0) + returnLine.Add(CalculateZIntercept(verts[2], verts[1], z)); + + if (returnLine.Count < 2) + returnLine.Add(CalculateZIntercept(verts[0], verts[2], z)); + + returnLine.Normal = new PointF(facet.Normal.X, facet.Normal.Y); + + if (!returnLine.Validate()) + throw new InvalidOperationException("Invalid Point Data"); + + return returnLine; + } + + public static PointF CalculateZIntercept(Vertex v1, Vertex v2, float z) + { + var returnX = CalculateDimensionalValueAtIndex(new PointF(v1.Z, v1.X), new PointF(v2.Z, v2.X), z); + var returnY = CalculateDimensionalValueAtIndex(new PointF(v1.Z, v1.Y), new PointF(v2.Z, v2.Y), z); + return new PointF + { + X = returnX, + Y = returnY + }; + } + + public static float CalculateDimensionalValueAtIndex(PointF p1, PointF p2, float z, string precision = "0000") + { + var slope = (p1.Y - p2.Y) / (p1.X - p2.X); + var intercept = p1.Y - (slope * p1.X); + var rawVal = slope * z + intercept; + // using floats we end up with some infinitesimal rounding errors, + // so we need to set the precision to something reasonable. Default is 1/100th of a micron + // I'm sure there's a better way than converting it to a string and then back to a float, + // but that's what I've got right now, so that's what I'm doing. + var strVal = rawVal.ToString($"0.{precision}"); + return float.Parse(strVal, CultureInfo.InvariantCulture.NumberFormat); + } + } +} diff --git a/UVtools.Core/Slicer/RangeUtils.cs b/UVtools.Core/Slicer/RangeUtils.cs new file mode 100644 index 0000000..92f7f42 --- /dev/null +++ b/UVtools.Core/Slicer/RangeUtils.cs @@ -0,0 +1,77 @@ +/* + * 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 QuantumConcepts.Formats.StereoLithography; + +namespace UVtools.Core.Slicer +{ + public static class RangeUtils + { + public static float CalculateHighX(STLDocument stl) + { + float highestX = stl.Facets[0].Vertices[0].X; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.X > highestX) + highestX = v.X; + })); + return highestX; + } + public static float CalculateLowX(STLDocument stl) + { + float lowestX = stl.Facets[0].Vertices[0].X; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.X < lowestX) + lowestX = v.X; + })); + return lowestX; + } + + public static float CalculateHighY(STLDocument stl) + { + float highestY = stl.Facets[0].Vertices[0].Y; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.Y > highestY) + highestY = v.Y; + })); + return highestY; + } + public static float CalculateLowY(STLDocument stl) + { + float lowestY = stl.Facets[0].Vertices[0].Y; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.Y < lowestY) + lowestY = v.Y; + })); + return lowestY; + } + + public static float CalculateHighZ(STLDocument stl) + { + float highestZ = stl.Facets[0].Vertices[0].Z; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.Z > highestZ) + highestZ = v.Z; + })); + return highestZ; + } + public static float CalculateLowZ(STLDocument stl) + { + float lowestZ = stl.Facets[0].Vertices[0].Z; + stl.Facets.ForEach(f => f.Vertices.ForEach(v => + { + if (v.Z < lowestZ) + lowestZ = v.Z; + })); + return lowestZ; + } + } +} diff --git a/UVtools.Core/Slicer/Slice.cs b/UVtools.Core/Slicer/Slice.cs new file mode 100644 index 0000000..de26bdc --- /dev/null +++ b/UVtools.Core/Slicer/Slice.cs @@ -0,0 +1,42 @@ +/* + * 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.Collections.Generic; +using System.Linq; + +namespace UVtools.Core.Slicer +{ + public class Slice : List<SliceLine> + { + public bool ValidateShapeIntegrity() + { + // A shape has to have at least three lines to close + if (Count < 3) + return false; + + var allPoints = this.ToList(); + var pointFreq = new Dictionary<SliceLine, int>(); + + // There can't be any hanging lines. Verify every point connects to at least one other line. + foreach (var p in allPoints) + { + if (!pointFreq.ContainsKey(p)) + pointFreq[p] = 1; + else + pointFreq[p]++; + } + + foreach (var p in pointFreq.Keys) + { + if (pointFreq[p] < 2) + return false; + } + + return true; + } + } +} diff --git a/UVtools.Core/Slicer/SliceLine.cs b/UVtools.Core/Slicer/SliceLine.cs new file mode 100644 index 0000000..1d82a7a --- /dev/null +++ b/UVtools.Core/Slicer/SliceLine.cs @@ -0,0 +1,61 @@ +/* + * 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.Generic; +using System.Drawing; + +namespace UVtools.Core.Slicer +{ + public class SliceLine : List<PointF> + { + private PointF _normal; + + public PointF Normal + { + get => _normal; + set => _normal = CalculateNormal(value); + } + + public PointF CalculateNormal(PointF normal = default) + { + if (!Validate()) + throw new InvalidOperationException("Can't calculate Normal without points set"); + + if (normal == PointF.Empty) + normal = _normal; + if (normal == PointF.Empty) + throw new InvalidOperationException("Can't calculate Normal without a starting point"); + + // calculate normal slopes + var dX = this[0].Y - this[1].Y; + var dY = this[0].X - this[1].X; + // determine normal direction + var xDir = normal.X >= 0 ? 1 : -1; + var yDir = normal.Y >= 0 ? 1 : -1; + // check for delta 0 in either direction + if (dX == 0 && dY == 0) + return new PointF(0, 0); + if (dX == 0) + return new PointF(0, yDir); + if (dY == 0) + return new PointF(xDir, 0); + + // if there aren't any zeroes, calculate the hypotenuse + var hypotenuse = (float)Math.Sqrt((dX * dX) + (dY * dY)); + // use cross-multiplication and solve for x & y to find normal values where hypotenuse == 1 + dX = Math.Abs(dX / hypotenuse) * xDir; + dY = Math.Abs(dY / hypotenuse) * yDir; + return new PointF(dX, dY); + } + + public bool Validate() + { + return Count == 2; + } + } +} diff --git a/UVtools.Core/Slicer/Slicer.cs b/UVtools.Core/Slicer/Slicer.cs new file mode 100644 index 0000000..a570948 --- /dev/null +++ b/UVtools.Core/Slicer/Slicer.cs @@ -0,0 +1,186 @@ +/* + * 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.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using QuantumConcepts.Formats.StereoLithography; + +namespace UVtools.Core.Slicer +{ + public class Slicer + { + private float _lowX = float.NaN; + private float _highX = float.NaN; + private float _lowY = float.NaN; + private float _highY = float.NaN; + private float _lowZ = float.NaN; + private float _highZ = float.NaN; + private readonly STLDocument _stl; + protected Dictionary<float, Slice> _slices = new (); + + private STLDocument Stl => _stl; + + /// <summary> + /// Gets the size of resolution + /// </summary> + public Size Resolution { get; private set; } + + /// <summary> + /// Gets the size of display + /// </summary> + public SizeF Display { get; private set; } + + /// <summary> + /// Gets the pixels per millimeters + /// </summary> + public SizeF Ppmm { get; private set; } + + public float LowX + { + get + { + if(_lowX == float.NaN) + _lowX = RangeUtils.CalculateLowX(_stl); + + return _lowX; + } + } + + public float HighX + { + get + { + if (_highX == float.NaN) + _highX = RangeUtils.CalculateHighX(_stl); + + return _highX; + } + } + + public float LowY + { + get + { + if (_lowY == float.NaN) + _lowY = RangeUtils.CalculateLowY(_stl); + + return _lowY; + } + } + + public float HighY + { + get + { + if (_highY == float.NaN) + _highY = RangeUtils.CalculateHighY(_stl); + + return _highY; + } + } + + public float LowZ + { + get + { + if (_lowZ == float.NaN) + _lowZ = RangeUtils.CalculateLowZ(_stl); + + return _lowZ; + } + } + + public float HighZ + { + get + { + if (_highZ == float.NaN) + _highZ = RangeUtils.CalculateHighZ(_stl); + + return _highZ; + } + } + + public Slicer(Size resolution, SizeF display) + { + Init(resolution, display); + } + + public Slicer(Size resolution, SizeF display, STLDocument stl) : this(resolution, display) + { + _stl = stl; + } + + public Slicer(Size resolution, SizeF display, string stlPath) : this(resolution, display) + { + _stl = STLDocument.Open(stlPath); + + if (_stl is null) + throw new FileNotFoundException(null, stlPath); + } + + public void Init(Size resolution, SizeF display) + { + Resolution = resolution; + Display = display; + + Ppmm = new SizeF(resolution.Width / display.Width, resolution.Height / display.Height); + } + + #region Slice Methods + public Slice GetSliceAtZIndex(float z) + { + // cache this in the event you have to retrieve a single value more than once, + // we don't want to have to do this math again + if (!_slices.ContainsKey(z)) + { + var facets = LinAlgUtils.FindFacetsIntersectingZIndex(_stl, z); + _slices[z] = new Slice(); + _slices[z].AddRange(facets.Select(f => LinAlgUtils.CreateLineFromFacetAtZIndex(f, z))); + } + + return _slices[z]; + } + + public Dictionary<float, Slice> SliceModel(float layerHeight) + { + var newDict = new Dictionary<float, Slice>(); + for (var z = RangeUtils.CalculateLowZ(_stl); z <= RangeUtils.CalculateHighZ(_stl); z += layerHeight) + { + if (_slices.Keys.Contains(z)) + newDict[z] = _slices[z]; + else + { + newDict[z] = GetSliceAtZIndex(z); + _slices[z] = newDict[z]; + } + } + return newDict; + } + #endregion + + public decimal MillimetersFromPixelsX(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Width, 2); + public decimal MillimetersFromPixelsY(uint pixels) => (decimal) Math.Round(pixels / Ppmm.Height, 2); + public decimal MillimetersFromPixels (uint pixels) => (decimal) Math.Round(pixels / Math.Max(Ppmm.Width, Ppmm.Height), 2); + + public static decimal MillimetersFromPixelsX(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Width / display.Width), 2); + public static decimal MillimetersFromPixelsY(Size resolution, SizeF display, uint pixels) => (decimal)Math.Round(pixels / (resolution.Height / display.Height), 2); + + + + public uint PixelsFromMillimetersX(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Width); + public uint PixelsFromMillimetersY(decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Ppmm.Height); + public uint PixelsFromMillimeters (decimal millimeters) => (uint) Math.Floor(millimeters * (decimal) Math.Max(Ppmm.Width, Ppmm.Height)); + + public static uint PixelsFromMillimetersX(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Width / display.Width * (double) millimeters); + public static uint PixelsFromMillimetersY(Size resolution, SizeF display, decimal millimeters) => (uint)Math.Floor(resolution.Height / display.Height * (double) millimeters); + } +} diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index d0f18c5..f6a08d3 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.13.1</Version> + <Version>2.13.2</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> @@ -53,6 +53,7 @@ <PackageReference Include="morelinq" Version="3.3.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.10" /> + <PackageReference Include="QuantumConcepts.Formats.STL.netcore" Version="1.3.1" /> <PackageReference Include="System.Memory" Version="4.5.4" /> <PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" /> </ItemGroup> |