From 64971d3f931611ca83e3d30fb2c5d3880f879816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 17 Feb 2024 21:07:33 +0000 Subject: [PATCH] v4.2.2 - **UI:** - (Add) Allow to drag and drop Gerber files into main window to open them in the PCB exposure tool (Hold SHIFT key to run the operation right away, without opening the tool dialog, it will run with the default settings) - (Improvement) Drag and drop invalid files that are not recognized no longer closes the current file nor open new instances when multiple files are passed or when SHIFT is held - **Pixel editor:** - (Improvement) Line shape was producing one pixel more than required - (Improvement) Make the shape preview more accurate with the actual real output and when defining a shape thickness instead of a fill - (Improvement) Increase the accuracy of the shapes by passing float instead of int - (Improvement) Make the square shape to be exactly the input brush size - (Improvement) Dispose Skia drawing objects after draw the pixel editor shapes into the previewer - (Improvement) Better performance and speed when rendering the layer image into the previewer - (Improvement) Dispose cached data when layer changes into the previewer - (Change) Layer image preview format from Bgr to Bgra (Added alpha channel, now 32 bits) - (Fix) Calibration Blooming effect: All layers are being set with bottom exposure time (#481) - (Upgrade) .NET from 6.0.26 to 6.0.27 - (Downgrade) AvaloniaUI from 11.0.9 to 11.0.7 (Fix #836 but may re-introduce others) --- CHANGELOG.md | 18 ++ Directory.Build.props | 2 + RELEASE_NOTES.md | 30 ++- UVtools.AvaloniaControls/AdvancedImageBox.cs | 46 ++-- .../UVtools.AvaloniaControls.csproj | 4 +- UVtools.Core/Extensions/DrawingExtensions.cs | 67 +++--- UVtools.Core/Extensions/EmguExtensions.cs | 58 +++-- UVtools.Core/Extensions/PointExtensions.cs | 4 +- UVtools.Core/Extensions/SizeExtensions.cs | 4 +- UVtools.Core/FileFormats/FileFormat.cs | 4 +- .../Gerber/Apertures/PoygonAperture.cs | 2 +- .../Gerber/Primitives/PolygonPrimitive.cs | 2 +- UVtools.Core/Layers/Layer.cs | 16 +- .../OperationCalibrateBloomingEffect.cs | 8 +- .../OperationCalibrateExposureFinder.cs | 2 +- .../Operations/OperationPCBExposure.cs | 5 +- UVtools.Core/SystemOS/SystemAware.cs | 25 ++- UVtools.Core/UVtools.Core.csproj | 4 +- .../CalibrateBloomingEffectControl.axaml.cs | 2 +- .../CalibrateElephantFootControl.axaml.cs | 2 +- .../CalibrateExposureFinderControl.axaml.cs | 2 +- .../CalibrateGrayscaleControl.axaml.cs | 2 +- .../CalibrateLiftHeightControl.axaml.cs | 2 +- .../CalibrateToleranceControl.axaml.cs | 2 +- .../CalibrateXYZAccuracyControl.axaml.cs | 2 +- .../Tools/ToolLithophaneControl.axaml.cs | 2 +- .../Controls/Tools/ToolMaskControl.axaml.cs | 6 +- .../Tools/ToolPCBExposureControl.axaml.cs | 6 +- .../Tools/ToolPhasedExposureControl.axaml | 1 + UVtools.UI/Extensions/BitmapExtension.cs | 109 +++------- UVtools.UI/LayerCache.cs | 67 ++++-- UVtools.UI/MainWindow.Information.cs | 4 +- UVtools.UI/MainWindow.LayerPreview.cs | 198 ++++++++---------- UVtools.UI/MainWindow.PixelEditor.cs | 113 +++++----- UVtools.UI/MainWindow.axaml.cs | 13 +- UVtools.UI/Structures/Color.cs | 10 +- UVtools.UI/UVtools.UI.csproj | 16 +- documentation/UVtools.Core.xml | 31 ++- 38 files changed, 501 insertions(+), 390 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edcc6bec..83b148fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 17/02/2024 - v4.2.2 + +- **UI:** + - (Add) Allow to drag and drop Gerber files into main window to open them in the PCB exposure tool (Hold SHIFT key to run the operation right away, without opening the tool dialog, it will run with the default settings) + - (Improvement) Drag and drop invalid files that are not recognized no longer closes the current file nor open new instances when multiple files are passed or when SHIFT is held +- **Pixel editor:** + - (Improvement) Line shape was producing one pixel more than required + - (Improvement) Make the shape preview more accurate with the actual real output and when defining a shape thickness instead of a fill + - (Improvement) Increase the accuracy of the shapes by passing float instead of int + - (Improvement) Make the square shape to be exactly the input brush size + - (Improvement) Dispose Skia drawing objects after draw the pixel editor shapes into the previewer +- (Improvement) Better performance and speed when rendering the layer image into the previewer +- (Improvement) Dispose cached data when layer changes into the previewer +- (Change) Layer image preview format from Bgr to Bgra (Added alpha channel, now 32 bits) +- (Fix) Calibration Blooming effect: All layers are being set with bottom exposure time (#481) +- (Upgrade) .NET from 6.0.26 to 6.0.27 +- (Downgrade) AvaloniaUI from 11.0.9 to 11.0.7 (Fix #836 but may re-introduce others) + ## 10/02/2024 - v4.2.1 - **UI:** diff --git a/Directory.Build.props b/Directory.Build.props index 0e509d24..d7d543b7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -25,6 +25,8 @@ $(MSBuildThisFileDirectory)build\UVtools.snk $(MSBuildProjectDirectory)=$(MSBuildProjectName) + + 11.0.7 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d33c3bd4..a45ff177 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,18 +1,16 @@ - **UI:** - - **Pixel editor:** - - (Add) Shortcut: Ctrl/⌘+E to de/activate pixel editor - - (Add) Profiles (#604, #831) - - (Improvement) Use DataGrid row header to show the operation count instead of process and store indexes for the objects - - (Fix) Drawings and Text controls are not stretched to fill the available space to the right - - (Improvement) macOS: Shortcuts Ctrl+0, Ctrl+R, Ctrl+F changed to use command key(⌘) instead of control key -- (Improvement) Dynamic layer height, Lithophane, PCB exposure and Phased exposure: Use better performant `HasNonZero` instead of `CountNonZero` to check if there are any pixels to process -- (Improvement) Update demo file with the newest version of the format and use SL1S printer instead -- (Change) Extract file contents shortcut: From Ctrl+E to Ctrl+Shift+E -- (Fix) Files with different resolution information from empty layers were generating wrong image creation for such layers (#833) -- (Fix) Possible throw an exception about invalid roi when using Roi and/or masks under some tools -- (Fix) Skeletonize memory leak -- (Fix) System.NullReferenceException when accessing reallocated layers (#835) -- (Fix) Unable to save profiles for "Light bleed compensation", "Phased exposure" -- (Fix) PrusaSlicer printer "UVtools Prusa SL1S SPEED": Fix the display width and height, they were flipped -- (Upgrade) AvaloniaUI from 11.0.7 to 11.0.9 + - (Add) Allow to drag and drop Gerber files into main window to open them in the PCB exposure tool (Hold SHIFT key to run the operation right away, without opening the tool dialog, it will run with the default settings) + - (Improvement) Drag and drop invalid files that are not recognized no longer closes the current file nor open new instances when multiple files are passed or when SHIFT is held +- **Pixel editor:** + - (Improvement) Line shape was producing one pixel more than required + - (Improvement) Make the shape preview more accurate with the actual real output and when defining a shape thickness instead of a fill + - (Improvement) Increase the accuracy of the shapes by passing float instead of int + - (Improvement) Make the square shape to be exactly the input brush size + - (Improvement) Dispose Skia drawing objects after draw the pixel editor shapes into the previewer +- (Improvement) Better performance and speed when rendering the layer image into the previewer +- (Improvement) Dispose cached data when layer changes into the previewer +- (Change) Layer image preview format from Bgr to Bgra (Added alpha channel, now 32 bits) +- (Fix) Calibration Blooming effect: All layers are being set with bottom exposure time (#481) +- (Upgrade) .NET from 6.0.26 to 6.0.27 +- (Downgrade) AvaloniaUI from 11.0.9 to 11.0.7 (Fix #836 but may re-introduce others) diff --git a/UVtools.AvaloniaControls/AdvancedImageBox.cs b/UVtools.AvaloniaControls/AdvancedImageBox.cs index 3132e104..b8b38b77 100644 --- a/UVtools.AvaloniaControls/AdvancedImageBox.cs +++ b/UVtools.AvaloniaControls/AdvancedImageBox.cs @@ -1022,7 +1022,7 @@ public Rect SelectionRegion TriggerRender(); RaisePropertyChanged(nameof(HaveSelection)); RaisePropertyChanged(nameof(SelectionRegionNet)); - RaisePropertyChanged(nameof(SelectionPixelSize)); + RaisePropertyChanged(nameof(SelectionRegionPixel)); } } @@ -1031,17 +1031,16 @@ public Rectangle SelectionRegionNet get { var rect = SelectionRegion; - return new Rectangle((int) Math.Ceiling(rect.X), (int)Math.Ceiling(rect.Y), - (int)rect.Width, (int)rect.Height); + return new ((int) Math.Ceiling(rect.X), (int)Math.Ceiling(rect.Y), (int)rect.Width, (int)rect.Height); } } - public PixelSize SelectionPixelSize + public PixelRect SelectionRegionPixel { get { var rect = SelectionRegion; - return new PixelSize((int) rect.Width, (int) rect.Height); + return new ((int)Math.Ceiling(rect.X), (int)Math.Ceiling(rect.Y), (int)rect.Width, (int)rect.Height); } } @@ -1191,8 +1190,7 @@ public override void Render(DrawingContext context) var image = Image; if (image is null) return; var imageViewPort = GetImageViewPort(); - - + // Draw image context.DrawImage(image, GetSourceImageRegion(), @@ -2395,6 +2393,10 @@ public Rect GetImageViewPort() #endregion #region Image methods + /// + /// Loads the image from the specified path. + /// + /// Image path from disk public void LoadImage(string path) { Image = new Bitmap(path); @@ -2405,31 +2407,31 @@ public void LoadImage(string path) var image = ImageAsWriteableBitmap; if (image is null || !HaveSelection) return null; - var selection = SelectionRegionNet; - var pixelSize = SelectionPixelSize; - using var frameBuffer = image.Lock(); - - var newBitmap = new WriteableBitmap(pixelSize, image.Dpi, frameBuffer.Format, AlphaFormat.Unpremul); - using var newFrameBuffer = newBitmap.Lock(); + var selection = SelectionRegionPixel; - int i = 0; + using var srcBuffer = image.Lock(); + var cropBitmap = new WriteableBitmap(selection.Size, image.Dpi, srcBuffer.Format, AlphaFormat.Unpremul); + using var dstBuffer = cropBitmap.Lock(); unsafe { - var inputPixels = (uint*) (void*) frameBuffer.Address; - var targetPixels = (uint*) (void*) newFrameBuffer.Address; + var ySrc = srcBuffer.Address + srcBuffer.RowBytes * selection.Y + selection.X * (srcBuffer.Format.BitsPerPixel / 8); + var yDst = dstBuffer.Address; for (int y = selection.Y; y < selection.Bottom; y++) { - var thisY = y * frameBuffer.Size.Width; - for (int x = selection.X; x < selection.Right; x++) - { - targetPixels![i++] = inputPixels![thisY + x]; - } + Buffer.MemoryCopy( + ySrc.ToPointer(), + yDst.ToPointer(), + dstBuffer.RowBytes, + dstBuffer.RowBytes); + + ySrc += srcBuffer.RowBytes; + yDst += dstBuffer.RowBytes; } } - return newBitmap; + return cropBitmap; } #endregion diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj index 9330e70c..14bf41e1 100644 --- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj +++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj @@ -7,7 +7,7 @@ - ExtendedNumericUpDown: Initial value with a reset button and value unit label - IndexSelector: Allow to choose an index from a collection count and display the selected number - GroupBox: Similar to GroupBox of WinForms, it contain an Header and Content - 3.0.2 + 3.0.3 MIT https://github.com/sn4k3/UVtools/tree/master/UVtools.AvaloniaControls @@ -24,7 +24,7 @@ - + diff --git a/UVtools.Core/Extensions/DrawingExtensions.cs b/UVtools.Core/Extensions/DrawingExtensions.cs index 1fc63148..c407da0a 100644 --- a/UVtools.Core/Extensions/DrawingExtensions.cs +++ b/UVtools.Core/Extensions/DrawingExtensions.cs @@ -38,54 +38,55 @@ public static double CalculatePolygonRadiusFromSideLength(double length, int sid return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0)); } - public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false) + + public static Point[] GetPolygonVertices(int sides, double diameter, PointF center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false, MidpointRounding midpointRounding = MidpointRounding.AwayFromZero) { if (sides < 3) throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides)); var vertices = new Point[sides]; + var radius = diameter / 2; - /*if (sides == 4) + if (sides == 4) { - var rotatedRect = new RotatedRect(center, new SizeF(radius * 2, radius * 2), (float)startingAngle); + var rotatedRect = new RotatedRect(center, new SizeF((float)diameter - 1, (float)diameter - 1), (float)startingAngle); var verticesF = rotatedRect.GetVertices(); for (var i = 0; i < verticesF.Length; i++) { - vertices[i] = verticesF[i].ToPoint(); + vertices[i] = verticesF[i].ToPoint(midpointRounding); } - return vertices; - }*/ - - - - var deg = 360.0 / sides;//calculate the rotation angle - var rad = Math.PI / 180.0; + } + else + { + var angleIncrement = 360.0 / sides; //calculate the rotation angle + var rad = Math.PI / 180.0; - var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad); - var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad); + var x0 = center.X + radius * Math.Cos(-(((180 - angleIncrement) / 2) + startingAngle) * rad); + var y0 = center.Y - radius * Math.Sin(-(((180 - angleIncrement) / 2) + startingAngle) * rad); - var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad); - var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad); + var x1 = center.X + radius * Math.Cos(-(((180 - angleIncrement) / 2) + startingAngle + angleIncrement) * rad); + var y1 = center.Y - radius * Math.Sin(-(((180 - angleIncrement) / 2) + startingAngle + angleIncrement) * rad); - vertices[0] = new( - (int) Math.Round(x0, MidpointRounding.AwayFromZero), - (int) Math.Round(y0, MidpointRounding.AwayFromZero) - ); + vertices[0] = new( + (int)Math.Round(x0, midpointRounding), + (int)Math.Round(y0, midpointRounding) + ); - vertices[1] = new( - (int) Math.Round(x1, MidpointRounding.AwayFromZero), - (int) Math.Round(y1, MidpointRounding.AwayFromZero) - ); + vertices[1] = new( + (int)Math.Round(x1, midpointRounding), + (int)Math.Round(y1, midpointRounding) + ); - for (int i = 0; i < sides - 2; i++) - { - var dsinrot = Math.Sin(deg * (i + 1) * rad); - var dcosrot = Math.Cos(deg * (i + 1) * rad); + for (int i = 0; i < sides - 2; i++) + { + var dsinrot = Math.Sin(angleIncrement * (i + 1) * rad); + var dcosrot = Math.Cos(angleIncrement * (i + 1) * rad); - vertices[i + 2] = new( - (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y), MidpointRounding.AwayFromZero), - (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y), MidpointRounding.AwayFromZero) - ); + vertices[i + 2] = new( + (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y), midpointRounding), + (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y), midpointRounding) + ); + } } if (flipHorizontally) @@ -94,7 +95,7 @@ public static Point[] GetPolygonVertices(int sides, int radius, Point center, do var endX = center.X + radius; for (int i = 0; i < sides; i++) { - vertices[i].X = endX - (vertices[i].X - startX); + vertices[i].X = (int)Math.Round(endX - (vertices[i].X - startX), midpointRounding); } } @@ -104,7 +105,7 @@ public static Point[] GetPolygonVertices(int sides, int radius, Point center, do var endY = center.Y + radius; for (int i = 0; i < sides; i++) { - vertices[i].Y = endY - (vertices[i].Y - startY); + vertices[i].Y = (int)Math.Round(endY - (vertices[i].Y - startY), midpointRounding); } } diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 6851796f..e4a284df 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -665,6 +665,22 @@ public static void CropByBounds(this Mat src, Mat dst) #endregion #region Copy methods + + /// + /// Copy the whole mat data to another reference + /// + /// It does not do any safe-check. + /// + /// Destination address to copy data to + public static void CopyTo(this Mat mat, IntPtr destination) + { + var totalBytes = (uint)mat.GetLength(); + unsafe + { + Buffer.MemoryCopy(mat.DataPointer.ToPointer(), destination.ToPointer(), totalBytes, totalBytes); + } + } + /// /// Copy a region from to center of other /// @@ -817,14 +833,24 @@ public static Mat Roi(this Mat mat, Mat fromMat) { return new Mat(mat, new(Point.Empty, fromMat.Size)); } - + + /// + /// Calculates the bounding rectangle and return a object with it + /// + /// + /// + public static Mat BoundingRectangleRoi(this Mat mat) + { + var boundingRectangle = CvInvoke.BoundingRectangle(mat); + return new Mat(mat, boundingRectangle); + } /// /// Calculates the bounding rectangle and return a object with it /// /// /// - public static MatRoi RoiFromAutoBoundingRectangle(this Mat mat) + public static MatRoi BoundingRectangleMatRoi(this Mat mat) { var boundingRectangle = CvInvoke.BoundingRectangle(mat); return new MatRoi(mat, boundingRectangle); @@ -1736,54 +1762,56 @@ public static void DrawCenteredRectangle(this Mat src, Size size, Point center, } /// - /// Draw a polygon given number of sides and length + /// Draw a polygon given number of sides and diameter /// /// /// Number of polygon sides, Special: use 1 to draw a line and >= 100 to draw a native OpenCV circle - /// Radius + /// Diameter /// Center position /// /// /// /// /// - public static void DrawPolygon(this Mat src, int sides, int radius, Point center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected, FlipType? flip = null) + /// + public static void DrawPolygon(this Mat src, int sides, double diameter, PointF center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected, FlipType? flip = null, MidpointRounding midpointRounding = MidpointRounding.AwayFromZero) { if (sides == 1) { - var point1 = center with {X = center.X - radius}; - var point2 = center with {X = center.X + radius}; + var point1 = center with { X = (float)Math.Round(center.X - diameter / 2, midpointRounding) }; + var point2 = point1 with { X = (float)(point1.X + diameter - 1) }; point1 = point1.Rotate(startingAngle, center); point2 = point2.Rotate(startingAngle, center); if (flip is FlipType.Horizontal or FlipType.Both) { - var newPoint1 = new Point(point2.X, point1.Y); - var newPoint2 = new Point(point1.X, point2.Y); + var newPoint1 = new PointF(point2.X, point1.Y); + var newPoint2 = new PointF(point1.X, point2.Y); point1 = newPoint1; point2 = newPoint2; } if (flip is FlipType.Vertical or FlipType.Both) { - var newPoint1 = new Point(point1.X, point2.Y); - var newPoint2 = new Point(point2.X, point1.Y); + var newPoint1 = new PointF(point1.X, point2.Y); + var newPoint2 = new PointF(point2.X, point1.Y); point1 = newPoint1; point2 = newPoint2; } - CvInvoke.Line(src, point1, point2, color, thickness < 1 ? 1 : thickness, lineType); + CvInvoke.Line(src, point1.ToPoint(midpointRounding), point2.ToPoint(midpointRounding), color, thickness < 1 ? 1 : thickness, lineType); return; } if (sides >= 100) { - CvInvoke.Circle(src, center, radius, color, thickness, lineType); + CvInvoke.Circle(src, center.ToPoint(midpointRounding), (int)Math.Round(diameter / 2, midpointRounding), color, thickness, lineType); return; } - var points = DrawingExtensions.GetPolygonVertices(sides, radius, center, startingAngle, + var points = DrawingExtensions.GetPolygonVertices(sides, diameter, center, startingAngle, flip is FlipType.Horizontal or FlipType.Both, - flip is FlipType.Vertical or FlipType.Both); + flip is FlipType.Vertical or FlipType.Both, + midpointRounding); if (thickness <= 0) { diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs index 956440fc..34595160 100644 --- a/UVtools.Core/Extensions/PointExtensions.cs +++ b/UVtools.Core/Extensions/PointExtensions.cs @@ -79,8 +79,8 @@ public static void Rotate(PointF[] points, double angleDegree, PointF pivot = de public static Point Half(this Point point) => new(point.X / 2, point.Y / 2); - public static PointF Half(this PointF point) => new(point.X / 2, point.Y / 2); - public static Point ToPoint(this PointF point) => new((int) Math.Round(point.X), (int) Math.Round(point.Y)); + public static PointF Half(this PointF point) => new(point.X / 2f, point.Y / 2f); + public static Point ToPoint(this PointF point, MidpointRounding midpointRounding = MidpointRounding.AwayFromZero) => new((int) Math.Round(point.X, midpointRounding), (int) Math.Round(point.Y, midpointRounding)); diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs index c7436a13..e1fc946f 100644 --- a/UVtools.Core/Extensions/SizeExtensions.cs +++ b/UVtools.Core/Extensions/SizeExtensions.cs @@ -138,7 +138,9 @@ public static SizeF Max(params SizeF[] sizes) public static SizeF Half(this SizeF size) => new(size.Width / 2f, size.Height / 2f); public static Point ToPoint(this Size size) => new(size.Width, size.Height); - public static PointF ToPoint(this SizeF size) => new(size.Width, size.Height); + public static PointF ToPointF(this Size size) => new(size.Width, size.Height); + public static Point ToPoint(this SizeF size) => new((int)size.Width, (int)size.Height); + public static PointF ToPointF(this SizeF size) => new(size.Width, size.Height); public static Size Rotate(this Size size, double angleDegree) { diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 4082c6bf..f5597717 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -6822,7 +6822,7 @@ or PixelOperation.PixelOperationType.Text continue; } - mat.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize / 2, operationDrawing.Location, + mat.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize, operationDrawing.Location, new MCvScalar(operationDrawing.Brightness), operationDrawing.RotationAngle, operationDrawing.Thickness, operationDrawing.LineType); /*switch (operationDrawing.BrushShape) { @@ -6840,7 +6840,7 @@ or PixelOperation.PixelOperationType.Text else if (operation.OperationType == PixelOperation.PixelOperationType.Text) { var operationText = (PixelText)operation; - mat.PutTextRotated(operationText.Text, operationText.Location, operationText.Font, operationText.FontScale, new MCvScalar(operationText.Brightness), operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.LineAlignment, operationText.Angle); + mat.PutTextRotated(operationText.Text, operationText.Location, operationText.Font, operationText.FontScale, new MCvScalar(operationText.Brightness), operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.LineAlignment, (double)operationText.Angle); } else if (operation.OperationType == PixelOperation.PixelOperationType.Eraser) { diff --git a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs index e06789b2..3edc2b39 100644 --- a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs +++ b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs @@ -33,6 +33,6 @@ public PolygonAperture(GerberFormat document, int index, double diameter, ushort public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { - mat.DrawPolygon(Vertices, Document.SizeMmToPx(Diameter / 2), Document.PositionMmToPx(at), color, 0, -1, lineType); + mat.DrawPolygon(Vertices, Document.SizeMmToPx(Diameter), Document.PositionMmToPx(at), color, 0, -1, lineType); } } \ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs index cebf8452..7db36c54 100644 --- a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs @@ -91,7 +91,7 @@ public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineTyp if (Diameter <= 0) return; mat.DrawPolygon(VerticesCount, - Document.SizeMmToPx(Diameter / 2), + Document.SizeMmToPx(Diameter), Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), Document.GetPolarityColor(Exposure), Rotation, -1, lineType); } diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs index 9b59706f..405e81ae 100644 --- a/UVtools.Core/Layers/Layer.cs +++ b/UVtools.Core/Layers/Layer.cs @@ -287,17 +287,23 @@ public Point LastPixelPosition public bool IsBottomLayer => _index < SlicerFile.BottomLayerCount; /// - /// Gets if is in the bottom layer group by count and height + /// Gets if this layer is in the bottom layer group by it and height /// public bool IsBottomLayerByHeight { get { var bottomLayers = SlicerFile.BottomLayerCount; - return _index < bottomLayers || PositionZ / SlicerFile.LayerHeight <= bottomLayers; + return _index < bottomLayers || RoundHeight(PositionZ / SlicerFile.LayerHeight) <= bottomLayers; } } + /// + /// Gets if this layer is in the normal layer group by it and height + /// + public bool IsNormalLayerByHeight => !IsBottomLayerByHeight; + + /// /// Gets if is in the normal layer group /// @@ -1894,9 +1900,9 @@ public static Rectangle GetBoundingRectangleUnion(params Layer[] layers) return rect; } - public static float RoundHeight(float height) => (float) Math.Round(height, HeightPrecision); - public static double RoundHeight(double height) => Math.Round(height, HeightPrecision); - public static decimal RoundHeight(decimal height) => Math.Round(height, HeightPrecision); + public static float RoundHeight(float height) => (float) Math.Round(height, HeightPrecision, MidpointRounding.AwayFromZero); + public static double RoundHeight(double height) => Math.Round(height, HeightPrecision, MidpointRounding.AwayFromZero); + public static decimal RoundHeight(decimal height) => Math.Round(height, HeightPrecision, MidpointRounding.AwayFromZero); public static string ShowHeight(float height) => string.Format($"{{0:F{HeightPrecision}}}", height); public static string ShowHeight(double height) => string.Format($"{{0:F{HeightPrecision}}}", height); diff --git a/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs b/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs index 6f5a74e0..ab4beea2 100644 --- a/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs +++ b/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs @@ -394,13 +394,17 @@ protected override bool ExecuteInternally(OperationProgress progress) newLayers.Add(layer); } - SlicerFile.Layers = newLayers.ToArray(); }); + // Fix exposure times + foreach (var layer in SlicerFile) + { + if (layer.IsBottomLayerByHeight) continue; + layer.ExposureTime = (float)_normalExposure; + } progress++; - return !progress.Token.IsCancellationRequested; } diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs index cbe269f2..313d5fa3 100644 --- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs +++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs @@ -1918,7 +1918,7 @@ protected override bool ExecuteInternally(OperationProgress progress) SlicerFile.SuppressRebuildPropertiesWork(() => { - SlicerFile.BottomLayerCount = (ushort)bottomLayerCount; + SlicerFile.BottomLayerCount = bottomLayerCount; SlicerFile.BottomExposureTime = (float)BottomExposure; SlicerFile.ExposureTime = (float)NormalExposure; SlicerFile.Layers = layers.ToArray(); diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs index 717691bc..f7e43e63 100644 --- a/UVtools.Core/Operations/OperationPCBExposure.cs +++ b/UVtools.Core/Operations/OperationPCBExposure.cs @@ -32,8 +32,9 @@ public class OperationPCBExposure : Operation public sealed class PCBExposureFile : GenericFileRepresentation { - public bool _invertPolarity; - public double _sizeScale = 1; + private bool _invertPolarity; + + private double _sizeScale = 1; /// /// Gets or sets to invert the polarity when drawing diff --git a/UVtools.Core/SystemOS/SystemAware.cs b/UVtools.Core/SystemOS/SystemAware.cs index 6be335e5..3148f0be 100644 --- a/UVtools.Core/SystemOS/SystemAware.cs +++ b/UVtools.Core/SystemOS/SystemAware.cs @@ -345,7 +345,7 @@ public static (string standardOutput, string errorOutput) GetProcessOutputWithEr using var outputWaitHandle = new AutoResetEvent(false); using var errorWaitHandle = new AutoResetEvent(false); - process.OutputDataReceived += (sender, e) => + void ProcessOnOutputDataReceived(object sender, DataReceivedEventArgs e) { if (e.Data is null) { @@ -355,9 +355,9 @@ public static (string standardOutput, string errorOutput) GetProcessOutputWithEr { standardOutput.AppendLine(e.Data); } - }; + } - process.ErrorDataReceived += (sender, e) => + void ProcessOnErrorDataReceived(object sender, DataReceivedEventArgs e) { if (e.Data is null) { @@ -367,7 +367,11 @@ public static (string standardOutput, string errorOutput) GetProcessOutputWithEr { errorOutput.AppendLine(e.Data); } - }; + } + + + process.OutputDataReceived += ProcessOnOutputDataReceived; + process.ErrorDataReceived += ProcessOnErrorDataReceived; process.Start(); @@ -378,6 +382,9 @@ public static (string standardOutput, string errorOutput) GetProcessOutputWithEr outputWaitHandle.WaitOne(timeout); errorWaitHandle.WaitOne(timeout); + process.OutputDataReceived -= ProcessOnOutputDataReceived; + process.ErrorDataReceived -= ProcessOnErrorDataReceived; + return (standardOutput.ToString(), errorOutput.ToString()); } @@ -395,7 +402,7 @@ public static string GetProcessOutput(string filename, string? arguments = null, using var outputWaitHandle = new AutoResetEvent(false); - process.OutputDataReceived += (sender, e) => + void ProcessOnOutputDataReceived(object sender, DataReceivedEventArgs e) { if (e.Data is null) { @@ -405,7 +412,9 @@ public static string GetProcessOutput(string filename, string? arguments = null, { stringBuilder.AppendLine(e.Data); } - }; + } + + process.OutputDataReceived += ProcessOnOutputDataReceived; process.Start(); @@ -413,7 +422,9 @@ public static string GetProcessOutput(string filename, string? arguments = null, process.WaitForExit(timeout); outputWaitHandle.WaitOne(timeout); - + + process.OutputDataReceived -= ProcessOnOutputDataReceived; + return stringBuilder.ToString(); } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 6b4d7091..e011e5e6 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -1,7 +1,7 @@ - 4.2.1 + 4.2.2 True ..\documentation\$(AssemblyName).xml @@ -36,7 +36,7 @@ - + diff --git a/UVtools.UI/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs index f71dad14..eff1bb9e 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs @@ -58,7 +58,7 @@ public void UpdatePreview() { using var mat = Operation.GetLayerPreview(); _previewImage?.Dispose(); - PreviewImage = mat.ToBitmapParallel(); + PreviewImage = mat.ToBitmap(); } } } diff --git a/UVtools.UI/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs index bc195058..f4a2fde6 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs @@ -64,7 +64,7 @@ public void UpdatePreview() { var layers = Operation.GetLayers(); _previewImage?.Dispose(); - PreviewImage = layers[0].ToBitmapParallel(); + PreviewImage = layers[0].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs index 5bd75ce3..f9a3ac21 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs @@ -66,7 +66,7 @@ public void UpdatePreview() _previewImage?.Dispose(); if (layers is not null) { - PreviewImage = layers[^1].ToBitmapParallel(); + PreviewImage = layers[^1].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Calibrators/CalibrateGrayscaleControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateGrayscaleControl.axaml.cs index 3f75a6cb..1b3f5a9e 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateGrayscaleControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateGrayscaleControl.axaml.cs @@ -63,7 +63,7 @@ public void UpdatePreview() { var layers = Operation.GetLayers(); _previewImage?.Dispose(); - PreviewImage = layers[2].ToBitmapParallel(); + PreviewImage = layers[2].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs index 62d42cbe..3aaf2e31 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs @@ -58,7 +58,7 @@ public void UpdatePreview() { var layers = Operation.GetLayers(); _previewImage?.Dispose(); - PreviewImage = layers[0].ToBitmapParallel(); + PreviewImage = layers[0].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Calibrators/CalibrateToleranceControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateToleranceControl.axaml.cs index 3b38d53c..b6c9d1ce 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateToleranceControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateToleranceControl.axaml.cs @@ -75,7 +75,7 @@ public void UpdatePreview() { var layers = Operation.GetLayers(); _previewImage?.Dispose(); - PreviewImage = layers[^1].ToBitmapParallel(); + PreviewImage = layers[^1].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs b/UVtools.UI/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs index f377fd2d..80f8184a 100644 --- a/UVtools.UI/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs +++ b/UVtools.UI/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs @@ -75,7 +75,7 @@ public void UpdatePreview() { var layers = Operation.GetLayers(); _previewImage?.Dispose(); - PreviewImage = layers[1].ToBitmapParallel(); + PreviewImage = layers[1].ToBitmap(); foreach (var layer in layers) { layer.Dispose(); diff --git a/UVtools.UI/Controls/Tools/ToolLithophaneControl.axaml.cs b/UVtools.UI/Controls/Tools/ToolLithophaneControl.axaml.cs index 16c651bd..e6daaf6c 100644 --- a/UVtools.UI/Controls/Tools/ToolLithophaneControl.axaml.cs +++ b/UVtools.UI/Controls/Tools/ToolLithophaneControl.axaml.cs @@ -57,7 +57,7 @@ public void UpdatePreview() { using var mat = Operation.GetTargetMat(); _previewImage?.Dispose(); - PreviewImage = mat?.ToBitmapParallel(); + PreviewImage = mat?.ToBitmap(); } public async void SelectFile() diff --git a/UVtools.UI/Controls/Tools/ToolMaskControl.axaml.cs b/UVtools.UI/Controls/Tools/ToolMaskControl.axaml.cs index 8f744e4e..2019703a 100644 --- a/UVtools.UI/Controls/Tools/ToolMaskControl.axaml.cs +++ b/UVtools.UI/Controls/Tools/ToolMaskControl.axaml.cs @@ -30,7 +30,7 @@ public bool IsMaskInverted if(!RaiseAndSetIfChanged(ref _isMaskInverted, value)) return; if (!Operation.HaveInputMask) return; Operation.InvertMask(); - MaskImage = Operation.Mask?.ToBitmapParallel(); + MaskImage = Operation.Mask?.ToBitmap(); } } @@ -97,7 +97,7 @@ public async void ImportImageMask() try { Operation.LoadFromFile(filePath, _isMaskInverted, App.MainWindow.ROI.Size.IsEmpty ? SlicerFile!.Resolution : App.MainWindow.ROI.Size); - MaskImage = Operation.Mask?.ToBitmapParallel(); + MaskImage = Operation.Mask?.ToBitmap(); } catch (Exception e) { @@ -135,6 +135,6 @@ public void GenerateMask() } if (_isMaskInverted) Operation.InvertMask(); - MaskImage = Operation.Mask.ToBitmapParallel(); + MaskImage = Operation.Mask.ToBitmap(); } } \ No newline at end of file diff --git a/UVtools.UI/Controls/Tools/ToolPCBExposureControl.axaml.cs b/UVtools.UI/Controls/Tools/ToolPCBExposureControl.axaml.cs index 4c449b32..d2b7d44a 100644 --- a/UVtools.UI/Controls/Tools/ToolPCBExposureControl.axaml.cs +++ b/UVtools.UI/Controls/Tools/ToolPCBExposureControl.axaml.cs @@ -121,18 +121,18 @@ public void UpdatePreview() if (!OperationPCBExposure.ValidExtensions.Any(extension => _selectedFile.IsExtension(extension)) || !_selectedFile.Exists) return; var file = (OperationPCBExposure.PCBExposureFile)_selectedFile.Clone(); - file._invertPolarity = ExcellonDrillFormat.Extensions.Any(extension => file.IsExtension(extension)); + file.InvertPolarity = ExcellonDrillFormat.Extensions.Any(extension => file.IsExtension(extension)); _previewImage?.Dispose(); using var mat = Operation.GetMat(file); if (_cropPreview) { using var matCropped = mat.CropByBounds(20); - PreviewImage = matCropped.ToBitmapParallel(); + PreviewImage = matCropped.ToBitmap(); } else { - PreviewImage = mat.ToBitmapParallel(); + PreviewImage = mat.ToBitmap(); } } catch (Exception e) diff --git a/UVtools.UI/Controls/Tools/ToolPhasedExposureControl.axaml b/UVtools.UI/Controls/Tools/ToolPhasedExposureControl.axaml index 36632b14..df2e8f08 100644 --- a/UVtools.UI/Controls/Tools/ToolPhasedExposureControl.axaml +++ b/UVtools.UI/Controls/Tools/ToolPhasedExposureControl.axaml @@ -85,6 +85,7 @@ RowHeaderWidth="40" LoadingRow="PhasedExposuresGrid_OnLoadingRow"> + diff --git a/UVtools.UI/Extensions/BitmapExtension.cs b/UVtools.UI/Extensions/BitmapExtension.cs index 17fe4d66..69515755 100644 --- a/UVtools.UI/Extensions/BitmapExtension.cs +++ b/UVtools.UI/Extensions/BitmapExtension.cs @@ -51,6 +51,11 @@ public static int GetPixelPos(this WriteableBitmap bitmap, int x, int y) public static int GetPixelPos(this WriteableBitmap bitmap, System.Drawing.Point location) => bitmap.GetPixelPos(location.X, location.Y); + public static unsafe Span GetPixelByteSpan(this ILockedFramebuffer buffer, int length = 0, int offset = 0) + { + return new(IntPtr.Add(buffer.Address, offset).ToPointer(), length > 0 ? length : buffer.RowBytes * buffer.Size.Height); + } + public static unsafe Span GetPixelSpan(this ILockedFramebuffer buffer, int length = 0, int offset = 0) => new(IntPtr.Add(buffer.Address, offset).ToPointer(), length > 0 ? length : buffer.GetLength()); @@ -119,89 +124,43 @@ public static SKImage ToSkImage(this Mat mat) public static WriteableBitmap ToBitmap(this Mat mat) { - var dataCount = mat.Width * mat.Height; - - var writableBitmap = new WriteableBitmap(new PixelSize(mat.Width, mat.Height), new Vector(96, 96), - PixelFormat.Bgra8888, AlphaFormat.Unpremul); - + var writableBitmap = new WriteableBitmap(new PixelSize(mat.Width, mat.Height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Unpremul); using var lockBuffer = writableBitmap.Lock(); - - unsafe + + switch (mat.NumberOfChannels) { - var targetPixels = (uint*) (void*) lockBuffer.Address; - switch (mat.NumberOfChannels) + case 1: { - //Stopwatch sw = Stopwatch.StartNew(); - // Method 1 (Span copy) - case 1: - var srcPixels1 = (byte*) (void*) mat.DataPointer; - for (var i = 0; i < dataCount; i++) - { - var color = srcPixels1[i]; - targetPixels[i] = (uint) (color | color << 8 | color << 16 | 0xff << 24); - } - - break; - case 3: - var srcPixels2 = (byte*) (void*) mat.DataPointer; - uint pixel = 0; - for (uint i = 0; i < dataCount; i++) - { - targetPixels[i] = (uint)(srcPixels2[pixel++] | srcPixels2[pixel++] << 8 | srcPixels2[pixel++] << 16 | 0xff << 24); - } - - break; - case 4: - - if (mat.Depth == DepthType.Cv8U) - { - var srcPixels4 = (byte*)(void*)mat.DataPointer; - uint pixel4 = 0; - for (uint i = 0; i < dataCount; i++) - { - targetPixels[i] = (uint)(srcPixels4[pixel4++] | srcPixels4[pixel4++] << 8 | srcPixels4[pixel4++] << 16 | srcPixels4[pixel4++] << 24); - } - } - else if (mat.Depth == DepthType.Cv32S) - { - var srcPixels4 = (uint*) (void*) mat.DataPointer; - for (uint i = 0; i < dataCount; i++) - { - targetPixels[i] = srcPixels4[i]; - } - } - - break; + using var convertMat = new Mat(); + CvInvoke.CvtColor(mat, convertMat, ColorConversion.Gray2Bgra); + convertMat.CopyTo(lockBuffer.Address); + break; } - } + case 3: + { + using var convertMat = new Mat(); + CvInvoke.CvtColor(mat, convertMat, ColorConversion.Bgr2Bgra); + convertMat.CopyTo(lockBuffer.Address); + break; + } + case 4: + { + if (mat.IsContinuous) + { + mat.CopyTo(lockBuffer.Address); + } + else + { + var srcSpan = mat.GetDataSpan2D(); + var dstSpan = lockBuffer.GetPixelByteSpan(); + srcSpan.CopyTo(dstSpan); + } - return writableBitmap; - /*Debug.WriteLine($"Method 1 (Span copy): {sw.ElapsedMilliseconds}ms"); - - // Method 2 (OpenCV Convertion + Copy Marshal) - sw.Restart(); - CvInvoke.CvtColor(mat, target, ColorConversion.Bgr2Bgra); - var buffer = target.GetBytes(); - Marshal.Copy(buffer, 0, lockBuffer.Address, buffer.Length); - Debug.WriteLine($"Method 2 (OpenCV Convertion + Copy Marshal): {sw.ElapsedMilliseconds}ms"); - - - //sw.Restart(); - CvInvoke.CvtColor(mat, target, ColorConversion.Bgr2Bgra); - unsafe - { - var srcAddress = (uint*)(void*)target.DataPointer; - var targetAddress = (uint*)(void*)lockBuffer.Address; - *targetAddress = *srcAddress; + break; + } } - //Debug.WriteLine($"Method 3 (OpenCV Convertion + Set Address): {sw.ElapsedMilliseconds}ms"); return writableBitmap; - */ - /* for (var y = 0; y < mat.Height; y++) - { - Marshal.Copy(buffer, y * lockBuffer.RowBytes, new IntPtr(lockBuffer.Address.ToInt64() + y * lockBuffer.RowBytes), lockBuffer.RowBytes); - }*/ } public static WriteableBitmap ToBitmapParallel(this Mat mat) diff --git a/UVtools.UI/LayerCache.cs b/UVtools.UI/LayerCache.cs index 3afe9c8a..7859e70f 100644 --- a/UVtools.UI/LayerCache.cs +++ b/UVtools.UI/LayerCache.cs @@ -6,6 +6,7 @@ * of this license document, but changing it is not allowed. */ +using System; using Avalonia.Media.Imaging; using Avalonia.Skia; using Emgu.CV; @@ -16,11 +17,12 @@ namespace UVtools.UI; -public sealed class LayerCache +public sealed class LayerCache : IDisposable { private Layer? _layer; - //private SKCanvas _canvas; + private Mat? _image; private WriteableBitmap? _bitmap; + private bool disposedValue; public bool IsCached => _layer is not null; @@ -30,30 +32,43 @@ public unsafe Layer? Layer set { //if (ReferenceEquals(_layer, value)) return; - Clear(); _layer = value; Image = _layer?.LayerMat; - if (Image is null) return; - CvInvoke.CvtColor(Image, ImageBgr, ColorConversion.Gray2Bgr); + if (_image is null) + { + Bitmap = null; + return; + } + CvInvoke.CvtColor(_image, ImageBgra, ColorConversion.Gray2Bgra); - ImageSpan = Image.GetBytePointer(); - ImageBgrSpan = ImageBgr.GetBytePointer(); + ImageSpan = _image.GetBytePointer(); + ImageBgraSpan = ImageBgra.GetBytePointer(); } } - public Mat? Image { get; private set; } + public Mat? Image + { + get => _image; + private set + { + _image?.Dispose(); + _image = value; + } + } - public Mat ImageBgr { get; } = new(); + public Mat ImageBgra { get; } = new(); public unsafe byte *ImageSpan { get; private set; } - public unsafe byte *ImageBgrSpan { get; private set; } + public unsafe byte *ImageBgraSpan { get; private set; } public WriteableBitmap? Bitmap { get => _bitmap; - set => _bitmap = value; - //_canvas?.Dispose(); - //_canvas = null; + set + { + _bitmap?.Dispose(); + _bitmap = value; + } } public SKCanvas? Canvas @@ -73,7 +88,31 @@ public SKCanvas? Canvas /// public void Clear() { + _image?.Dispose(); + _bitmap?.Dispose(); + _layer = null; - Image?.Dispose(); + _image = null; + _bitmap = null; + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Clear(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/UVtools.UI/MainWindow.Information.cs b/UVtools.UI/MainWindow.Information.cs index 4108d526..f0f07ee7 100644 --- a/UVtools.UI/MainWindow.Information.cs +++ b/UVtools.UI/MainWindow.Information.cs @@ -125,7 +125,7 @@ public int VisibleThumbnailIndex if (value >= SlicerFile!.ThumbnailsCount) return; if (SlicerFile.Thumbnails[value].IsEmpty) return; - VisibleThumbnailImage = SlicerFile.Thumbnails[value].ToBitmapParallel(); + VisibleThumbnailImage = SlicerFile.Thumbnails[value].ToBitmap(); } } @@ -301,7 +301,7 @@ public void RefreshThumbnail() { if (!IsFileLoaded) return; if (_visibleThumbnailIndex < 0 || _visibleThumbnailIndex >= SlicerFile!.ThumbnailsCount) return; - VisibleThumbnailImage = SlicerFile.Thumbnails[_visibleThumbnailIndex].ToBitmapParallel(); + VisibleThumbnailImage = SlicerFile.Thumbnails[_visibleThumbnailIndex].ToBitmap(); } #endregion diff --git a/UVtools.UI/MainWindow.LayerPreview.cs b/UVtools.UI/MainWindow.LayerPreview.cs index 08fe3db3..e53b999a 100644 --- a/UVtools.UI/MainWindow.LayerPreview.cs +++ b/UVtools.UI/MainWindow.LayerPreview.cs @@ -853,7 +853,7 @@ public void GoMassLayer(object whichObj) public void RefreshLayerImage() { - LayerImageBox.Image = LayerCache.ImageBgr.ToBitmapParallel(); + LayerImageBox.Image = LayerCache.ImageBgra.ToBitmap(); } /// @@ -934,13 +934,13 @@ public unsafe void ShowLayer() var imageSpan = LayerCache.ImageSpan; - var imageBgrSpan = LayerCache.ImageBgrSpan; + var imageBgrSpan = LayerCache.ImageBgraSpan; if (_showLayerOutlineEdgeDetection) { using var canny = new Mat(); CvInvoke.Canny(LayerCache.Image, canny, 80, 40, 3, true); - CvInvoke.CvtColor(canny, LayerCache.ImageBgr, ColorConversion.Gray2Bgr); + CvInvoke.CvtColor(canny, LayerCache.ImageBgra, ColorConversion.Gray2Bgra); } else if (_showLayerOutlineDistanceDetection) { @@ -948,12 +948,12 @@ public unsafe void ShowLayer() CvInvoke.DistanceTransform(LayerCache.Image, distance, null, DistType.C, 3); //distance.ConvertTo(distance, DepthType.Cv8U); CvInvoke.Normalize(distance, distance, byte.MinValue, byte.MaxValue, NormType.MinMax, DepthType.Cv8U); - CvInvoke.CvtColor(distance, LayerCache.ImageBgr, ColorConversion.Gray2Bgr); + CvInvoke.CvtColor(distance, LayerCache.ImageBgra, ColorConversion.Gray2Bgra); } else if (_showLayerOutlineSkeletonize) { using var skeletonize = LayerCache.Image.Skeletonize(); - CvInvoke.CvtColor(skeletonize, LayerCache.ImageBgr, ColorConversion.Gray2Bgr); + CvInvoke.CvtColor(skeletonize, LayerCache.ImageBgra, ColorConversion.Gray2Bgra); } else if (_showLayerImageDifference) { @@ -1019,9 +1019,8 @@ public unsafe void ShowLayer() int width = LayerCache.Image.GetRealStep(); - int channels = LayerCache.ImageBgr.NumberOfChannels; - bool showSimilarityInstead = - Settings.LayerPreview.LayerDifferenceHighlightSimilarityInstead; + int channels = LayerCache.ImageBgra.NumberOfChannels; + bool showSimilarityInstead = Settings.LayerPreview.LayerDifferenceHighlightSimilarityInstead; Parallel.For(rect.Y, rect.Bottom, CoreSettings.ParallelOptions, y => { @@ -1064,7 +1063,7 @@ public unsafe void ShowLayer() imageBgrSpan[bgrPixel] = color.B; // B imageBgrSpan[bgrPixel + 1] = color.G; // G imageBgrSpan[bgrPixel + 2] = color.R; // R - //imageBgrSpan[++bgrPixel] = color.A; // A + imageBgrSpan[bgrPixel + 3] = color.A; // A } }); } @@ -1145,7 +1144,7 @@ is not MainIssue.IssueType.PrintHeight case IssueOfContours issueOfContours: { using var vec = new VectorOfVectorOfPoint(issueOfContours.Contours); - CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, new MCvScalar(color.B, color.G, color.R), -1); + CvInvoke.DrawContours(LayerCache.ImageBgra, vec, -1, color.ToMCvScalar(), -1); break; } case IssueOfPoints issueOfPoints: @@ -1156,7 +1155,7 @@ is not MainIssue.IssueType.PrintHeight byte brightness = imageSpan[pixelPos]; if (brightness == 0) continue; - int pixelBgrPos = pixelPos * LayerCache.ImageBgr.NumberOfChannels; + int pixelBgrPos = pixelPos * LayerCache.ImageBgra.NumberOfChannels; var newColor = color.FactorColor(brightness, 80); @@ -1174,18 +1173,15 @@ is not MainIssue.IssueType.PrintHeight if (_showLayerOutlinePrintVolumeBoundary) { - CvInvoke.Rectangle(LayerCache.ImageBgr, SlicerFile.BoundingRectangle, - new MCvScalar(Settings.LayerPreview.VolumeBoundsOutlineColor.B, - Settings.LayerPreview.VolumeBoundsOutlineColor.G, - Settings.LayerPreview.VolumeBoundsOutlineColor.R), + CvInvoke.Rectangle(LayerCache.ImageBgra, SlicerFile.BoundingRectangle, + Settings.LayerPreview.VolumeBoundsOutlineColor.ToMCvScalar(), Settings.LayerPreview.VolumeBoundsOutlineThickness); } if (_showLayerOutlineLayerBoundary && !SlicerFile[_actualLayer].BoundingRectangle.IsEmpty) { - CvInvoke.Rectangle(LayerCache.ImageBgr, SlicerFile[_actualLayer].BoundingRectangle, - new MCvScalar(Settings.LayerPreview.LayerBoundsOutlineColor.B, - Settings.LayerPreview.LayerBoundsOutlineColor.G, Settings.LayerPreview.LayerBoundsOutlineColor.R), + CvInvoke.Rectangle(LayerCache.ImageBgra, SlicerFile[_actualLayer].BoundingRectangle, + Settings.LayerPreview.LayerBoundsOutlineColor.ToMCvScalar(), Settings.LayerPreview.LayerBoundsOutlineThickness); } @@ -1207,11 +1203,8 @@ is not MainIssue.IssueType.PrintHeight continue; } - CvInvoke.Rectangle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].BoundingRectangle, - new MCvScalar( - Settings.LayerPreview.ContourBoundsOutlineColor.B, - Settings.LayerPreview.ContourBoundsOutlineColor.G, - Settings.LayerPreview.ContourBoundsOutlineColor.R), + CvInvoke.Rectangle(LayerCache.ImageBgra, LayerCache.Layer.Contours[i].BoundingRectangle, + Settings.LayerPreview.ContourBoundsOutlineColor.ToMCvScalar(), Settings.LayerPreview.ContourBoundsOutlineThickness); lastParent = parent; @@ -1222,11 +1215,9 @@ is not MainIssue.IssueType.PrintHeight { for (int i = 0; i < LayerCache.Layer.Contours.Count; i++) { - LayerCache.Layer.Contours[i].FitCircle(LayerCache.ImageBgr, - new MCvScalar( - Settings.LayerPreview.EnclosingCirclesOutlineColor.B, - Settings.LayerPreview.EnclosingCirclesOutlineColor.G, - Settings.LayerPreview.EnclosingCirclesOutlineColor.R), Settings.LayerPreview.EnclosingCirclesOutlineThickness, LineType.AntiAlias); + LayerCache.Layer.Contours[i].FitCircle(LayerCache.ImageBgra, + Settings.LayerPreview.EnclosingCirclesOutlineColor.ToMCvScalar(), + Settings.LayerPreview.EnclosingCirclesOutlineThickness, LineType.AntiAlias); } } @@ -1243,10 +1234,8 @@ is not MainIssue.IssueType.PrintHeight using var vec = EmguContours.GetNegativeContours(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy); if (vec.Size > 0) { - CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, - new MCvScalar(Settings.LayerPreview.HollowOutlineColor.B, - Settings.LayerPreview.HollowOutlineColor.G, - Settings.LayerPreview.HollowOutlineColor.R), + CvInvoke.DrawContours(LayerCache.ImageBgra, vec, -1, + Settings.LayerPreview.HollowOutlineColor.ToMCvScalar(), Settings.LayerPreview.HollowOutlineLineThickness); } } @@ -1267,24 +1256,17 @@ is not MainIssue.IssueType.PrintHeight { if (Settings.LayerPreview.CentroidOutlineHollow) { - CvInvoke.Circle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].Centroid, - Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( - Settings.LayerPreview.HollowOutlineColor.B, - Settings.LayerPreview.HollowOutlineColor.G, - Settings.LayerPreview.HollowOutlineColor.R), 1, LineType.AntiAlias); + CvInvoke.Circle(LayerCache.ImageBgra, LayerCache.Layer.Contours[i].Centroid, + Settings.LayerPreview.CentroidOutlineDiameter / 2, Settings.LayerPreview.HollowOutlineColor.ToMCvScalar(), 1, LineType.AntiAlias); } } else { - CvInvoke.Circle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].Centroid, - Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( - Settings.LayerPreview.CentroidOutlineColor.B, - Settings.LayerPreview.CentroidOutlineColor.G, - Settings.LayerPreview.CentroidOutlineColor.R), -1, LineType.AntiAlias); + CvInvoke.Circle(LayerCache.ImageBgra, LayerCache.Layer.Contours[i].Centroid, + Settings.LayerPreview.CentroidOutlineDiameter / 2, Settings.LayerPreview.CentroidOutlineColor.ToMCvScalar(), + -1, LineType.AntiAlias); } - - lastParent = parent; } } @@ -1292,14 +1274,12 @@ is not MainIssue.IssueType.PrintHeight if (_showLayerOutlineTriangulate) { var groups = EmguContours.GetPositiveContoursInGroups(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy); - var lineColor = new MCvScalar( - Settings.LayerPreview.TriangulateOutlineColor.B, - Settings.LayerPreview.TriangulateOutlineColor.G, - Settings.LayerPreview.TriangulateOutlineColor.R); + var lineColor = Settings.LayerPreview.TriangulateOutlineColor.ToMCvScalar(); var dotColor = new MCvScalar( byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.B, byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.G, - byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.R); + byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.R, + Settings.LayerPreview.TriangulateOutlineColor.A); uint triangleCount = 0; @@ -1327,11 +1307,11 @@ is not MainIssue.IssueType.PrintHeight triangle.V2.ToPoint() }; - CvInvoke.Polylines(LayerCache.ImageBgr, points, true, lineColor, Settings.LayerPreview.TriangulateOutlineLineThickness); + CvInvoke.Polylines(LayerCache.ImageBgra, points, true, lineColor, Settings.LayerPreview.TriangulateOutlineLineThickness); - CvInvoke.Circle(LayerCache.ImageBgr, points[0], 2, dotColor, -1); - CvInvoke.Circle(LayerCache.ImageBgr, points[1], 2, dotColor, -1); - CvInvoke.Circle(LayerCache.ImageBgr, points[2], 2, dotColor, -1); + CvInvoke.Circle(LayerCache.ImageBgra, points[0], 2, dotColor, -1); + CvInvoke.Circle(LayerCache.ImageBgra, points[1], 2, dotColor, -1); + CvInvoke.Circle(LayerCache.ImageBgra, points[2], 2, dotColor, -1); } triangleCount += (uint)triangles.Length; @@ -1339,7 +1319,7 @@ is not MainIssue.IssueType.PrintHeight if (triangleCount > 0 && Settings.LayerPreview.TriangulateOutlineShowCount) { - CvInvoke.PutText(LayerCache.ImageBgr, $"Triangles: {triangleCount:N0}", new Point(10, 80), FontFace.HersheyDuplex, 3, dotColor, 3); + CvInvoke.PutText(LayerCache.ImageBgra, $"Triangles: {triangleCount:N0}", new Point(10, 80), FontFace.HersheyDuplex, 3, dotColor, 3); } } @@ -1347,10 +1327,8 @@ is not MainIssue.IssueType.PrintHeight if (_maskPoints is not null && _maskPoints.Count > 0) { using var vec = new VectorOfVectorOfPoint(_maskPoints.ToArray()); - CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, - new MCvScalar(Settings.LayerPreview.MaskOutlineColor.B, - Settings.LayerPreview.MaskOutlineColor.G, - Settings.LayerPreview.MaskOutlineColor.R), + CvInvoke.DrawContours(LayerCache.ImageBgra, vec, -1, + Settings.LayerPreview.MaskOutlineColor.ToMCvScalar(), Settings.LayerPreview.MaskOutlineLineThickness); } @@ -1370,23 +1348,22 @@ is not MainIssue.IssueType.PrintHeight : Settings.PixelEditor.RemovePixelColor); if (operationDrawing.BrushSize == 1) { - LayerCache.ImageBgr.SetByte(operation.Location.X, operation.Location.Y, - new[] {color.B, color.G, color.R}); + LayerCache.ImageBgra.SetByte(operation.Location.X, operation.Location.Y, new[] {color.B, color.G, color.R, color.A}); continue; } - LayerCache.ImageBgr.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize / 2, operationDrawing.Location, - new MCvScalar(color.B, color.G, color.R), operationDrawing.RotationAngle, operationDrawing.Thickness, operationDrawing.LineType); + LayerCache.ImageBgra.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize, operationDrawing.Location, + color.ToMCvScalar(), operationDrawing.RotationAngle, operationDrawing.Thickness, operationDrawing.LineType); /*switch (operationDrawing.BrushShape) { case PixelDrawing.BrushShapeType.Square: CvInvoke.Rectangle(LayerCache.ImageBgr, operationDrawing.Rectangle, - new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness, + color.ToMCvScalar(), operationDrawing.Thickness, operationDrawing.LineType); break; case PixelDrawing.BrushShapeType.Circle: CvInvoke.Circle(LayerCache.ImageBgr, operation.Location, operationDrawing.BrushSize / 2, - new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness, + color.ToMCvScalar(), operationDrawing.Thickness, operationDrawing.LineType); break; default: @@ -1404,14 +1381,9 @@ is not MainIssue.IssueType.PrintHeight ? Settings.PixelEditor.RemovePixelHighlightColor : Settings.PixelEditor.RemovePixelColor); - - - /*CvInvoke.PutText(LayerCache.ImageBgr, operationText.Text, operationText.Location, - operationText.Font, operationText.FontScale, new MCvScalar(color.B, color.G, color.R), - operationText.Thickness, operationText.LineType, operationText.Mirror);*/ - LayerCache.ImageBgr.PutTextRotated(operationText.Text, operationText.Location, - operationText.Font, operationText.FontScale, new MCvScalar(color.B, color.G, color.R), - operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.LineAlignment, operationText.Angle); + LayerCache.ImageBgra.PutTextRotated(operationText.Text, operationText.Location, + operationText.Font, operationText.FontScale, color.ToMCvScalar(), + operationText.Thickness, operationText.LineType, operationText.Mirror, operationText.LineAlignment, (double)operationText.Angle); } else if (operation.OperationType == PixelOperation.PixelOperationType.Eraser) { @@ -1422,7 +1394,7 @@ is not MainIssue.IssueType.PrintHeight : Settings.PixelEditor.RemovePixelColor; using var vec = EmguContours.GetContoursInside(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy, operation.Location); - if (vec.Size > 0) CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, new MCvScalar(color.B, color.G, color.R), -1); + if (vec.Size > 0) CvInvoke.DrawContours(LayerCache.ImageBgra, vec, -1, color.ToMCvScalar(), -1); /*var hollowGroups = EmguContours.GetPositiveContoursInGroups(LayerCache.LayerContours, LayerCache.LayerContourHierarchy); @@ -1441,8 +1413,8 @@ is not MainIssue.IssueType.PrintHeight ? Settings.PixelEditor.SupportsHighlightColor : Settings.PixelEditor.SupportsColor; - CvInvoke.Circle(LayerCache.ImageBgr, operation.Location, operationSupport.TipDiameter / 2, - new MCvScalar(color.B, color.G, color.R), -1); + CvInvoke.Circle(LayerCache.ImageBgra, operation.Location, operationSupport.TipDiameter / 2, + color.ToMCvScalar(), -1); } else if (operation.OperationType == PixelOperation.PixelOperationType.DrainHole) { @@ -1451,8 +1423,7 @@ is not MainIssue.IssueType.PrintHeight ? Settings.PixelEditor.DrainHoleHighlightColor : Settings.PixelEditor.DrainHoleColor; - CvInvoke.Circle(LayerCache.ImageBgr, operation.Location, operationDrainHole.Diameter / 2, - new MCvScalar(color.B, color.G, color.R), -1); + CvInvoke.Circle(LayerCache.ImageBgra, operation.Location, operationDrainHole.Diameter / 2, color.ToMCvScalar(), -1); } } @@ -1491,15 +1462,15 @@ and not MainIssue.IssueType.PrintHeight else if (_showLayerImageFlippedVertically) flipType = FlipType.Vertical; - CvInvoke.Flip(LayerCache.ImageBgr, LayerCache.ImageBgr, flipType); + CvInvoke.Flip(LayerCache.ImageBgra, LayerCache.ImageBgra, flipType); } if (_showLayerImageRotated) { - CvInvoke.Rotate(LayerCache.ImageBgr, LayerCache.ImageBgr, _showLayerImageRotateCcwDirection ? RotateFlags.Rotate90CounterClockwise : RotateFlags.Rotate90Clockwise); + CvInvoke.Rotate(LayerCache.ImageBgra, LayerCache.ImageBgra, _showLayerImageRotateCcwDirection ? RotateFlags.Rotate90CounterClockwise : RotateFlags.Rotate90Clockwise); } - LayerImageBox.Image = LayerCache.Bitmap = LayerCache.ImageBgr.ToBitmapParallel(); + LayerImageBox.Image = LayerCache.Bitmap = LayerCache.ImageBgra.ToBitmap(); RefreshCurrentLayerData(); @@ -1523,8 +1494,7 @@ public void DrawCrosshair(Rectangle rect) // This prevents the crosshair lines from disappearing due to being too thin to // render at very low zoom factors. var lineThickness = (LayerImageBox.Zoom > 100) ? 1 : (LayerImageBox.Zoom < 50) ? 3 : 2; - var color = new MCvScalar(Settings.LayerPreview.CrosshairColor.B, Settings.LayerPreview.CrosshairColor.G, - Settings.LayerPreview.CrosshairColor.R); + var color = Settings.LayerPreview.CrosshairColor.ToMCvScalar(); // LEFT @@ -1535,7 +1505,7 @@ public void DrawCrosshair(Rectangle rect) ? 0 : (int)Math.Max(0, startPoint.X - Settings.LayerPreview.CrosshairLength + 1)}; - CvInvoke.Line(LayerCache.ImageBgr, + CvInvoke.Line(LayerCache.ImageBgra, startPoint, endPoint, color, @@ -1543,13 +1513,13 @@ public void DrawCrosshair(Rectangle rect) // RIGHT - startPoint.X = Math.Min(LayerCache.ImageBgr.Width, + startPoint.X = Math.Min(LayerCache.ImageBgra.Width, rect.Right + Settings.LayerPreview.CrosshairMargin); endPoint.X = Settings.LayerPreview.CrosshairLength == 0 - ? LayerCache.ImageBgr.Width - : (int)Math.Min(LayerCache.ImageBgr.Width, startPoint.X + Settings.LayerPreview.CrosshairLength - 1); + ? LayerCache.ImageBgra.Width + : (int)Math.Min(LayerCache.ImageBgra.Width, startPoint.X + Settings.LayerPreview.CrosshairLength - 1); - CvInvoke.Line(LayerCache.ImageBgr, + CvInvoke.Line(LayerCache.ImageBgra, startPoint, endPoint, color, @@ -1563,19 +1533,19 @@ public void DrawCrosshair(Rectangle rect) : Math.Max(0, startPoint.Y - Settings.LayerPreview.CrosshairLength + 1))}; - CvInvoke.Line(LayerCache.ImageBgr, + CvInvoke.Line(LayerCache.ImageBgra, startPoint, endPoint, color, lineThickness); // Bottom - startPoint.Y = Math.Min(LayerCache.ImageBgr.Height, rect.Bottom + Settings.LayerPreview.CrosshairMargin); + startPoint.Y = Math.Min(LayerCache.ImageBgra.Height, rect.Bottom + Settings.LayerPreview.CrosshairMargin); endPoint.Y = Settings.LayerPreview.CrosshairLength == 0 - ? LayerCache.ImageBgr.Height - : (int)Math.Min(LayerCache.ImageBgr.Height, startPoint.Y + Settings.LayerPreview.CrosshairLength - 1); + ? LayerCache.ImageBgra.Height + : (int)Math.Min(LayerCache.ImageBgra.Height, startPoint.Y + Settings.LayerPreview.CrosshairLength - 1); - CvInvoke.Line(LayerCache.ImageBgr, + CvInvoke.Line(LayerCache.ImageBgra, startPoint, endPoint, color, @@ -2317,7 +2287,7 @@ public async void SaveCurrentLayerImage() using var file = await SaveFilePickerAsync(SlicerFile!.DirectoryPath, $"{SlicerFile.FilenameNoExt}_layer{ActualLayer}.png", AvaloniaStatic.PngFileFilter); if (file?.TryGetLocalPath() is not { } filePath) return; - LayerCache.ImageBgr.Save(filePath); + LayerCache.ImageBgra.Save(filePath); } public async void SaveCurrentROIImage() @@ -2331,11 +2301,11 @@ public async void SaveCurrentROIImage() LayerImageBox.GetSelectedBitmap()?.Save(filePath); } - const byte _pixelEditorCursorMinDiamater = 10; + const byte PixelEditorCursorMinDiameter = 10; public void UpdatePixelEditorCursor() { Mat? cursor = null; - var _pixelEditorCursorColor = new MCvScalar( + var pixelEditorCursorColor = new MCvScalar( Settings.PixelEditor.CursorColor.B, Settings.PixelEditor.CursorColor.G, Settings.PixelEditor.CursorColor.R, @@ -2348,15 +2318,27 @@ public void UpdatePixelEditorCursor() { if ((byte)DrawingPixelDrawing.BrushShape >= 1) { - int cursorSize = DrawingPixelDrawing.BrushSize; + int cursorSize = DrawingPixelDrawing.BrushSize + 1; if (DrawingPixelDrawing.Thickness > 1) { cursorSize += DrawingPixelDrawing.Thickness; } - cursor = EmguExtensions.InitMat(new Size(cursorSize, cursorSize), 4); - cursor.DrawPolygon((byte) DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.BrushSize / 2, cursor.Size.ToPoint().Half(), - _pixelEditorCursorColor, DrawingPixelDrawing.RotationAngle, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.LineType); + //if (cursorSize % 2 != 0) cursorSize++; + + cursor = EmguExtensions.InitMat(new Size(cursorSize, cursorSize), 4); + //cursor.SetTo(new MCvScalar(255,255,255,255)); // Debug + + /*FlipType? flip = null; + if (_showLayerImageFlipped) + { + if (_showLayerImageFlippedHorizontally && _showLayerImageFlippedVertically) flip = FlipType.Both; + else if (_showLayerImageFlippedHorizontally) flip = FlipType.Horizontal; + else if (_showLayerImageFlippedVertically) flip = FlipType.Vertical; + }*/ + + cursor.DrawPolygon((byte) DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.BrushSize, new PointF(cursor.Width / 2.0f, cursor.Height / 2.0f), + pixelEditorCursorColor, DrawingPixelDrawing.RotationAngle, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.LineType); if (DrawingPixelDrawing.BrushShape != PixelDrawing.BrushShapeType.Circle) { @@ -2428,7 +2410,7 @@ public void UpdatePixelEditorCursor() //_pixelEditorCursorColor.V3 = 255; //CvInvoke.Rectangle(cursor, new Rectangle(new Point(size.Width, 0), size), _pixelEditorCursorColor, 1, DrawingPixelText.LineType); - cursor.PutTextExtended(text, size.ToPoint(), DrawingPixelText.Font, DrawingPixelText.FontScale, _pixelEditorCursorColor, DrawingPixelText.Thickness, DrawingPixelText.LineType, DrawingPixelText.Mirror, DrawingPixelText.LineAlignment); + cursor.PutTextExtended(text, size.ToPoint(), DrawingPixelText.Font, DrawingPixelText.FontScale, pixelEditorCursorColor, DrawingPixelText.Thickness, DrawingPixelText.LineType, DrawingPixelText.Mirror, DrawingPixelText.LineAlignment); //CvInvoke.PutText(cursor, text, size.ToPoint(), DrawingPixelText.Font, DrawingPixelText.FontScale, _pixelEditorCursorColor, DrawingPixelText.Thickness, DrawingPixelText.LineType, DrawingPixelText.Mirror); cursor.RotateAdjustBounds(DrawingPixelText.Angle); //cursor.Rotate(DrawingPixelText.Angle); @@ -2457,21 +2439,21 @@ public void UpdatePixelEditorCursor() var diameter = SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Supports ? DrawingPixelSupport.TipDiameter : DrawingPixelDrainHole.Diameter; - if (diameter >= _pixelEditorCursorMinDiamater) + if (diameter >= PixelEditorCursorMinDiameter) { cursor = EmguExtensions.InitMat(new Size(diameter, diameter), 4); var center = new Point(diameter / 2, diameter / 2); CvInvoke.Circle(cursor, center, center.X, - _pixelEditorCursorColor, + pixelEditorCursorColor, -1, LineType.AntiAlias ); - _pixelEditorCursorColor.V3 = 255; + pixelEditorCursorColor.V3 = 255; CvInvoke.Circle(cursor, center, center.X, - _pixelEditorCursorColor, + pixelEditorCursorColor, 1, LineType.AntiAlias ); } @@ -2480,7 +2462,13 @@ public void UpdatePixelEditorCursor() if (cursor is not null) { - LayerImageBox.TrackerImage = cursor.ToBitmapParallel(); + /*using var cursorGrey = new Mat(); + CvInvoke.CvtColor(cursor, cursorGrey, ColorConversion.Bgra2Gray); + var bounds = CvInvoke.BoundingRectangle(cursorGrey); + using var cursorRoi = new Mat(cursor, bounds);*/ + + LayerImageBox.TrackerImage = cursor.ToBitmap(); + //cursorRoi.Save("D:\\Cursor.png"); //LayerImageBox.Cursor = new Cursor(cursor.ToBitmap(), new PixelPoint(cursor.Width / 2, cursor.Height / 2)); //cursor.Save("D:\\Cursor.png"); //LayerImageBox.TrackerImage.Save("D:\\CursorAVA.png"); diff --git a/UVtools.UI/MainWindow.PixelEditor.cs b/UVtools.UI/MainWindow.PixelEditor.cs index 119e612d..898614e4 100644 --- a/UVtools.UI/MainWindow.PixelEditor.cs +++ b/UVtools.UI/MainWindow.PixelEditor.cs @@ -8,11 +8,9 @@ using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Media.Imaging; using Avalonia.Threading; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using SkiaSharp; using System; using System.Collections.Generic; @@ -189,11 +187,6 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) return; } - WriteableBitmap bitmap = LayerImageBox.ImageAsWriteableBitmap!; - //var context = CreateRenderTarget().CreateDrawingContext(bitmap); - - - //Bitmap bmp = pbLayer.Image as Bitmap; if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Drawing) { var drawings = new List(); @@ -221,13 +214,14 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) continue; } - int halfBrush = operationDrawing.BrushSize / 2; - double angle = operationDrawing.RotationAngle; + float halfBrush = operationDrawing.BrushSize / 2f; + var angle = operationDrawing.RotationAngle; switch (operationDrawing.BrushShape) { case PixelDrawing.BrushShapeType.Line: - Point point1 = location with {X = location.X - halfBrush}; - Point point2 = location with {X = location.X + halfBrush}; + { + var point1 = location with { X = (int)Math.Round(location.X - halfBrush, MidpointRounding.AwayFromZero) }; + var point2 = point1 with { X = point1.X + operationDrawing.BrushSize }; if (_showLayerImageRotated) { @@ -243,7 +237,7 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) point1 = point1.Rotate(angle, location); point2 = point2.Rotate(angle, location); - + if (_showLayerImageFlipped) { @@ -293,19 +287,21 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) }*/ - - LayerCache.Canvas?.DrawLine(point1.X, point1.Y, point2.X, point2.Y, new SKPaint + using var linePaint = new SKPaint { IsAntialias = operationDrawing.LineType == LineType.AntiAlias, Color = new SKColor(color.ToUint32()), IsStroke = operationDrawing.Thickness >= 0, StrokeWidth = operationDrawing.Thickness, StrokeCap = SKStrokeCap.Round - }); + }; + + LayerCache.Canvas?.DrawLine(point1.X, point1.Y, point2.X, point2.Y, linePaint); break; + } /*case PixelDrawing.BrushShapeType.Square: - LayerCache.Canvas.DrawRect(location.X - halfBrush, location.Y - halfBrush, - operationDrawing.BrushSize, + LayerCache.Canvas.DrawRect(location.X - halfBrush, location.Y - halfBrush, + operationDrawing.BrushSize, operationDrawing.BrushSize, new SKPaint { @@ -319,23 +315,27 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) operationDrawing.LineType);*/ //break; case PixelDrawing.BrushShapeType.Circle: - LayerCache.Canvas?.DrawCircle(location.X, location.Y, operationDrawing.BrushSize / 2f, - new SKPaint - { - IsAntialias = operationDrawing.LineType == LineType.AntiAlias, - Color = new SKColor(color.ToUint32()), - IsStroke = operationDrawing.Thickness >= 0, - StrokeWidth = operationDrawing.Thickness - }); - + { + using var circlePaint = new SKPaint + { + IsAntialias = operationDrawing.LineType == LineType.AntiAlias, + Color = new SKColor(color.ToUint32()), + IsStroke = operationDrawing.Thickness >= 0, + StrokeWidth = operationDrawing.Thickness + }; + LayerCache.Canvas?.DrawCircle(location.X, location.Y, operationDrawing.BrushSize / 2f, circlePaint); + /*CvInvoke.Circle(LayerCache.ImageBgr, location, operationDrawing.BrushSize / 2, new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness, operationDrawing.LineType);*/ break; + } default: + { if (_showLayerImageRotated) { - if (!_showLayerImageFlipped || _showLayerImageFlippedHorizontally && _showLayerImageFlippedVertically) + if (!_showLayerImageFlipped || _showLayerImageFlippedHorizontally && + _showLayerImageFlippedVertically) { if (_showLayerImageRotateCcwDirection) { @@ -359,36 +359,49 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) } } - - var vertices = DrawingExtensions.GetPolygonVertices((byte) operationDrawing.BrushShape, - operationDrawing.BrushSize / 2, location, angle, _showLayerImageFlipped && _showLayerImageFlippedHorizontally, _showLayerImageFlipped && _showLayerImageFlippedVertically); + var vertices = DrawingExtensions.GetPolygonVertices((byte)operationDrawing.BrushShape, + operationDrawing.BrushSize, + location, angle, _showLayerImageFlipped && _showLayerImageFlippedHorizontally, + _showLayerImageFlipped && _showLayerImageFlippedVertically); //if(angle % 360 != 0) PointExtensions.Rotate(vertices, angle, location); - var path = new SKPath(); + using var canvas = LayerCache.Canvas; + using var linePaint = new SKPaint + { + IsAntialias = operationDrawing.LineType == LineType.AntiAlias, + Color = new SKColor(color.ToUint32()), + IsStroke = true, + StrokeWidth = operationDrawing.Thickness, + StrokeJoin = SKStrokeJoin.Round + }; + + using var fillPaint = new SKPaint + { + IsAntialias = operationDrawing.LineType == LineType.AntiAlias, + Color = new SKColor(color.ToUint32()), + IsStroke = operationDrawing.Thickness >= 0, + StrokeWidth = operationDrawing.Thickness, + StrokeJoin = SKStrokeJoin.Round, + }; + + using var path = new SKPath(); path.MoveTo(vertices[0].X, vertices[0].Y); + canvas!.DrawPoint(vertices[0].X, vertices[0].Y, linePaint); for (var i = 1; i < vertices.Length; i++) { path.LineTo(vertices[i].X, vertices[i].Y); + canvas.DrawLine(vertices[i - 1].X, vertices[i - 1].Y, vertices[i].X, vertices[i].Y, linePaint); + canvas.DrawPoint(vertices[i].X, vertices[i].Y, linePaint); } + + canvas.DrawLine(vertices[0].X, vertices[0].Y, vertices[^1].X, vertices[^1].Y, linePaint); path.Close(); - LayerCache.Canvas?.DrawPath(path, new SKPaint - { - IsAntialias = operationDrawing.LineType == LineType.AntiAlias, - Color = new SKColor(color.ToUint32()), - IsStroke = operationDrawing.Thickness >= 0, - StrokeWidth = operationDrawing.Thickness - }); - /*LayerCache.Canvas.DrawPoints(SKPointMode.Polygon, points, - new SKPaint - { - IsAntialias = operationDrawing.LineType == LineType.AntiAlias, - Color = new SKColor(color.ToUint32()), - IsStroke = operationDrawing.Thickness >= 0, - StrokeWidth = operationDrawing.Thickness - });*/ + canvas.DrawPath(path, fillPaint); + break; + } } LayerImageBox.InvalidateVisual(); //RefreshLayerImage(); @@ -477,8 +490,8 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) //if (PixelHistory.Contains(operation)) return; AddDrawing(operationSupport); - CvInvoke.Circle(LayerCache.ImageBgr, location, operationSupport.TipDiameter / 2, - new MCvScalar(Settings.PixelEditor.SupportsColor.B, Settings.PixelEditor.SupportsColor.G, Settings.PixelEditor.SupportsColor.R), -1); + CvInvoke.Circle(LayerCache.ImageBgra, location, operationSupport.TipDiameter / 2, + Settings.PixelEditor.SupportsColor.ToMCvScalar(), -1); RefreshLayerImage(); return; } @@ -490,8 +503,8 @@ void DrawPixel(bool isAdd, Point location, KeyModifiers keyModifiers) //if (PixelHistory.Contains(operation)) return; AddDrawing(operationDrainHole); - CvInvoke.Circle(LayerCache.ImageBgr, location, operationDrainHole.Diameter / 2, - new MCvScalar(Settings.PixelEditor.DrainHoleColor.B, Settings.PixelEditor.DrainHoleColor.G, Settings.PixelEditor.DrainHoleColor.R), -1); + CvInvoke.Circle(LayerCache.ImageBgra, location, operationDrainHole.Diameter / 2, + Settings.PixelEditor.DrainHoleColor.ToMCvScalar(), -1); RefreshLayerImage(); return; } diff --git a/UVtools.UI/MainWindow.axaml.cs b/UVtools.UI/MainWindow.axaml.cs index 7c5fdaf2..91da8f1e 100644 --- a/UVtools.UI/MainWindow.axaml.cs +++ b/UVtools.UI/MainWindow.axaml.cs @@ -1308,6 +1308,17 @@ public async void ProcessFiles(string[] files, bool openNewWindow = false, FileF { if (files.Length == 0) return; + if (files.All(s => OperationPCBExposure.ValidExtensions.Any(extension => s.EndsWith($".{extension}", StringComparison.OrdinalIgnoreCase)))) + { + if (!IsFileLoaded) return; + var operation = new OperationPCBExposure(); + operation.AddFiles(files); + if (operation.Count == 0) return; + if ((_globalModifiers & KeyModifiers.Shift) != 0) await RunOperation(operation); + else await ShowRunOperation(operation); + return; + } + for (int i = 0; i < files.Length; i++) { if (!File.Exists(files[i])) continue; @@ -1353,6 +1364,7 @@ public async void ProcessFiles(string[] files, bool openNewWindow = false, FileF continue; } + if (FileFormat.FindByExtensionOrFilePath(files[i]) is null) continue; if (i == 0 && !openNewWindow && (_globalModifiers & KeyModifiers.Shift) == 0) { @@ -1361,7 +1373,6 @@ public async void ProcessFiles(string[] files, bool openNewWindow = false, FileF } App.NewInstance(files[i]); - } } diff --git a/UVtools.UI/Structures/Color.cs b/UVtools.UI/Structures/Color.cs index 7c247519..15d8d4d5 100644 --- a/UVtools.UI/Structures/Color.cs +++ b/UVtools.UI/Structures/Color.cs @@ -7,6 +7,7 @@ */ using Avalonia.Media; using System; +using Emgu.CV.Structure; namespace UVtools.UI.Structures; @@ -77,7 +78,12 @@ public Color FactorColor(double factor, byte min = 0, byte max = byte.MaxValue) byte b = (byte)(B == 0 ? 0 : Math.Min(Math.Max(min, B * factor), max)); - return Color.FromArgb(A, r, g, b); + return FromArgb(A, r, g, b); + } + + public MCvScalar ToMCvScalar() + { + return new MCvScalar(B, G, R, A); } /// @@ -88,7 +94,7 @@ public Color FactorColor(double factor, byte min = 0, byte max = byte.MaxValue) /// public uint ToUint32() { - return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; + return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | B; } /// diff --git a/UVtools.UI/UVtools.UI.csproj b/UVtools.UI/UVtools.UI.csproj index 8382adf4..6dc14433 100644 --- a/UVtools.UI/UVtools.UI.csproj +++ b/UVtools.UI/UVtools.UI.csproj @@ -2,7 +2,7 @@ WinExe UVtools - 4.2.1 + 4.2.2 true 1701;1702; @@ -14,13 +14,13 @@ - - - - - - - + + + + + + + diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index 47fd4f6b..e872da9c 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -1300,6 +1300,14 @@ + + + Copy the whole mat data to another reference + + It does not do any safe-check. + + Destination address to copy data to + Copy a region from to center of other @@ -1348,7 +1356,14 @@ - + + + Calculates the bounding rectangle and return a object with it + + + + + Calculates the bounding rectangle and return a object with it @@ -1605,19 +1620,20 @@ - + - Draw a polygon given number of sides and length + Draw a polygon given number of sides and diameter Number of polygon sides, Special: use 1 to draw a line and >= 100 to draw a native OpenCV circle - Radius + Diameter Center position + @@ -6324,7 +6340,12 @@ - Gets if is in the bottom layer group by count and height + Gets if this layer is in the bottom layer group by it and height + + + + + Gets if this layer is in the normal layer group by it and height