diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27353d0f..45e38d59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 14/09/2024 - v4.4.2
+
+- (Add) Tool - Redraw model: Add multiple operator modes (#926)
+- (Fix) Tool - Redraw model: Redo (Ctrl + Shift + Z) would cause a crash
+- (Fix) Pixel Editor: Make the content scrollable when the window is resized to a smaller size
+- (Fix) Calibration - Exposure time finder: When the "Multiple exposures" panel is collapsed it become disabled and unusable
+- (Fix) Layer preview - Difference: Fixes the white background over black pixels
+- (Fix) macOS: Change title "Avalonia Application" to "UVtools" on some app managers
+
## 19/08/2024 - v4.4.1
- (Add) Pixel Editor: Fill tool and merge into Erase section, left click fills and right click erases
diff --git a/Directory.Build.props b/Directory.Build.props
index 3abb727e..32a9094c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -31,7 +31,7 @@
$(MSBuildThisFileDirectory)publish
- 4.4.1
+ 4.4.2
11.1.3
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0c69cfce..1ab91a1b 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,7 @@
-- (Add) Pixel Editor: Fill tool and merge into Erase section, left click fills and right click erases
-- (Improvement) SL1: Implement missing material override properties
-- (Fix) File formats: Error while trying to generate a thumbnail for a file that have a empty first layer (#912)
-- (Upgrade) AvaloniaUI from 11.1.1 to 11.1.3
-- (Upgrade) .NET from 6.0.32 to 6.0.33
+- (Add) Tool - Redraw model: Add multiple operator modes (#926)
+- (Fix) Tool - Redraw model: Redo (Ctrl + Shift + Z) would cause a crash
+- (Fix) Pixel Editor: Make the content scrollable when the window is resized to a smaller size
+- (Fix) Calibration - Exposure time finder: When the "Multiple exposures" panel is collapsed it become disabled and unusable
+- (Fix) Layer preview - Difference: Fixes the white background over black pixels
+- (Fix) macOS: Change title "Avalonia Application" to "UVtools" on some app managers
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index b719744b..cf8c03b9 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -67,15 +67,6 @@ public static byte[] CreateByteArray(this Mat mat)
public static Mat New(this Mat mat)
=> new(mat.Size, mat.Depth, mat.NumberOfChannels);
- ///
- /// Creates a new with same size and type of the source
- ///
- ///
- ///
- ///
- public static Mat New(this Mat src, MCvScalar color)
- => InitMat(src.Size, color, src.NumberOfChannels, src.Depth);
-
///
/// Creates a new blanked (All zeros) with same size and type of the source
///
@@ -97,9 +88,10 @@ public static UMat NewZeros(this UMat mat)
///
///
///
+ ///
///
- public static Mat NewSetTo(this Mat mat, MCvScalar color)
- => InitMat(mat.Size, color, mat.NumberOfChannels, mat.Depth);
+ public static Mat NewSetTo(this Mat mat, MCvScalar color, IInputArray? mask = null)
+ => InitMat(mat.Size, color, mat.NumberOfChannels, mat.Depth, mask);
///
@@ -134,12 +126,13 @@ public static UMat InitUMat(Size size, int channels = 1, DepthType depthType = D
///
///
///
+ ///
///
- public static Mat InitMat(Size size, MCvScalar color, int channels = 1, DepthType depthType = DepthType.Cv8U)
+ public static Mat InitMat(Size size, MCvScalar color, int channels = 1, DepthType depthType = DepthType.Cv8U, IInputArray? mask = null)
{
if (size.IsEmpty) return new();
var mat = new Mat(size, depthType, channels);
- mat.SetTo(color);
+ mat.SetTo(color, mask);
return mat;
}
diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
index 2aeaf4ab..5d2059d4 100644
--- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
@@ -1293,7 +1293,7 @@ public Mat[] GetLayers(bool isPreview = false)
var bars = Bars;
var bulleyes = BullsEyes;
var textSize = TextSize;
-
+
int featuresMarginX = (int)(Xppmm * _featuresMargin);
int featuresMarginY = (int)(Yppmm * _featuresMargin);
ushort startCaseThickness = StaircaseThickness;
diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs
index e6912282..e5f3f936 100644
--- a/UVtools.Core/Operations/OperationRedrawModel.cs
+++ b/UVtools.Core/Operations/OperationRedrawModel.cs
@@ -10,6 +10,7 @@
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
+using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
@@ -23,6 +24,29 @@ namespace UVtools.Core.Operations;
public class OperationRedrawModel : Operation
#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode()
{
+ #region Enums
+ public enum RedrawModelOperators : byte
+ {
+ [Description("Set: to a brightness")]
+ Set,
+ [Description("Add: with a brightness")]
+ Add,
+ [Description("Subtract: with a brightness")]
+ Subtract,
+ [Description("Multiply: with a brightness")]
+ Multiply,
+ [Description("Divide: with a brightness")]
+ Divide,
+ [Description("Minimum: set to a brightness if is lower than the current pixel")]
+ Minimum,
+ [Description("Maximum: set to a brightness if is higher than the current pixel")]
+ Maximum,
+ [Description("AbsDiff: perform a absolute difference between pixel and brightness")]
+ AbsDiff,
+ }
+
+ #endregion
+
#region Members
private string _filePath = null!;
@@ -30,6 +54,7 @@ public class OperationRedrawModel : Operation
private bool _contactPointsOnly = true;
private RedrawTypes _redrawType = RedrawTypes.Supports;
private bool _ignoreContactLessPixels = true;
+ private RedrawModelOperators _operator = RedrawModelOperators.Minimum;
#endregion
@@ -44,7 +69,7 @@ public class OperationRedrawModel : Operation
"Note: Run this tool prior to any made modification. You must find the optimal exposure/brightness combo, or supports can fail.";
public override string ConfirmationText => "redraw the "+ (_redrawType == RedrawTypes.Supports ? "supports" : "model") +
- $" with an brightness of {_brightness}?";
+ " with an"+ (_redrawType == RedrawTypes.Model || !_contactPointsOnly ? $" {_operator}" : string.Empty) + $" brightness of {_brightness}?";
public override string ProgressTitle => "Redrawing " + (_redrawType == RedrawTypes.Supports ? "supports" : "model");
@@ -59,13 +84,30 @@ public class OperationRedrawModel : Operation
sb.AppendLine("The selected file is not valid.");
}
+ if (_redrawType == RedrawTypes.Model || _contactPointsOnly)
+ {
+ switch (_operator)
+ {
+ case RedrawModelOperators.Add:
+ case RedrawModelOperators.Subtract:
+ case RedrawModelOperators.Maximum:
+ case RedrawModelOperators.AbsDiff:
+ if (_brightness == 0) sb.AppendLine($"{_operator} with a brightness of 0 yield no result, please use a value larger than 0.");
+ break;
+ case RedrawModelOperators.Divide:
+ if (_brightness == 0) sb.AppendLine($"{_operator} with a brightness of 0 is not valid, please use a value larger than 0.");
+ else if (_brightness == 1) sb.AppendLine($"{_operator} with a brightness of 0 yield no result, please use a value larger than 0.");
+ break;
+ }
+ }
+
return sb.ToString();
}
public override string ToString()
{
- var result = $"[{_redrawType}] [B: {_brightness}] [CS: {_contactPointsOnly}] [ICLP: {_ignoreContactLessPixels}]";
+ var result = $"[{_redrawType}] [B: {_brightness}] [OP: {_operator}] [CS: {_contactPointsOnly}] [ICLP: {_ignoreContactLessPixels}]";
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
@@ -104,7 +146,11 @@ public RedrawTypes RedrawType
set => RaiseAndSetIfChanged(ref _redrawType, value);
}
- public static Array RedrawTypesItems => Enum.GetValues(typeof(RedrawTypes));
+ public RedrawModelOperators Operator
+ {
+ get => _operator;
+ set => RaiseAndSetIfChanged(ref _operator, value);
+ }
public byte Brightness
{
@@ -169,6 +215,11 @@ protected override bool ExecuteInternally(OperationProgress progress)
if (startLayerIndex < 0) return false;
Parallel.For(0, otherFile.LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ if (SlicerFile[layerIndex].IsEmpty)
+ {
+ progress.LockAndIncrement();
+ return;
+ }
progress.PauseIfRequested();
var fullMatLayerIndex = startLayerIndex + layerIndex;
using var fullMat = SlicerFile[fullMatLayerIndex].LayerMat;
@@ -176,7 +227,6 @@ protected override bool ExecuteInternally(OperationProgress progress)
using var bodyMat = otherFile[layerIndex].LayerMat;
using var fullMatRoi = GetRoiOrDefault(fullMat);
using var bodyMatRoi = GetRoiOrDefault(bodyMat);
- using var patternMat = EmguExtensions.InitMat(fullMatRoi.Size, new MCvScalar(255 - _brightness));
using var supportsMat = new Mat();
bool modified = false;
@@ -229,9 +279,41 @@ protected override bool ExecuteInternally(OperationProgress progress)
case RedrawTypes.Model:
CvInvoke.BitwiseAnd(fullMatRoi, bodyMatRoi, supportsMat); // Model
break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(RedrawType), _redrawType, null);
}
- CvInvoke.Subtract(fullMatRoi, patternMat, fullMatRoi, supportsMat);
+ using var patternMat = fullMatRoi.NewSetTo(new MCvScalar(_brightness), supportsMat);
+
+ switch (_operator)
+ {
+ case RedrawModelOperators.Set:
+ patternMat.CopyTo(fullMatRoi, fullMatRoi);
+ break;
+ case RedrawModelOperators.Add:
+ CvInvoke.Add(fullMatRoi, patternMat, fullMatRoi, supportsMat);
+ break;
+ case RedrawModelOperators.Subtract:
+ CvInvoke.Subtract(fullMatRoi, patternMat, fullMatRoi, supportsMat);
+ break;
+ case RedrawModelOperators.Multiply:
+ CvInvoke.Multiply(fullMatRoi, patternMat, fullMatRoi, EmguExtensions.ByteScale);
+ break;
+ case RedrawModelOperators.Divide:
+ CvInvoke.Divide(fullMatRoi, patternMat, fullMatRoi);
+ break;
+ case RedrawModelOperators.Minimum:
+ CvInvoke.Min(fullMatRoi, patternMat, fullMatRoi);
+ break;
+ case RedrawModelOperators.Maximum:
+ CvInvoke.Max(fullMatRoi, patternMat, fullMatRoi);
+ break;
+ case RedrawModelOperators.AbsDiff:
+ CvInvoke.AbsDiff(fullMatRoi, patternMat, fullMatRoi);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(Operator), _operator, null);
+ }
modified = true;
}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 8bf390b2..0b1de9a8 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/UVtools.UI/App.axaml b/UVtools.UI/App.axaml
index 422d5518..e6007934 100644
--- a/UVtools.UI/App.axaml
+++ b/UVtools.UI/App.axaml
@@ -1,9 +1,12 @@
+
diff --git a/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml
index 53aba0c3..33094add 100644
--- a/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml
+++ b/UVtools.UI/Controls/Calibrators/CalibrateExposureFinderControl.axaml
@@ -746,7 +746,7 @@
+ IsExpanded="{Binding $self.IsEnabled, Mode=OneTime}">
diff --git a/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml b/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml
index 1e86459c..53bc7647 100644
--- a/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml
+++ b/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml
@@ -4,25 +4,24 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="https://github.com/projektanker/icons.avalonia"
xmlns:tools="clr-namespace:UVtools.UI.Controls.Tools"
+ xmlns:op="clr-namespace:UVtools.Core.Operations;assembly=UVtools.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UVtools.UI.Controls.Tools.ToolRedrawModelControl"
x:DataType="tools:ToolRedrawModelControl">
-
-
-
+
@@ -34,24 +33,28 @@
Text="Redraw:"/>
-
+
-
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml.cs b/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml.cs
index d93f4da1..2c9c09fb 100644
--- a/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml.cs
+++ b/UVtools.UI/Controls/Tools/ToolRedrawModelControl.axaml.cs
@@ -23,7 +23,7 @@ public override void Callback(ToolWindow.Callbacks callback)
{
case ToolWindow.Callbacks.Init:
case ToolWindow.Callbacks.AfterLoadProfile:
- ParentWindow!.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.FilePath);
+ if (ParentWindow is not null) ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.FilePath);
Operation.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Operation.FilePath))
diff --git a/UVtools.UI/Converters/EnumToBooleanConverter.cs b/UVtools.UI/Converters/EnumToBooleanConverter.cs
new file mode 100644
index 00000000..33b84525
--- /dev/null
+++ b/UVtools.UI/Converters/EnumToBooleanConverter.cs
@@ -0,0 +1,19 @@
+using System;
+using Avalonia.Data;
+using System.Globalization;
+using Avalonia.Data.Converters;
+
+namespace UVtools.UI.Converters;
+
+public class EnumToBooleanConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.ToString()?.Equals(parameter);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;
+ }
+}
\ No newline at end of file
diff --git a/UVtools.UI/MainWindow.axaml b/UVtools.UI/MainWindow.axaml
index 194ddff1..0f21f704 100644
--- a/UVtools.UI/MainWindow.axaml
+++ b/UVtools.UI/MainWindow.axaml
@@ -1080,11 +1080,10 @@
-
+
@@ -1092,579 +1091,559 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
-
+
+
+
diff --git a/UVtools.UI/Structures/Color.cs b/UVtools.UI/Structures/Color.cs
index 15d8d4d5..d61eb0f6 100644
--- a/UVtools.UI/Structures/Color.cs
+++ b/UVtools.UI/Structures/Color.cs
@@ -56,7 +56,7 @@ public Avalonia.Media.Color ToAvalonia()
return new Avalonia.Media.Color(A, R, G, B);
}
- public bool IsEmpty => ReferenceEquals(this, Empty);
+ public bool IsEmpty => Equals(Empty);
public static Color FromArgb(byte a, byte r, byte g, byte b)
{
diff --git a/build/platforms/osx/Info.plist b/build/platforms/osx/Info.plist
index f35958d6..03a0b15d 100644
--- a/build/platforms/osx/Info.plist
+++ b/build/platforms/osx/Info.plist
@@ -8,6 +8,8 @@
UVtools.icns
CFBundleIdentifier
com.UVtools
+ CFBundleDisplayName
+ UVtools
CFBundleName
UVtools
CFBundleVersion
diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml
index 63f75c0a..8f92f340 100644
--- a/documentation/UVtools.Core.xml
+++ b/documentation/UVtools.Core.xml
@@ -1088,14 +1088,6 @@
-
-
- Creates a new with same size and type of the source
-
-
-
-
-
Creates a new blanked (All zeros) with same size and type of the source
@@ -1110,12 +1102,13 @@
Blanked
-
+
Creates a with same size and type of the source and set it to a color
+
@@ -1136,7 +1129,7 @@
-
+
Creates a new and set it to a
@@ -1144,6 +1137,7 @@
+