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

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2022-04-11 02:08:15 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2022-04-11 02:08:15 +0300
commit99e3e33ac6ee2c9cdf586eacb47dc69e192296d2 (patch)
treeaa53d41365086aca64457d3c81ec5a52532009a7
parent84aa3d47e34a96c8d8400bdf1d2d303decc5d108 (diff)
v3.3.0v3.3.0
- **Shortcuts:** - (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer - (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers - (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer - (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer - (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers - (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer - (Add) **Home:** While on layer preview will go to first layer - (Add) **End:** While on layer preview will go to last layer - (Add) **Page up:** While on layer preview will skip +10 layers - (Add) **Page down:** While on layer preview will skip -10 layers - (Add) Tool - Lithophane: Generate lithophane from a picture - (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome - (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
-rw-r--r--CHANGELOG.md17
-rw-r--r--RELEASE_NOTES.md36
-rw-r--r--Scripts/010 Editor/cxdlp.bt4
-rw-r--r--UVtools.Core/EmguCV/EmguContour.cs42
-rw-r--r--UVtools.Core/EmguCV/EmguContours.cs39
-rw-r--r--UVtools.Core/Enumerations.cs25
-rw-r--r--UVtools.Core/Extensions/EmguExtensions.cs39
-rw-r--r--UVtools.Core/FileFormats/CXDLPFile.cs99
-rw-r--r--UVtools.Core/Layers/Layer.cs11
-rw-r--r--UVtools.Core/Managers/MatCacheManager.cs4
-rw-r--r--UVtools.Core/Operations/OperationCalibrateElephantFoot.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateExposureFinder.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateGrayscale.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateStressTower.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateTolerance.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs2
-rw-r--r--UVtools.Core/Operations/OperationLayerExportGif.cs4
-rw-r--r--UVtools.Core/Operations/OperationLayerExportHeatMap.cs4
-rw-r--r--UVtools.Core/Operations/OperationLayerExportImage.cs4
-rw-r--r--UVtools.Core/Operations/OperationLithophane.cs527
-rw-r--r--UVtools.Core/Operations/OperationPixelArithmetic.cs2
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml246
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml.cs82
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs159
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs4
-rw-r--r--UVtools.WPF/Structures/OperationProfiles.cs1
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj2
29 files changed, 1203 insertions, 164 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e9ffca0..4cd4313 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## 10/04/2022 - v3.3.0
+
+- **Shortcuts:**
+ - (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer
+ - (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers
+ - (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer
+ - (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer
+ - (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers
+ - (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer
+ - (Add) **Home:** While on layer preview will go to first layer
+ - (Add) **End:** While on layer preview will go to last layer
+ - (Add) **Page up:** While on layer preview will skip +10 layers
+ - (Add) **Page down:** While on layer preview will skip -10 layers
+- (Add) Tool - Lithophane: Generate lithophane from a picture
+- (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome
+- (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
+
## 06/04/2022 - v3.2.2
- **Settings:**
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 1ebb05c..11aab5f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,23 +1,15 @@
-- **Settings:**
- - (Add) Remove source file after automatic conversion (#444)
- - (Add) Remove source file after manual conversion (#444)
- - (Add) **Average resin bottle cost:** The average cost per one resin bottle of 1000ml.
- Used to calculate the material cost when the file lacks that information.
- Use 0 to disable this feature and only show the cost if file have that information.
- If this value is changed, you need to reload the current file to update the cost.
- - (Change) Move "Expand and show tool descriptions by default" to From `General` to `Tools` tab (Setting will reset to default)
-- **File formats:**
- - (Add) Property `StartingMaterialMilliliters`: Gets the starting material milliliters when the file was loaded
- - (Add) Property `StartingMaterialCost`: Gets the starting material cost when the file was loaded
- - (Add) Property `MaterialMilliliterCost`: Gets the material cost per one milliliter
- - (Improvement) Update `MaterialCost` when `MaterialMilliliters` changes (#449)
-- **Raft relief:**
- - (Add) Linked lines: Remove the raft, keep supports and link them with lines
- - (Improvement) Change the supports detection parameters to be more effective and precise on detect the starting layer
- - (Fix) Brightness percentage not getting updated
- - (Fix) Remove anti-aliased edges from Tabs
-- (Improvement) Core: Minor clean-up
-- (Fix) Issue repair error when 'Auto repair layers and issues on file load' is enabled (#446)
-- (Fix) UI: Selecting a object with ROI when flip is verically, will cause 1px up-shift on the preview
-- (Fix) macOS permission error due loss of attributes on the build script, WSL have bug with mv, using ln&rm instead
+- **Shortcuts:**
+ - (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer
+ - (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers
+ - (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer
+ - (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer
+ - (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers
+ - (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer
+ - (Add) **Home:** While on layer preview will go to first layer
+ - (Add) **End:** While on layer preview will go to last layer
+ - (Add) **Page up:** While on layer preview will skip +10 layers
+ - (Add) **Page down:** While on layer preview will skip -10 layers
+- (Add) Tool - Lithophane: Generate lithophane from a picture
+- (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome
+- (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
diff --git a/Scripts/010 Editor/cxdlp.bt b/Scripts/010 Editor/cxdlp.bt
index 6d82510..b92fae4 100644
--- a/Scripts/010 Editor/cxdlp.bt
+++ b/Scripts/010 Editor/cxdlp.bt
@@ -24,9 +24,9 @@ typedef struct {
typedef struct() {
uint32 layerArea <fgcolor=cBlack, bgcolor=cWhite>;
- uint32 layerPointNum <fgcolor=cBlack, bgcolor=cWhite>;
+ uint32 layerLineCount <fgcolor=cBlack, bgcolor=cWhite>;
- layerPointsData pD()[layerPointNum];
+ layerPointsData pD()[layerLineCount];
ubyte CR_LF2[2] <fgcolor=cBlack, bgcolor=cRed>;
} layerData;
diff --git a/UVtools.Core/EmguCV/EmguContour.cs b/UVtools.Core/EmguCV/EmguContour.cs
index 4ecfecb..f7d3bd1 100644
--- a/UVtools.Core/EmguCV/EmguContour.cs
+++ b/UVtools.Core/EmguCV/EmguContour.cs
@@ -14,6 +14,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
+using System.Linq;
using UVtools.Core.Extensions;
namespace UVtools.Core.EmguCV;
@@ -21,7 +22,7 @@ namespace UVtools.Core.EmguCV;
/// <summary>
/// A contour cache for OpenCV
/// </summary>
-public class EmguContour : IReadOnlyCollection<Point>, IDisposable
+public class EmguContour : IReadOnlyCollection<Point>, IDisposable, IComparable<EmguContour>, IComparer<EmguContour>
{
#region Constants
@@ -257,4 +258,43 @@ public class EmguContour : IReadOnlyCollection<Point>, IDisposable
_moments?.Dispose();
}
#endregion
+
+ #region Equality
+
+ protected bool Equals(EmguContour other)
+ {
+ if (Count != other.Count) return false;
+ return _points.ToArray().SequenceEqual(other.ToArray());
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((EmguContour) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return _points.GetHashCode();
+ }
+
+ public int CompareTo(EmguContour? other)
+ {
+ if (ReferenceEquals(this, other)) return 0;
+ if (ReferenceEquals(null, other)) return 1;
+ return _area.CompareTo(other._area);
+ }
+
+ public int Compare(EmguContour? x, EmguContour? y)
+ {
+ if (ReferenceEquals(x, y)) return 0;
+ if (ReferenceEquals(null, y)) return 1;
+ if (ReferenceEquals(null, x)) return -1;
+ return x._area.CompareTo(y._area);
+ }
+ #endregion
+
+
} \ No newline at end of file
diff --git a/UVtools.Core/EmguCV/EmguContours.cs b/UVtools.Core/EmguCV/EmguContours.cs
index d8c0639..d0ac9a5 100644
--- a/UVtools.Core/EmguCV/EmguContours.cs
+++ b/UVtools.Core/EmguCV/EmguContours.cs
@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
+using Emgu.CV.CvEnum;
using UVtools.Core.Extensions;
namespace UVtools.Core.EmguCV;
@@ -38,6 +39,7 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
public int Count => _contours.Length;
+ public readonly int[,] Hierarchy = new int[0,0];
public EmguContour this[int index] => _contours[index];
@@ -51,6 +53,21 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
}
}
+ public EmguContours(VectorOfVectorOfPoint vectorOfPointsOfPoints, int[,] hierarchy) : this(vectorOfPointsOfPoints)
+ {
+ Hierarchy = hierarchy;
+ }
+
+ public EmguContours(IInputOutputArray mat, RetrType mode = RetrType.List, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
+ {
+ using var contours = mat.FindContours(out Hierarchy, mode, method, offset);
+ _contours = new EmguContour[contours.Size];
+ for (int i = 0; i < _contours.Length; i++)
+ {
+ _contours[i] = new EmguContour(contours[i]);
+ }
+ }
+
public (int Index, EmguContour Contour, double Distance)[][] CalculateCentroidDistances(bool includeOwn = false, bool sortByDistance = true)
{
var items = new (int Index, EmguContour Contour, double Distance)[Count][];
@@ -259,6 +276,24 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
}
/// <summary>
+ /// Gets the largest contour area from a contour list
+ /// </summary>
+ /// <param name="contours">Contour list</param>
+ /// <returns></returns>
+ public static double GetLargestContourArea(VectorOfVectorOfPoint contours)
+ {
+ var vectorSize = contours.Size;
+ if (vectorSize == 0) return 0;
+
+ double result = 0;
+ for (var i = 0; i < vectorSize; i++)
+ {
+ result = Math.Max(result, CvInvoke.ContourArea(contours[i]));
+ }
+ return result;
+ }
+
+ /// <summary>
/// Gets contours real area for a group of contours
/// </summary>
/// <param name="contours">Grouped contours</param>
@@ -305,8 +340,8 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
using var contour2Mat = EmguExtensions.InitMat(totalRect.Size);
var inverseOffset = new Point(-totalRect.X, -totalRect.Y);
- CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
- CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
+ CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset);
+ CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset);
CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, contour1Mat);
diff --git a/UVtools.Core/Enumerations.cs b/UVtools.Core/Enumerations.cs
index e756538..1e4e9b8 100644
--- a/UVtools.Core/Enumerations.cs
+++ b/UVtools.Core/Enumerations.cs
@@ -32,27 +32,33 @@ public enum LayerRangeSelection : byte
Last
}
-public enum FlipDirection : byte
+/// <summary>
+/// Flip direction, same as <see cref="FlipType"/>, but add None which must be taken care/ignored on code
+/// </summary>
+public enum FlipDirection : sbyte
{
- None,
- Horizontally,
- Vertically,
- Both,
+ None = sbyte.MinValue,
+ Horizontally = FlipType.Horizontal,
+ Vertically = FlipType.Vertical,
+ Both = FlipType.Both,
}
+/// <summary>
+/// Rotate direction, same as <see cref="RotateFlags"/>, but add None which must be taken care/ignored on code
+/// </summary>
public enum RotateDirection : sbyte
{
[Description("None")]
None = -1,
/// <summary>Rotate 90 degrees clockwise (0)</summary>
[Description("Rotate 90º CW")]
- Rotate90Clockwise = 0,
+ Rotate90Clockwise = RotateFlags.Rotate90Clockwise,
/// <summary>Rotate 180 degrees clockwise (1)</summary>
[Description("Rotate 180º")]
- Rotate180 = 1,
+ Rotate180 = RotateFlags.Rotate180,
/// <summary>Rotate 270 degrees clockwise (2)</summary>
[Description("Rotate 90º CCW")]
- Rotate90CounterClockwise = 2,
+ Rotate90CounterClockwise = RotateFlags.Rotate90CounterClockwise,
}
public enum Anchor : byte
@@ -118,6 +124,7 @@ public enum RemoveSourceFileAction : byte
Prompt
}
+/*
public static class Enumerations
{
public static FlipType ToOpenCVFlipType(FlipDirection flip)
@@ -143,4 +150,4 @@ public static class Enumerations
_ => throw new ArgumentOutOfRangeException(nameof(rotate), rotate, null)
};
}
-} \ No newline at end of file
+}*/ \ No newline at end of file
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index 243b669..07120bd 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -440,6 +440,9 @@ public static class EmguExtensions
{
return CvInvoke.Imencode(".png", mat);
}
+
+ public static Point GetCenterPoint(this Mat mat) => new(mat.Width / 2, mat.Height / 2);
+
#endregion
#region Create methods
@@ -457,20 +460,28 @@ public static class EmguExtensions
return src.CreateMask(vec);
}
- public static Mat TrimByBounds(this Mat src)
+ public static Mat CropByBounds(this Mat src, bool cloneInsteadRoi = false)
{
var rect = CvInvoke.BoundingRectangle(src);
if (rect.Size == Size.Empty) return src.New();
- if (src.Size == rect.Size) return src.Clone();
- using var roi = src.Roi(rect);
- return roi.Clone();
+ if (src.Size == rect.Size) return cloneInsteadRoi ? src.Roi(src.Size) : src.Clone();
+ var roi = src.Roi(rect);
+
+ if (cloneInsteadRoi)
+ {
+ var clone = roi.Clone();
+ roi.Dispose();
+ return clone;
+ }
+
+ return roi;
}
- public static void TrimByBounds(this Mat src, Mat dst)
+ public static void CropByBounds(this Mat src, Mat dst)
{
- var mat = src.TrimByBounds();
- CvInvoke.Swap(mat, dst);
- src.Dispose();
+ using var mat = src.CropByBounds();
+ dst.Create(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels);
+ src.CopyTo(dst);
}
@@ -918,6 +929,18 @@ public static class EmguExtensions
xTrans + (src.Width - src.Width * xScale) / 2.0,
yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation);
}
+
+ /// <summary>
+ /// Resize source mat proportional to a scale
+ /// </summary>
+ /// <param name="src"></param>
+ /// <param name="scale"></param>
+ /// <param name="interpolation"></param>
+ public static void Resize(this Mat src, double scale, Inter interpolation = Inter.Linear)
+ {
+ if (scale == 1) return;
+ CvInvoke.Resize(src, src, new Size((int) (src.Width * scale), (int) (src.Height * scale)), 0, 0, interpolation);
+ }
#endregion
#region Draw Methods
diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs
index eea39a5..d303793 100644
--- a/UVtools.Core/FileFormats/CXDLPFile.cs
+++ b/UVtools.Core/FileFormats/CXDLPFile.cs
@@ -19,7 +19,9 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using Emgu.CV.CvEnum;
using UVtools.Core.Converters;
+using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.Layers;
using UVtools.Core.Objects;
@@ -720,16 +722,10 @@ public class CXDLPFile : FileFormat
//var preLayers = new PreLayer[LayerCount];
//var layerDefs = new LayerDef[LayerCount];
//var layersStreams = new MemoryStream[LayerCount];
-
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- //var layer = this[layerIndex];
- outputFile.WriteBytes(BitExtensions.ToBytesBigEndian((uint)Math.Round(this[layerIndex].BoundingRectangleMillimeters.Area()*1000)));
- //preLayers[layerIndex] = new(layer.NonZeroPixelCount);
- }
- //Helpers.SerializeWriteFileStream(outputFile, preLayers);
- //Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+
+ var layerAreaPosition = outputFile.Position;
+ outputFile.Seek(4 * LayerCount, SeekOrigin.Current);
outputFile.WriteBytes(pageBreak);
if (HeaderSettings.Version >= 3)
@@ -737,7 +733,9 @@ public class CXDLPFile : FileFormat
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoV3Settings);
}
+ var layerLargestContourArea = new uint[LayerCount];
var layerBytes = new List<byte>[LayerCount];
+ var pixelArea = PixelArea;
foreach (var batch in BatchLayersIndexes())
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
@@ -745,6 +743,10 @@ public class CXDLPFile : FileFormat
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
{
+ using var contours = mat.FindContours(RetrType.External);
+ layerLargestContourArea[layerIndex] = (uint)(EmguContours.GetLargestContourArea(contours) * pixelArea * 1000);
+ //Debug.WriteLine($"Area: {contourArea} ({contourArea * PixelArea * 1000}) BR: {max.Bounds.Area()} ({max.Bounds.Area() * PixelArea * 1000})");
+
var span = mat.GetDataByteSpan();
layerBytes[layerIndex] = new();
@@ -783,9 +785,7 @@ public class CXDLPFile : FileFormat
}
}
- layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(
- (uint)Math.Round(layer.BoundingRectangleMillimeters.Area() * 1000),
- lineCount));
+ layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layerLargestContourArea[layerIndex], lineCount));
layerBytes[layerIndex].AddRange(pageBreak);
}
@@ -799,75 +799,14 @@ public class CXDLPFile : FileFormat
}
}
-
- /*Parallel.For(0, LayerCount, CoreSettings.ParallelOptions,
- //new ParallelOptions{MaxDegreeOfParallelism = 1},
- layerIndex =>
- {
- if (progress.Token.IsCancellationRequested) return;
- //List<LayerLine> layerLines = new();
- var layer = this[layerIndex];
- using var mat = layer.LayerMat;
- var span = mat.GetDataByteSpan();
-
- layerBytes[layerIndex] = new();
-
- for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
- {
- int y = layer.BoundingRectangle.Y;
- int startY = -1;
- byte lastColor = 0;
- for (; y < layer.BoundingRectangle.Bottom; y++)
- {
- int pos = mat.GetPixelPos(x, y);
- byte color = span[pos];
-
- if (lastColor == color && color != 0) continue;
-
- if (startY >= 0)
- {
- layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
- //layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
- //Debug.WriteLine(layerLines[^1]);
- }
-
- startY = color == 0 ? -1 : y;
-
- lastColor = color;
- }
-
- if (startY >= 0)
- {
- layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
- //layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
- //Debug.WriteLine(layerLines[^1]);
- }
- }
-
- //layerDefs[layerIndex] = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray());
- //var layerDef = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray());
- //layersStreams[layerIndex] = new MemoryStream();
- //Helpers.Serializer.Serialize(layersStreams[layerIndex], layerDef);
-
- //layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layer.NonZeroPixelCount, (uint) layerBytes[layerIndex].Count));
- //layerBytes[layerIndex].AddRange(PageBreak.Bytes);
-
- progress.LockAndIncrement();
- });
-
- progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ // Write layer largest contour area (mm^2 * 1000)
+ outputFile.Seek(layerAreaPosition, SeekOrigin.Begin);
+ foreach (var area in layerLargestContourArea)
{
- progress.Token.ThrowIfCancellationRequested();
- //Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]);
- //outputFile.WriteStream(layersStreams[layerIndex]);
- //layersStreams[layerIndex].Dispose();
- outputFile.WriteBytes(LayerDef.GetHeaderBytes(this[layerIndex].NonZeroPixelCount, (uint)layerBytes[layerIndex].Count));
- outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
- outputFile.WriteBytes(pageBreak);
- progress++;
- }*/
+ outputFile.WriteUIntBigEndian(area);
+ }
+ outputFile.Seek(0, SeekOrigin.End);
Helpers.SerializeWriteFileStream(outputFile, FooterSettings);
progress.Reset("Calculating checksum");
@@ -986,7 +925,7 @@ public class CXDLPFile : FileFormat
linesBytes[layerIndex] = null!;
- _layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
+ _layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
}
progress.LockAndIncrement();
diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs
index 4db7a9e..535f93d 100644
--- a/UVtools.Core/Layers/Layer.cs
+++ b/UVtools.Core/Layers/Layer.cs
@@ -1488,6 +1488,17 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
_materialMilliliters = _materialMilliliters,
};*/
}
+
+ public Layer[] Clone(uint times)
+ {
+ var layers = new Layer[times];
+ for (int i = 0; i < times; i++)
+ {
+ layers[i] = Clone();
+ }
+
+ return layers;
+ }
#endregion
#region Static Methods
diff --git a/UVtools.Core/Managers/MatCacheManager.cs b/UVtools.Core/Managers/MatCacheManager.cs
index 2934c3f..9ffef33 100644
--- a/UVtools.Core/Managers/MatCacheManager.cs
+++ b/UVtools.Core/Managers/MatCacheManager.cs
@@ -170,12 +170,12 @@ public class MatCacheManager : IDisposable
if (Flip != FlipDirection.None)
{
- CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], Enumerations.ToOpenCVFlipType(Flip));
+ CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (FlipType)Flip);
}
if (Rotate != RotateDirection.None)
{
- CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], Enumerations.ToOpenCVRotateFlags(Rotate));
+ CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (RotateFlags) Rotate);
}
if (StripAntiAliasing)
diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
index bce1855..58c73d6 100644
--- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
+++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
@@ -671,7 +671,7 @@ public sealed class OperationCalibrateElephantFoot : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
+ Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
}
// Preview
diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
index 5adc175..efd6b15 100644
--- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
@@ -2238,7 +2238,7 @@ public sealed class OperationCalibrateExposureFinder : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- new OperationFlip(SlicerFile) { FlipDirection = Enumerations.ToOpenCVFlipType(flip) }.Execute(progress);
+ new OperationFlip(SlicerFile) { FlipDirection = (FlipType)flip }.Execute(progress);
}
}
diff --git a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
index 4b64dd4..010e3a4 100644
--- a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
+++ b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
@@ -467,7 +467,7 @@ public sealed class OperationCalibrateGrayscale : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
+ Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
}
return layers;
diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
index c651fe4..ce33999 100644
--- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs
+++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
@@ -387,7 +387,7 @@ public sealed class OperationCalibrateStressTower : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
+ Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
}
return layers;
diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
index d675c82..c6b9460 100644
--- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs
+++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
@@ -687,7 +687,7 @@ public sealed class OperationCalibrateTolerance : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
+ Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
}
return layers;
diff --git a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
index 8e64255..dcee1aa 100644
--- a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
+++ b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
@@ -722,7 +722,7 @@ public sealed class OperationCalibrateXYZAccuracy : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
+ Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
}
return layers;
diff --git a/UVtools.Core/Operations/OperationLayerExportGif.cs b/UVtools.Core/Operations/OperationLayerExportGif.cs
index cce026a..cd4a0ed 100644
--- a/UVtools.Core/Operations/OperationLayerExportGif.cs
+++ b/UVtools.Core/Operations/OperationLayerExportGif.cs
@@ -239,12 +239,12 @@ public sealed class OperationLayerExportGif : Operation
if (_flipDirection != FlipDirection.None)
{
- CvInvoke.Flip(matRoi, matRoi, Enumerations.ToOpenCVFlipType(_flipDirection));
+ CvInvoke.Flip(matRoi, matRoi, (FlipType)_flipDirection);
}
if (_rotateDirection != RotateDirection.None)
{
- CvInvoke.Rotate(matRoi, matRoi, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
+ CvInvoke.Rotate(matRoi, matRoi, (RotateFlags)_rotateDirection);
}
if (_renderLayerCount)
diff --git a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
index 47ca83c..2ed99d2 100644
--- a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
+++ b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
@@ -175,12 +175,12 @@ public sealed class OperationLayerExportHeatMap : Operation
if (_flipDirection != FlipDirection.None)
{
- CvInvoke.Flip(sumMat, sumMat, Enumerations.ToOpenCVFlipType(_flipDirection));
+ CvInvoke.Flip(sumMat, sumMat, (FlipType)_flipDirection);
}
if (_rotateDirection != RotateDirection.None)
{
- CvInvoke.Rotate(sumMat, sumMat, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
+ CvInvoke.Rotate(sumMat, sumMat, (RotateFlags)_rotateDirection);
}
if (_cropByRoi && HaveROI)
diff --git a/UVtools.Core/Operations/OperationLayerExportImage.cs b/UVtools.Core/Operations/OperationLayerExportImage.cs
index 5fbc7ca..b8787a8 100644
--- a/UVtools.Core/Operations/OperationLayerExportImage.cs
+++ b/UVtools.Core/Operations/OperationLayerExportImage.cs
@@ -191,12 +191,12 @@ public sealed class OperationLayerExportImage : Operation
if (_flipDirection != FlipDirection.None)
{
- CvInvoke.Flip(matRoi, matRoi, Enumerations.ToOpenCVFlipType(_flipDirection));
+ CvInvoke.Flip(matRoi, matRoi, (FlipType)_flipDirection);
}
if (_rotateDirection != RotateDirection.None)
{
- CvInvoke.Rotate(matRoi, matRoi, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
+ CvInvoke.Rotate(matRoi, matRoi, (RotateFlags)_rotateDirection);
}
var filename = SlicerFile[layerIndex].FormatFileName(_filename, _padLayerIndex ? SlicerFile.LayerDigits : byte.MinValue, IndexStartNumber.Zero, string.Empty);
diff --git a/UVtools.Core/Operations/OperationLithophane.cs b/UVtools.Core/Operations/OperationLithophane.cs
new file mode 100644
index 0000000..d497e36
--- /dev/null
+++ b/UVtools.Core/Operations/OperationLithophane.cs
@@ -0,0 +1,527 @@
+/*
+ * 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.Concurrent;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using Emgu.CV.Structure;
+using UVtools.Core.Extensions;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Layers;
+
+namespace UVtools.Core.Operations;
+
+[Serializable]
+public class OperationLithophane : Operation
+{
+ #region Enum
+
+ public enum LithophaneBaseType : byte
+ {
+ None,
+ Square,
+ Model
+ }
+
+ #endregion
+
+ #region Members
+ private decimal _layerHeight;
+ private ushort _bottomLayerCount;
+ private decimal _bottomExposure;
+ private decimal _normalExposure;
+
+ private string? _filePath;
+ private RotateDirection _rotate = RotateDirection.None;
+ private bool _mirror;
+ private bool _invertColor;
+ private decimal _resizeFactor = 100;
+ private bool _enhanceContrast;
+ private sbyte _brightnessGain;
+ private byte _gapClosingIterations = 4;
+ private byte _removeNoiseIterations = 4;
+ private byte _gaussianBlur;
+ private byte _startThresholdRange = 1;
+ private byte _endThresholdRange = byte.MaxValue;
+ private decimal _baseThickness = 2;
+ private LithophaneBaseType _baseType = LithophaneBaseType.Square;
+ private ushort _baseMargin = 80;
+ private decimal _lithophaneHeight = 3;
+ private bool _oneLayerPerThreshold;
+ private bool _enableAntiAliasing = true;
+
+ #endregion
+
+ #region Overrides
+
+ public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
+ public override string IconClass => "fas fa-portrait";
+ public override string Title => "Lithophane";
+ public override string Description =>
+ "Generate lithophane from a picture.\n" +
+ "Note: The current opened file will be overwritten with this lithophane, use a dummy or a not needed file.";
+
+ public override string ConfirmationText =>
+ "generate the lithophane?";
+
+ public override string ProgressTitle =>
+ "Generating lithophane";
+
+ public override string ProgressAction => "Threshold levels";
+
+ public override string? ValidateInternally()
+ {
+ var sb = new StringBuilder();
+ if (string.IsNullOrWhiteSpace(_filePath))
+ {
+ sb.AppendLine("The selected file is empty");
+ }
+ else if(!File.Exists(_filePath))
+ {
+ sb.AppendLine("The selected file does not exists");
+ }
+
+ if (_startThresholdRange > _endThresholdRange)
+ {
+ sb.AppendLine("Start threshold can't be higher than end threshold");
+ }
+
+ using var mat = GetSourceMat();
+ if (mat is null)
+ {
+ sb.AppendLine("Unable to generate the mat from source file, is it a valid image file?");
+ }
+ else
+ {
+ if (SlicerFile.ResolutionX < mat.Width * _resizeFactor / 100 || SlicerFile.ResolutionY < mat.Height * _resizeFactor / 100)
+ {
+ //int differenceX = (int)SlicerFile.ResolutionX - mat.Width;
+ //int differenceY = (int)SlicerFile.ResolutionY - mat.Height;
+ var scaleX = SlicerFile.ResolutionX * 100f / mat.Width;
+ var scaleY = SlicerFile.ResolutionY * 100f / mat.Height;
+ var maxScale = Math.Min(scaleX, scaleY);
+
+ sb.AppendLine($"The printer resolution is not enough to accomodate the lithophane image, please scale down to a maximum of {maxScale:F0}%");
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ var result = $"{(FileExists ? $"{Path.GetFileName(_filePath)} ({Math.Abs(GetHashCode())})" : $"Lithophane {Math.Abs(GetHashCode())}")}";
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+ #endregion
+
+ #region Constructor
+
+ public OperationLithophane() { }
+
+ public OperationLithophane(FileFormat slicerFile) : base(slicerFile)
+ {
+ if (_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
+ if (_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime;
+ if (_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime;
+ if (_bottomLayerCount <= 0) _bottomLayerCount = SlicerFile.BottomLayerCount;
+ _mirror = SlicerFile.DisplayMirror != FlipDirection.None;
+ }
+
+ #endregion
+
+ #region Properties
+ public decimal LayerHeight
+ {
+ get => _layerHeight;
+ set => RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value));
+ }
+
+ public ushort BottomLayerCount
+ {
+ get => _bottomLayerCount;
+ set => RaiseAndSetIfChanged(ref _bottomLayerCount, value);
+ }
+
+ public decimal BottomExposure
+ {
+ get => _bottomExposure;
+ set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2));
+ }
+
+ public decimal NormalExposure
+ {
+ get => _normalExposure;
+ set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2));
+ }
+
+ public string? FilePath
+ {
+ get => _filePath;
+ set => RaiseAndSetIfChanged(ref _filePath, value);
+ }
+
+ public bool FileExists => !string.IsNullOrWhiteSpace(_filePath) && File.Exists(_filePath);
+
+ public RotateDirection Rotate
+ {
+ get => _rotate;
+ set => RaiseAndSetIfChanged(ref _rotate, value);
+ }
+
+ public bool Mirror
+ {
+ get => _mirror;
+ set => RaiseAndSetIfChanged(ref _mirror, value);
+ }
+
+ public bool InvertColor
+ {
+ get => _invertColor;
+ set => RaiseAndSetIfChanged(ref _invertColor, value);
+ }
+
+ public decimal ResizeFactor
+ {
+ get => _resizeFactor;
+ set => RaiseAndSetIfChanged(ref _resizeFactor, Math.Max(1, value));
+ }
+
+ public bool EnhanceContrast
+ {
+ get => _enhanceContrast;
+ set => RaiseAndSetIfChanged(ref _enhanceContrast, value);
+ }
+
+ public sbyte BrightnessGain
+ {
+ get => _brightnessGain;
+ set => RaiseAndSetIfChanged(ref _brightnessGain, value);
+ }
+
+ public byte GapClosingIterations
+ {
+ get => _gapClosingIterations;
+ set => RaiseAndSetIfChanged(ref _gapClosingIterations, value);
+ }
+
+ public byte RemoveNoiseIterations
+ {
+ get => _removeNoiseIterations;
+ set => RaiseAndSetIfChanged(ref _removeNoiseIterations, value);
+ }
+
+ public byte GaussianBlur
+ {
+ get => _gaussianBlur;
+ set => RaiseAndSetIfChanged(ref _gaussianBlur, value);
+ }
+
+ public byte StartThresholdRange
+ {
+ get => _startThresholdRange;
+ set => RaiseAndSetIfChanged(ref _startThresholdRange, Math.Max((byte)1, value));
+ }
+
+ public byte EndThresholdRange
+ {
+ get => _endThresholdRange;
+ set => RaiseAndSetIfChanged(ref _endThresholdRange, Math.Max((byte)1, value));
+ }
+
+ public decimal BaseThickness
+ {
+ get => _baseThickness;
+ set => RaiseAndSetIfChanged(ref _baseThickness, Math.Max(0, value));
+ }
+
+ public LithophaneBaseType BaseType
+ {
+ get => _baseType;
+ set => RaiseAndSetIfChanged(ref _baseType, value);
+ }
+
+ public ushort BaseMargin
+ {
+ get => _baseMargin;
+ set => RaiseAndSetIfChanged(ref _baseMargin, value);
+ }
+
+ public decimal LithophaneHeight
+ {
+ get => _lithophaneHeight;
+ set => RaiseAndSetIfChanged(ref _lithophaneHeight, Math.Max(0.01m, value));
+ }
+
+ public bool OneLayerPerThreshold
+ {
+ get => _oneLayerPerThreshold;
+ set => RaiseAndSetIfChanged(ref _oneLayerPerThreshold, value);
+ }
+
+ public bool EnableAntiAliasing
+ {
+ get => _enableAntiAliasing;
+ set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value);
+ }
+
+ #endregion
+
+ #region Equality
+
+ protected bool Equals(OperationLithophane other)
+ {
+ return _layerHeight == other._layerHeight && _bottomLayerCount == other._bottomLayerCount && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _filePath == other._filePath && _rotate == other._rotate && _mirror == other._mirror && _invertColor == other._invertColor && _enhanceContrast == other._enhanceContrast && _resizeFactor == other._resizeFactor && _brightnessGain == other._brightnessGain && _gapClosingIterations == other._gapClosingIterations && _removeNoiseIterations == other._removeNoiseIterations && _gaussianBlur == other._gaussianBlur && _startThresholdRange == other._startThresholdRange && _endThresholdRange == other._endThresholdRange && _baseThickness == other._baseThickness && _baseType == other._baseType && _baseMargin == other._baseMargin && _lithophaneHeight == other._lithophaneHeight && _oneLayerPerThreshold == other._oneLayerPerThreshold && _enableAntiAliasing == other._enableAntiAliasing;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((OperationLithophane) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(_layerHeight);
+ hashCode.Add(_bottomLayerCount);
+ hashCode.Add(_bottomExposure);
+ hashCode.Add(_normalExposure);
+ hashCode.Add(_filePath);
+ hashCode.Add((int) _rotate);
+ hashCode.Add(_mirror);
+ hashCode.Add(_invertColor);
+ hashCode.Add(_enhanceContrast);
+ hashCode.Add(_resizeFactor);
+ hashCode.Add(_brightnessGain);
+ hashCode.Add(_gapClosingIterations);
+ hashCode.Add(_removeNoiseIterations);
+ hashCode.Add(_gaussianBlur);
+ hashCode.Add(_startThresholdRange);
+ hashCode.Add(_endThresholdRange);
+ hashCode.Add(_baseThickness);
+ hashCode.Add((int) _baseType);
+ hashCode.Add(_baseMargin);
+ hashCode.Add(_lithophaneHeight);
+ hashCode.Add(_oneLayerPerThreshold);
+ hashCode.Add(_enableAntiAliasing);
+ return hashCode.ToHashCode();
+ }
+
+ #endregion
+
+ #region Methods
+
+ public Mat? GetSourceMat()
+ {
+ if (!FileExists) return null;
+ try
+ {
+ var mat = CvInvoke.Imread(_filePath, ImreadModes.Grayscale);
+ if (_invertColor) CvInvoke.BitwiseNot(mat, mat);
+ mat = mat.CropByBounds();
+ return mat.Size == Size.Empty ? null : mat;
+ }
+ catch
+ {
+ // ignored
+ }
+
+ return null;
+ }
+
+ public Mat? GetTargetMat()
+ {
+ var mat = GetSourceMat();
+ if (mat is null) return null;
+
+ if (_resizeFactor != 100) mat.Resize((double)_resizeFactor / 100.0);
+
+ if (_enhanceContrast) CvInvoke.EqualizeHist(mat, mat);
+ if (_brightnessGain != 0)
+ {
+ using var mask = mat.NewSetTo(new MCvScalar(Math.Abs(_brightnessGain)));
+ if(_brightnessGain > 0) CvInvoke.Add(mat, mask, mat, mat);
+ else CvInvoke.Subtract(mat, mask, mat, mat);
+ }
+
+ if (_gaussianBlur > 0)
+ {
+ var ksize = 1 + _gaussianBlur * 2;
+ CvInvoke.GaussianBlur(mat, mat, new Size(ksize, ksize), 0);
+ }
+
+ if (_removeNoiseIterations > 0) CvInvoke.MorphologyEx(mat, mat, MorphOp.Open, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _removeNoiseIterations, BorderType.Reflect101, default);
+ if (_gapClosingIterations > 0) CvInvoke.MorphologyEx(mat, mat, MorphOp.Close, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _gapClosingIterations, BorderType.Reflect101, default);
+
+ if (_rotate != RotateDirection.None) CvInvoke.Rotate(mat, mat, (RotateFlags) _rotate);
+ if (_mirror)
+ {
+ var flip = SlicerFile.DisplayMirror;
+ if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
+ CvInvoke.Flip(mat, mat, (FlipType)flip);
+ }
+
+ return mat;
+ }
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ using var mat = GetTargetMat();
+ if (mat is null) return false;
+
+ var layersBag = new ConcurrentDictionary<byte, Layer>();
+ progress.Reset("Threshold levels", byte.MaxValue);
+ Parallel.For(_startThresholdRange, _endThresholdRange, CoreSettings.GetParallelOptions(progress), threshold =>
+ {
+ using var thresholdMat = new Mat();
+ CvInvoke.Threshold(mat, thresholdMat, threshold, byte.MaxValue, ThresholdType.Binary);
+ if (CvInvoke.CountNonZero(thresholdMat) == 0) return;
+
+ if (_enableAntiAliasing)
+ {
+ CvInvoke.GaussianBlur(thresholdMat, thresholdMat, new Size(3, 3), 0);
+ }
+
+ using var layerMat = EmguExtensions.InitMat(SlicerFile.Resolution);
+ thresholdMat.CopyToCenter(layerMat);
+ layersBag.TryAdd((byte)threshold, new Layer(layerMat, SlicerFile));
+ progress.LockAndIncrement();
+ });
+
+ var thresholdLayers = layersBag.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToArray();
+
+ if (!_oneLayerPerThreshold)
+ {
+ var layerIncrementF = thresholdLayers.Length * _layerHeight / Math.Max(_layerHeight, _lithophaneHeight);
+ if (layerIncrementF >= 2)
+ {
+ var layerIncrement = (uint) layerIncrementF;
+ var indexes = new int[(int)Math.Ceiling(thresholdLayers.Length / (float)layerIncrement)];
+ var newLayers = new Layer[indexes.Length];
+ var count = 0;
+ for (int index = 0; index < thresholdLayers.Length; index++)
+ {
+ if (index % layerIncrement != 0) continue;
+ newLayers[count] = thresholdLayers[index];
+ indexes[count++] = index;
+
+ }
+
+ progress.ResetNameAndProcessed("Packed layers");
+ Parallel.ForEach(indexes, CoreSettings.GetParallelOptions(progress), i =>
+ {
+ progress.LockAndIncrement();
+ using var mat = thresholdLayers[i].LayerMat;
+ for (int index = i+1; index < i + layerIncrement && index < thresholdLayers.Length; index++)
+ {
+ using var nextMat = thresholdLayers[index].LayerMat;
+ CvInvoke.Max(mat, nextMat, mat);
+ progress.LockAndIncrement();
+ }
+
+ thresholdLayers[i].LayerMat = mat;
+ });
+
+
+ thresholdLayers = newLayers;
+ }
+ else if (layerIncrementF < 1)
+ {
+ var layerIncrement = (uint)(1/layerIncrementF);
+ if (layerIncrement > 1)
+ {
+ progress.Reset("Packed layers");
+ var newLayers = new Layer[thresholdLayers.Length * layerIncrement];
+ for (int i = 0; i < thresholdLayers.Length; i++)
+ {
+ var layer = thresholdLayers[i];
+ var newIndex = i * layerIncrement;
+ newLayers[newIndex] = layer;
+ for (int x = 1; x < layerIncrement; x++)
+ {
+ newLayers[++newIndex] = layer.Clone();
+ }
+
+ }
+ thresholdLayers = newLayers;
+ }
+ }
+ }
+
+ if (_baseType != LithophaneBaseType.None && _baseThickness > 0)
+ {
+ int baseLayerCount = (int)(_baseThickness / _layerHeight);
+ var newLayers = new Layer[thresholdLayers.Length + baseLayerCount];
+ using var baseMat = SlicerFile.CreateMat();
+
+ switch (_baseType)
+ {
+ case LithophaneBaseType.Square:
+ {
+ var rectangle = new Rectangle(
+ baseMat.Width / 2 - mat.Width / 2 - _baseMargin / 2,
+ baseMat.Height / 2 - mat.Height / 2 - _baseMargin / 2,
+ mat.Width + _baseMargin,
+ mat.Height + _baseMargin);
+ CvInvoke.Rectangle(baseMat, rectangle, EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
+ break;
+ }
+ case LithophaneBaseType.Model:
+ {
+ using var dilatedMat = new Mat();
+ CvInvoke.Threshold(mat, dilatedMat, 1, byte.MaxValue, ThresholdType.Binary);
+ CvInvoke.Dilate(dilatedMat, dilatedMat, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _baseMargin, BorderType.Reflect101, default);
+ dilatedMat.CopyToCenter(baseMat);
+ break;
+ }
+ }
+
+ var baseLayer = new Layer(baseMat, SlicerFile);
+ newLayers[0] = baseLayer;
+ for (int i = 1; i < baseLayerCount; i++)
+ {
+ newLayers[i] = baseLayer.Clone();
+ }
+ Array.Copy(thresholdLayers, 0, newLayers, baseLayerCount, thresholdLayers.Length);
+ thresholdLayers = newLayers;
+ }
+
+ SlicerFile.SuppressRebuildPropertiesWork(() =>
+ {
+ SlicerFile.LayerHeight = (float) _layerHeight;
+ SlicerFile.BottomLayerCount = _bottomLayerCount;
+ SlicerFile.BottomExposureTime = (float) _bottomExposure;
+ SlicerFile.ExposureTime = (float) _normalExposure;
+
+ SlicerFile.Layers = thresholdLayers;
+ }, true);
+
+
+ using var bgrMat = new Mat();
+ CvInvoke.CvtColor(mat, bgrMat, ColorConversion.Gray2Bgr);
+ int baseLine = 0;
+ var textSize = CvInvoke.GetTextSize("UVtools Lithophane", FontFace.HersheyDuplex, 2, 3, ref baseLine);
+ CvInvoke.PutText(bgrMat, "UVtools Lithophane", new Point(bgrMat.Width / 2 - textSize.Width / 2, 60), FontFace.HersheyDuplex, 2, new MCvScalar(255, 27, 245), 3);
+ SlicerFile.SetThumbnails(bgrMat);
+
+ return !progress.Token.IsCancellationRequested;
+ }
+
+
+ #endregion
+} \ No newline at end of file
diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs
index 8ae65f2..1613018 100644
--- a/UVtools.Core/Operations/OperationPixelArithmetic.cs
+++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs
@@ -866,7 +866,7 @@ public class OperationPixelArithmetic : Operation
default:
throw new ArgumentOutOfRangeException(nameof(_ignoreAreaOperator));
}
- ApplyMask(originalRoi, target);
+ ApplyMask(original, mat);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 69928f1..32b8612 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>3.2.2</Version>
+ <Version>3.3.0</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
index fb18481..3257bcb 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ComponentRules="OneToOne"?>
<!-- SourceDir instructs IsWiX the location of the directory that contains files for this merge module -->
- <?define SourceDir="..\publish\UVtools_win-x64_v3.2.2"?>
+ <?define SourceDir="..\publish\UVtools_win-x64_v3.3.0"?>
<Module Id="UVtools" Language="1033" Version="1.0.0.0">
<Package Id="12aaa1cf-ff06-4a02-abd5-2ac01ac4f83b" Manufacturer="PTRTECH" InstallerVersion="200" Keywords="MSLA, DLP" Description="MSLA/DLP, file analysis, repair, conversion and manipulation" InstallScope="perMachine" Platform="x64" />
<Directory Id="TARGETDIR" Name="SourceDir">
diff --git a/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml b/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml
new file mode 100644
index 0000000..39d0207
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml
@@ -0,0 +1,246 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Controls.Tools.ToolLithophaneControl">
+ <Grid ColumnDefinitions="Auto,10,350">
+ <StackPanel Spacing="10">
+ <Grid
+ RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,190,20,Auto,10,190">
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Layer height:"/>
+ <NumericUpDown Grid.Row="0" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ Increment="0.01"
+ Minimum="0.01"
+ Maximum="0.30"
+ FormatString="F3"
+ Value="{Binding Operation.LayerHeight}"/>
+
+ <TextBlock Grid.Row="0" Grid.Column="4"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Right"
+ Text="Bottom layer count:"/>
+ <NumericUpDown Grid.Row="0" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_layers"
+ Increment="1"
+ Minimum="1"
+ Maximum="1000"
+ Value="{Binding Operation.BottomLayerCount}"/>
+
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Bottom exposure:"/>
+ <NumericUpDown Grid.Row="2" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_s"
+ Increment="0.5"
+ Minimum="0.1"
+ Maximum="200"
+ FormatString="F2"
+ Value="{Binding Operation.BottomExposure}"/>
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Right"
+ Text="Normal exposure:"/>
+ <NumericUpDown Grid.Row="2" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_s"
+ Increment="0.5"
+ Minimum="0.1"
+ Maximum="200"
+ FormatString="F2"
+ Value="{Binding Operation.NormalExposure}"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Image:"/>
+
+ <Grid Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="5"
+ ColumnDefinitions="*,Auto">
+ <TextBox Grid.Column="0"
+ IsReadOnly="True"
+ VerticalAlignment="Center"
+ Text="{Binding Operation.FilePath}"/>
+ <Button Grid.Column="1"
+ VerticalAlignment="Stretch"
+ Command="{Binding SelectFile}"
+ i:Attached.Icon="fas fa-file-import"/>
+ </Grid>
+
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Rotate:"/>
+
+ <ComboBox Grid.Row="6" Grid.Column="2"
+ HorizontalAlignment="Stretch"
+ Items="{Binding Operation.Rotate, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+ SelectedItem="{Binding Operation.Rotate, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
+
+ <StackPanel Grid.Row="6" Grid.Column="4" Grid.ColumnSpan="3"
+ Orientation="Horizontal" Spacing="20">
+ <CheckBox VerticalAlignment="Center"
+ IsChecked="{Binding Operation.Mirror}"
+ Content="Mirror"/>
+
+ <CheckBox VerticalAlignment="Center"
+ IsChecked="{Binding Operation.InvertColor}"
+ Content="Invert color"/>
+ </StackPanel>
+
+ <TextBlock Grid.Row="8" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Resize:"/>
+
+ <NumericUpDown Grid.Row="8" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_percent"
+ Minimum="1"
+ Maximum="900"
+ Increment="1"
+ FormatString="F2"
+ Value="{Binding Operation.ResizeFactor}"/>
+
+ <TextBlock Grid.Row="10" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Brightness gain:"/>
+
+ <NumericUpDown Grid.Row="10" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_sun"
+ Minimum="-128"
+ Maximum="127"
+ Increment="1"
+ Value="{Binding Operation.BrightnessGain}"/>
+
+ <CheckBox Grid.Row="10" Grid.Column="4" Grid.ColumnSpan="3"
+ VerticalAlignment="Center"
+ IsChecked="{Binding Operation.EnhanceContrast}"
+ Content="Enhance contrast"/>
+
+ <TextBlock Grid.Row="12" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Remove noise:"/>
+
+ <NumericUpDown Grid.Row="12" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_px"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.RemoveNoiseIterations}"/>
+
+ <TextBlock Grid.Row="12" Grid.Column="4"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Right"
+ Text="Gap closing:"/>
+
+ <NumericUpDown Grid.Row="12" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_px"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.GapClosingIterations}"/>
+
+ <TextBlock Grid.Row="14" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Gaussian blur:"/>
+
+ <NumericUpDown Grid.Row="14" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_px"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.GaussianBlur}"/>
+
+ <TextBlock Grid.Row="16" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Start threshold:"/>
+
+ <NumericUpDown Grid.Row="16" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_sun"
+ Minimum="1"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.StartThresholdRange}"/>
+
+ <TextBlock Grid.Row="16" Grid.Column="4"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Right"
+ Text="End threshold:"/>
+
+ <NumericUpDown Grid.Row="16" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_sun"
+ Minimum="1"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.EndThresholdRange}"/>
+
+ <TextBlock Grid.Row="18" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Base type:"/>
+
+ <ComboBox Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="5"
+ HorizontalAlignment="Stretch"
+ Items="{Binding Operation.BaseType, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+ SelectedItem="{Binding Operation.BaseType, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
+
+ <TextBlock Grid.Row="20" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Base thickness:"/>
+
+ <NumericUpDown Grid.Row="20" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ FormatString="F2"
+ Value="{Binding Operation.BaseThickness}"/>
+
+ <TextBlock Grid.Row="20" Grid.Column="4"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Text="Base margin:"/>
+
+ <NumericUpDown Grid.Row="20" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_px"
+ Minimum="0"
+ Maximum="65535"
+ Increment="1"
+ Value="{Binding Operation.BaseMargin}"/>
+
+ <TextBlock Grid.Row="22" Grid.Column="0"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding !Operation.OneLayerPerThreshold}"
+ Text="Lithophane height:"/>
+
+ <NumericUpDown Grid.Row="22" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_px"
+ Minimum="0"
+ Maximum="10000"
+ Increment="1"
+ IsEnabled="{Binding !Operation.OneLayerPerThreshold}"
+ Value="{Binding Operation.LithophaneHeight}"/>
+
+ <CheckBox Grid.Row="22" Grid.Column="4" Grid.ColumnSpan="3"
+ VerticalAlignment="Center"
+ IsChecked="{Binding Operation.OneLayerPerThreshold}"
+ Content="One layer per threshold level"/>
+
+ <CheckBox Grid.Row="24" Grid.Column="2"
+ VerticalAlignment="Center"
+ IsChecked="{Binding Operation.EnableAntiAliasing}"
+ Content="Enable Anti-Aliasing"/>
+ </Grid>
+
+ </StackPanel>
+
+ <StackPanel Grid.Column="2" Orientation="Vertical" Spacing="10">
+ <Image Stretch="Uniform"
+ Source="{Binding PreviewImage}"/>
+
+ <TextBlock Text="{Binding PreviewImage.Size, StringFormat=Size: {0}}" HorizontalAlignment="Center"/>
+ </StackPanel>
+ </Grid>
+</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml.cs
new file mode 100644
index 0000000..f75f7e7
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolLithophaneControl.axaml.cs
@@ -0,0 +1,82 @@
+using System.Timers;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
+using UVtools.Core.Operations;
+using UVtools.WPF.Extensions;
+using UVtools.WPF.Windows;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public partial class ToolLithophaneControl : ToolControl
+ {
+ public OperationLithophane Operation => BaseOperation as OperationLithophane;
+
+ private readonly Timer _timer;
+
+ private Bitmap _previewImage;
+ public Bitmap PreviewImage
+ {
+ get => _previewImage;
+ set => RaiseAndSetIfChanged(ref _previewImage, value);
+ }
+
+ public ToolLithophaneControl()
+ {
+ BaseOperation = new OperationLithophane(SlicerFile);
+ if (!ValidateSpawn()) return;
+ InitializeComponent();
+
+ _timer = new Timer(20)
+ {
+ AutoReset = false
+ };
+ _timer.Elapsed += (sender, e) => Dispatcher.UIThread.InvokeAsync(UpdatePreview);
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void Callback(ToolWindow.Callbacks callback)
+ {
+ if (App.SlicerFile is null) return;
+ switch (callback)
+ {
+ case ToolWindow.Callbacks.Init:
+ case ToolWindow.Callbacks.Loaded:
+ Operation.PropertyChanged += (sender, e) =>
+ {
+ _timer.Stop();
+ _timer.Start();
+ };
+ _timer.Stop();
+ _timer.Start();
+ break;
+ }
+ }
+
+ public void UpdatePreview()
+ {
+ using var mat = Operation.GetTargetMat();
+ _previewImage?.Dispose();
+ PreviewImage = mat?.ToBitmap();
+ }
+
+ public async void SelectFile()
+ {
+ var dialog = new OpenFileDialog
+ {
+ AllowMultiple = false,
+ Filters = Helpers.ImagesFileFilter,
+ };
+
+ var files = await dialog.ShowAsync(ParentWindow);
+ if (files is null || files.Length == 0) return;
+
+ Operation.FilePath = files[0];
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index b8bb051..ff0db6f 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -24,11 +24,13 @@ using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
+using MessageBox.Avalonia.Enums;
using UVtools.AvaloniaControls;
using UVtools.Core;
using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.Layers;
+using UVtools.Core.Operations;
using UVtools.Core.PixelEditor;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
@@ -798,6 +800,18 @@ public partial class MainWindow
ActualLayer = SliderMaximumValue;
}
+ public void GoUpLayers(uint layers)
+ {
+ if (!IsFileLoaded) return;
+ ActualLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + layers);
+ }
+
+ public void GoDownLayers(uint layers)
+ {
+ if (!IsFileLoaded) return;
+ ActualLayer = (uint)Math.Max(0, (int)ActualLayer - layers);
+ }
+
public void GoMassLayer(string which)
{
if (!IsFileLoaded) return;
@@ -1884,41 +1898,141 @@ public partial class MainWindow
private void LayerImageBox_KeyDown(object? sender, KeyEventArgs e)
{
- if (e.Key == Key.Up)
+ switch (e.Key)
{
- GoNextLayer();
- e.Handled = true;
- return;
- }
-
- if (e.Key == Key.Down)
- {
- GoPreviousLayer();
- e.Handled = true;
- return;
+ case Key.Up:
+ GoNextLayer();
+ e.Handled = true;
+ return;
+ case Key.Down:
+ GoPreviousLayer();
+ e.Handled = true;
+ return;
+ case Key.PageUp:
+ GoUpLayers(10);
+ e.Handled = true;
+ return;
+ case Key.PageDown:
+ GoDownLayers(10);
+ e.Handled = true;
+ return;
}
}
- private void LayerImageBox_KeyUp(object? sender, KeyEventArgs e)
+ private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e)
{
- if (e.Key == Key.Escape)
+ switch (e.Key)
{
- if (e.KeyModifiers == KeyModifiers.Shift)
+ case Key.Escape:
{
- ClearROI();
+ if (e.KeyModifiers == KeyModifiers.Shift)
+ {
+ ClearROI();
+ }
+ /*else if(e.KeyModifiers == KeyModifiers.Alt)
+ {
+ ClearMask();
+ }*/
+ else
+ {
+ ClearROIAndMask();
+ }
+ e.Handled = true;
+ return;
}
- /*else if(e.KeyModifiers == KeyModifiers.Alt)
+ case Key.Insert:
{
- ClearMask();
- }*/
- else
+ if (e.KeyModifiers == KeyModifiers.Control)
+ {
+ if (await this.MessageBoxQuestion($"Are you sure you want to clone the current layer {_actualLayer}?",
+ "Clone the current layer?") != ButtonResult.Yes) return;
+
+ var operationLayerClone = new OperationLayerClone(SlicerFile);
+ operationLayerClone.SelectCurrentLayer(_actualLayer);
+ await RunOperation(operationLayerClone);
+
+ e.Handled = true;
+ return;
+ }
+
+ if (ROI == Rectangle.Empty && _maskPoints.Count == 0) return;
+ var operation = new OperationPixelArithmetic(SlicerFile)
+ {
+ Operator = OperationPixelArithmetic.PixelArithmeticOperators.KeepRegion,
+ ROI = ROI,
+ MaskPoints = _maskPoints.ToArray()
+ };
+
+ string layerRange;
+ if (e.KeyModifiers == KeyModifiers.Alt)
+ {
+ operation.SelectAllLayers();
+ layerRange = $"within all {SlicerFile.LayerCount} layers";
+ }
+ else
+ {
+ operation.SelectCurrentLayer(ActualLayer);
+ layerRange = $"in the current {ActualLayer} layer";
+ }
+
+ if (await this.MessageBoxQuestion($"Are you sure you want to keep only the selected region/mask(s) {layerRange}?",
+ "Keep only selected region/mask(s)?") != ButtonResult.Yes) return;
+ await RunOperation(operation);
+ e.Handled = true;
+ return;
+ }
+ case Key.Delete:
{
- ClearROIAndMask();
+ if (e.KeyModifiers == KeyModifiers.Control)
+ {
+ if (await this.MessageBoxQuestion($"Are you sure you want to remove the current layer {_actualLayer}?",
+ "Remove the current layer?") != ButtonResult.Yes) return;
+
+ var operationLayerRemove = new OperationLayerRemove(SlicerFile);
+ operationLayerRemove.SelectCurrentLayer(_actualLayer);
+ await RunOperation(operationLayerRemove);
+
+ e.Handled = true;
+ return;
+ }
+
+ if (ROI == Rectangle.Empty && _maskPoints.Count == 0) return;
+ var operation = new OperationPixelArithmetic(SlicerFile)
+ {
+ Operator = OperationPixelArithmetic.PixelArithmeticOperators.DiscardRegion,
+ ROI = ROI,
+ MaskPoints = _maskPoints.ToArray()
+ };
+
+ string layerRange;
+ if (e.KeyModifiers == KeyModifiers.Alt)
+ {
+ operation.SelectAllLayers();
+ layerRange = $"within all {SlicerFile.LayerCount} layers";
+ }
+ else
+ {
+ operation.SelectCurrentLayer(ActualLayer);
+ layerRange = $"in the current {ActualLayer} layer";
+ }
+
+ if (await this.MessageBoxQuestion($"Are you sure you want to discard the selected region/mask(s) {layerRange}?",
+ "Discard selected region/mask(s)?") != ButtonResult.Yes) return;
+ await RunOperation(operation);
+ e.Handled = true;
+ return;
}
- e.Handled = true;
- return;
+ case Key.Home:
+ GoFirstLayer();
+ e.Handled = true;
+ return;
+ case Key.End:
+ GoLastLayer();
+ e.Handled = true;
+ return;
}
+
if ((e.KeyModifiers & KeyModifiers.Control) != 0)
{
if (e.Key is Key.LeftShift or Key.RightShift || (e.KeyModifiers & KeyModifiers.Shift) != 0) // Ctrl + Shift
@@ -1968,6 +2082,7 @@ public partial class MainWindow
if (e.Key == Key.B)
{
SelectModelVolumeRoi();
+ e.Handled = true;
return;
}
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 7165d44..efdda1d 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -168,6 +168,10 @@ public partial class MainWindow : WindowEx
},
new()
{
+ Tag = new OperationLithophane(),
+ },
+ new()
+ {
Tag = new OperationScripting(),
},
new()
diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs
index 6691e49..774abcb 100644
--- a/UVtools.WPF/Structures/OperationProfiles.cs
+++ b/UVtools.WPF/Structures/OperationProfiles.cs
@@ -47,6 +47,7 @@ public class OperationProfiles //: IList<Operation>
[XmlElement(typeof(OperationRaiseOnPrintFinish))]
[XmlElement(typeof(OperationChangeResolution))]
[XmlElement(typeof(OperationTimelapse))]
+ [XmlElement(typeof(OperationLithophane))]
[XmlElement(typeof(OperationScripting))]
[XmlElement(typeof(OperationLayerExportGif))]
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index b688f5d..77dd831 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>3.2.2</Version>
+ <Version>3.3.0</Version>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>UVtools.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>