diff options
Diffstat (limited to 'UVtools.Core/Extensions/EmguExtensions.cs')
-rw-r--r-- | UVtools.Core/Extensions/EmguExtensions.cs | 175 |
1 files changed, 161 insertions, 14 deletions
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 3a55047..4240372 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -8,23 +8,20 @@ using System; using System.Drawing; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace UVtools.Core.Extensions { public static class EmguExtensions { - public static readonly MCvScalar WhiteByte = new(255); - public static readonly MCvScalar White3Byte = new(255, 255, 255); - public static readonly MCvScalar BlackByte = new(0); - public static readonly MCvScalar Black3Byte = new(0, 0, 0); - public static readonly MCvScalar Transparent4Byte = new(0, 0, 0, 0); - public static readonly MCvScalar Black4Byte = new(0, 0, 0, 255); + 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(); public static unsafe byte* GetBytePointer(this Mat mat) { @@ -72,7 +69,7 @@ namespace UVtools.Core.Extensions public static unsafe Span<T> GetPixelColSpan<T>(this Mat mat, int x, int length = 0, int offset = 0) { var colMat = mat.Col(x); - return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length); + return new(IntPtr.Add(colMat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length); } /// <summary> @@ -95,25 +92,89 @@ namespace UVtools.Core.Extensions 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); - using (var translateTransform = new Matrix<double>(2, 3) + using var translateTransform = new Matrix<double>(2, 3) { [0, 0] = xScale, // xScale [1, 1] = yScale, // yScale [0, 2] = xTrans, //x translation + compensation of x scaling [1, 2] = yTrans // y translation + compensation of y scaling - }) + }; + CvInvoke.WarpAffine(src, src, translateTransform, dstSize.IsEmpty ? src.Size : dstSize, interpolation); + } + + /// <summary> + /// Rotates a Mat by an angle while keeping the image size + /// </summary> + /// <param name="src"></param> + /// <param name="angle"></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); + + /// <summary> + /// Rotates a Mat by an angle while keeping the image size + /// </summary> + /// <param name="src"></param> + /// <param name="dst"></param> + /// <param name="angle"></param> + /// <param name="scale"></param> + public static void Rotate(this Mat src, Mat dst, double angle, Size newSize = default, double scale = 1.0) + { + if (angle % 360 == 0 && scale == 1.0) return; + if (newSize.IsEmpty) + { + newSize = src.Size; + } + + var halfWidth = src.Width / 2.0f; + var halfHeight = src.Height / 2.0f; + using var translateTransform = new Matrix<double>(2, 3); + CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform); + + if (src.Size != newSize) { - CvInvoke.WarpAffine(src, src, translateTransform, dstSize.IsEmpty ? src.Size : dstSize, interpolation); + // adjust the rotation matrix to take into account translation + translateTransform[0, 2] += newSize.Width / 2.0 - halfWidth; + translateTransform[1, 2] += newSize.Height / 2.0 - halfHeight; } + + CvInvoke.WarpAffine(src, dst, translateTransform, newSize); } - public static void Rotate(this Mat src, double angle) + /// <summary> + /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content + /// </summary> + /// <param name="src"></param> + /// <param name="angle"></param> + /// <param name="scale"></param> + public static void RotateAdjustBounds(this Mat src, double angle, double scale = 1.0) => RotateAdjustBounds(src, src, angle, scale); + + /// <summary> + /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content + /// </summary> + /// <param name="src"></param> + /// <param name="dst"></param> + /// <param name="angle"></param> + /// <param name="scale"></param> + public static void RotateAdjustBounds(this Mat src, Mat dst, double angle, double scale = 1.0) { + if (angle % 360 == 0 && scale == 1.0) return; var halfWidth = src.Width / 2.0f; var halfHeight = src.Height / 2.0f; using var translateTransform = new Matrix<double>(2, 3); - CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, 1.0, translateTransform); - CvInvoke.WarpAffine(src, src, translateTransform, src.Size); + CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform); + var cos = Math.Abs(translateTransform[0, 0]); + var sin = Math.Abs(translateTransform[0, 1]); + + // compute the new bounding dimensions of the image + int newWidth = (int) (src.Height * sin + src.Width * cos); + int newHeight = (int) (src.Height * cos + src.Width * sin); + + // adjust the rotation matrix to take into account translation + translateTransform[0, 2] += newWidth / 2.0 - halfWidth; + translateTransform[1, 2] += newHeight / 2.0 - halfHeight; + + + CvInvoke.WarpAffine(src, dst, translateTransform, new Size(newWidth, newHeight)); } /// <summary> @@ -134,6 +195,85 @@ namespace UVtools.Core.Extensions yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation); } + 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) + { + if (angle % 360 == 0) // No rotation needed, cheaper cycle + { + CvInvoke.PutText(src, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin); + return; + } + + using var rotatedSrc = src.Clone(); + rotatedSrc.RotateAdjustBounds(-angle); + 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); + + using var mask = rotatedSrc.CloneBlank(); + CvInvoke.PutText(mask, text, org, fontFace, fontScale, WhiteColor, thickness, lineType, bottomLeftOrigin); + + rotatedSrc.Rotate(angle, src.Size); + mask.Rotate(angle, src.Size); + + rotatedSrc.CopyTo(src, mask); + } + + /// <summary> + /// Determine the area (i.e. total number of pixels in the image), + /// initialize the output skeletonized image, and construct the + /// morphological structuring element + /// </summary> + /// <param name="src"></param> + /// <param name="iterations">Number of iterations required to perform the skeletoize</param> + /// <param name="ksize"></param> + /// <param name="elementShape"></param> + public static Mat Skeletonize(this Mat src, out int iterations, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle) + { + if (ksize.IsEmpty) ksize = new Size(3, 3); + Point anchor = new(-1, -1); + var skeleton = src.CloneBlank(); + using var kernel = CvInvoke.GetStructuringElement(elementShape, ksize, anchor); + + var image = src; + using var temp = new Mat(); + iterations = 0; + while (true) + { + iterations++; + + // erode and dilate the image using the structuring element + using var eroded = new Mat(); + CvInvoke.Erode(image, eroded, kernel, anchor, 1, BorderType.Reflect101, default); + CvInvoke.Dilate(eroded, temp, kernel, anchor, 1, BorderType.Reflect101, default); + + // subtract the temporary image from the original, eroded + // image, then take the bitwise 'or' between the skeleton + // and the temporary image + CvInvoke.Subtract(image, temp, temp); + CvInvoke.BitwiseOr(skeleton, temp, skeleton); + image = eroded.Clone(); + + // if there are no more 'white' pixels in the image, then + // break from the loop + if (CvInvoke.CountNonZero(image) == 0) break; + } + + return skeleton; + } + + /// <summary> + /// Determine the area (i.e. total number of pixels in the image), + /// initialize the output skeletonized image, and construct the + /// morphological structuring element + /// </summary> + /// <param name="src"></param> + /// <param name="ksize"></param> + /// <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> @@ -226,6 +366,13 @@ namespace UVtools.Core.Extensions 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> |