diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-11-23 05:28:31 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-11-23 05:28:31 +0300 |
commit | c71ff0dd923b8e9b2f40178621008cbb3f5ae8dc (patch) | |
tree | b814abe6d9fce37f2414f74d9951fd0a9ab46f6e | |
parent | 6f6b7c07e58a385efac7c2c025d915f60eb923d6 (diff) |
v2.25.1v2.25.1
- **Change resolution:**
- (Add) Presets: 5K UHD, 6K and 8K UHD
- (Add) Resulting pixel ratio information
- (Add) Fix the pixel ratio by resize the layers images with the proposed ratio to match the new resolution
- (Fix) New images could have noise when processed on linux and macos
- (Add) Layer slider debounce time to render the image [Configurable] (#343)
- (Fix) CTB v1: Incorrect getter for the LightOffDelay
- (Fix) Reallocating layers were not notifying nor updating the layer collection about the changes, leading to wrong layer count
- (Fix) Undo and redo now also reverts the file resolution when changed
-rw-r--r-- | CHANGELOG.md | 14 | ||||
-rw-r--r-- | UVtools.Core/Extensions/EmguExtensions.cs | 86 | ||||
-rw-r--r-- | UVtools.Core/Extensions/SizeExtensions.cs | 25 | ||||
-rw-r--r-- | UVtools.Core/FileFormats/ChituboxFile.cs | 21 | ||||
-rw-r--r-- | UVtools.Core/FileFormats/FileFormat.cs | 3 | ||||
-rw-r--r-- | UVtools.Core/Layers/LayerManager.cs | 96 | ||||
-rw-r--r-- | UVtools.Core/Managers/ClipboardManager.cs | 22 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationChangeResolution.cs | 82 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationLayerImport.cs | 16 | ||||
-rw-r--r-- | UVtools.Core/UVtools.Core.csproj | 4 | ||||
-rw-r--r-- | UVtools.WPF/Assets/Styles/Styles.xaml | 6 | ||||
-rw-r--r-- | UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml | 94 | ||||
-rw-r--r-- | UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs | 2 | ||||
-rw-r--r-- | UVtools.WPF/MainWindow.LayerPreview.cs | 2 | ||||
-rw-r--r-- | UVtools.WPF/MainWindow.axaml | 4 | ||||
-rw-r--r-- | UVtools.WPF/MainWindow.axaml.cs | 11 | ||||
-rw-r--r-- | UVtools.WPF/UVtools.WPF.csproj | 2 | ||||
-rw-r--r-- | UVtools.WPF/Windows/AboutWindow.axaml | 3 |
18 files changed, 357 insertions, 136 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f682326..c25a270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 23/11/2021 - v2.25.1 + +- **Change resolution:** + - (Add) Presets: 5K UHD, 6K and 8K UHD + - (Add) Resulting pixel ratio information + - (Add) Fix the pixel ratio by resize the layers images with the proposed ratio to match the new resolution + - (Fix) New images could have noise when processed on linux and macos +- (Add) Layer slider debounce time to render the image [Configurable] (#343) +- (Fix) CTB v1: Incorrect getter for the LightOffDelay +- (Fix) Reallocating layers were not notifying nor updating the layer collection about the changes, leading to wrong layer count +- (Fix) Undo and redo now also reverts the file resolution when changed + ## 18/11/2021 - v2.25.0 - **File formats:** @@ -7,7 +19,7 @@ - (Add) More abstraction on partial save - **Scripting:** - (Add) ScriptOpenFolderDialogInput - Selects a folder path - - (Add) ScriptOpenFileDialogInput - Selectes a file to open + - (Add) ScriptOpenFileDialogInput - Selects a file to open - (Add) ScriptSaveFileDialogInput - Selects a file to save - (Add) [UNSAVED] tag to the title bar when there are unsaved changes on the current session - (Improvement) Better handling of empty images on the UI diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index e41eb34..60d6e90 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -473,6 +473,30 @@ namespace UVtools.Core.Extensions #region Copy methods /// <summary> + /// Copy a region from <see cref="Mat"/> to center of other <see cref="Mat"/> + /// </summary> + /// <param name="src">Source <see cref="Mat"/> to be copied to</param> + /// <param name="size">Size of the center offset</param> + /// <param name="dst">Target <see cref="Mat"/> to paste the <param name="src"></param></param> + public static void CopyCenterToCenter(this Mat src, Size size, Mat dst) + { + var srcRoi = src.RoiFromCenter(size); + CopyToCenter(srcRoi, dst); + } + + /// <summary> + /// Copy a region from <see cref="Mat"/> to center of other <see cref="Mat"/> + /// </summary> + /// <param name="src">Source <see cref="Mat"/> to be copied to</param> + /// <param name="region">Region to copy</param> + /// <param name="dst">Target <see cref="Mat"/> to paste the <param name="src"></param></param> + public static void CopyRegionToCenter(this Mat src, Rectangle region, Mat dst) + { + var srcRoi = src.Roi(region); + CopyToCenter(srcRoi, dst); + } + + /// <summary> /// Copy a <see cref="Mat"/> to center of other <see cref="Mat"/> /// </summary> /// <param name="src">Source <see cref="Mat"/> to be copied to</param> @@ -481,20 +505,30 @@ namespace UVtools.Core.Extensions { var srcStep = src.GetRealStep(); var dstStep = dst.GetRealStep(); + var dx = Math.Abs(dstStep - srcStep) / 2; + var dy = Math.Abs(dst.Height - src.Height) / 2; + + if (src.Size == dst.Size) + { + src.CopyTo(dst); + return; + } + if (dstStep > srcStep && dst.Height > src.Height) { - var dx = Math.Abs(dstStep - srcStep) / 2; - var dy = Math.Abs(dst.Height - src.Height) / 2; - Mat m = new(dst, new Rectangle(dx, dy, src.Width, src.Height)); - src.CopyTo(m); + using var dstRoi = dst.Roi(new Rectangle(dx, dy, src.Width, src.Height)); + src.CopyTo(dstRoi); + return; } - else if (dstStep < srcStep && dst.Height < src.Height) + + if (dstStep < srcStep && dst.Height < src.Height) { - var dx = Math.Abs(dstStep - srcStep) / 2; - var dy = Math.Abs(dst.Height - src.Height) / 2; - Mat m = new(src, new Rectangle(dx, dy, dst.Width, dst.Height)); - m.CopyTo(dst); + using var srcRoi = src.Roi(new Rectangle(dx, dy, dst.Width, dst.Height)); + srcRoi.CopyTo(dst); + return; } + + throw new InvalidOperationException("Unable to copy, out of bounds"); } public static void CopyAreasSmallerThan(this Mat src, double threshold, Mat dst) @@ -564,29 +598,47 @@ namespace UVtools.Core.Extensions } /// <summary> + /// Gets a Roi from center, but return source when have same size as source + /// </summary> + /// <param name="mat"></param> + /// <param name="size"></param> + /// <returns></returns> + public static Mat RoiFromCenter(this Mat mat, Size size) + { + if(mat.Size == size) return mat; + + var newRoi = mat.Roi(new Rectangle( + mat.Width / 2 - size.Width / 2, + mat.Height / 2 - size.Height / 2, + size.Width, + size.Height + )); + + return newRoi; + } + + /// <summary> /// Gets a new mat obtained from it center at a target size and roi /// </summary> /// <param name="mat"></param> /// <param name="targetSize"></param> /// <param name="roi"></param> /// <returns></returns> - public static Mat NewRoiFromCenter(this Mat mat, Size targetSize, Rectangle roi) + public static Mat NewMatFromCenterRoi(this Mat mat, Size targetSize, Rectangle roi) { if (targetSize == mat.Size) return mat.Clone(); var newMat = InitMat(targetSize); - - var roiMat = new Mat(mat, roi); - - + var roiMat = mat.Roi(roi); + //int xStart = mat.Width / 2 - targetSize.Width / 2; //int yStart = mat.Height / 2 - targetSize.Height / 2; - - var newMatRoi = new Mat(newMat, new Rectangle( + var newMatRoi = newMat.RoiFromCenter(roi.Size); + /*var newMatRoi = new Mat(newMat, new Rectangle( targetSize.Width / 2 - roi.Width / 2, targetSize.Height / 2 - roi.Height / 2, roi.Width, roi.Height - )); + ));*/ roiMat.CopyTo(newMatRoi); return newMat; } diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs index 4c320bd..bf600ff 100644 --- a/UVtools.Core/Extensions/SizeExtensions.cs +++ b/UVtools.Core/Extensions/SizeExtensions.cs @@ -6,9 +6,7 @@ * of this license document, but changing it is not allowed. */ using System; -using System.Collections.Generic; using System.Drawing; -using System.Text; namespace UVtools.Core.Extensions { @@ -43,10 +41,25 @@ namespace UVtools.Core.Extensions SizeSuffixes[mag]); } - public static Size Inflate(this Size size, Size otherSize) => new (size.Width + otherSize.Width, size.Height + otherSize.Height); - public static Size Inflate(this Size size) => size.Inflate(size); - public static Size Inflate(this Size size, int pixels) => new (size.Width + pixels, size.Height + pixels); - public static Size Inflate(this Size size, int width, int height) => new (size.Width + width, size.Height + height); + public static Size Add(this Size size, Size otherSize) => new (size.Width + otherSize.Width, size.Height + otherSize.Height); + public static Size Add(this Size size) => size.Add(size); + public static Size Add(this Size size, int pixels) => new (size.Width + pixels, size.Height + pixels); + public static Size Add(this Size size, int width, int height) => new (size.Width + width, size.Height + height); + + public static Size Subtract(this Size size, Size otherSize) => new(size.Width - otherSize.Width, size.Height - otherSize.Height); + public static Size Subtract(this Size size) => size.Subtract(size); + public static Size Subtract(this Size size, int pixels) => new(size.Width - pixels, size.Height - pixels); + public static Size Subtract(this Size size, int width, int height) => new(size.Width - width, size.Height - height); + + public static Size Multiply(this Size size, SizeF otherSize) => new((int)(size.Width * otherSize.Width), (int)(size.Height * otherSize.Height)); + public static Size Multiply(this Size size) => size.Multiply(size); + public static Size Multiply(this Size size, double dxy) => new((int)(size.Width * dxy), (int)(size.Height * dxy)); + public static Size Multiply(this Size size, double dx, double dy) => new((int)(size.Width * dx), (int)(size.Height * dy)); + + public static Size Divide(this Size size, SizeF otherSize) => new((int)(size.Width / otherSize.Width), (int)(size.Height / otherSize.Height)); + public static Size Divide(this Size size) => size.Divide(size); + public static Size Divide(this Size size, double dxy) => new((int)(size.Width / dxy), (int)(size.Height / dxy)); + public static Size Divide(this Size size, double dx, double dy) => new((int)(size.Width / dx), (int)(size.Height / dy)); /// <summary> /// Gets if this size have a zero value on width or height diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index 2137b8f..f032d6d 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -1799,7 +1799,7 @@ namespace UVtools.Core.FileFormats } //uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); - LayerDefinitions = new LayerDef[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; + LayerDefinitions = new LayerDef[HeaderSettings.AntiAliasLevel, LayerCount]; using var outputFile = new FileStream(FileFullPath, FileMode.Create, FileAccess.Write); outputFile.Seek(Helpers.Serializer.SizeOf(HeaderSettings), SeekOrigin.Begin); @@ -1869,7 +1869,7 @@ namespace UVtools.Core.FileFormats HeaderSettings.LayersDefinitionOffsetAddress = (uint)outputFile.Position; uint layerDefSize = (uint)Helpers.Serializer.SizeOf(new LayerDef()); //uint layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; - uint layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + layerDefSize * HeaderSettings.LayerCount * HeaderSettings.AntiAliasLevel; + uint layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + layerDefSize * LayerCount * HeaderSettings.AntiAliasLevel; var layersHash = new Dictionary<string, LayerDef>(); progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); @@ -1932,7 +1932,7 @@ namespace UVtools.Core.FileFormats } outputFile.Seek(HeaderSettings.LayersDefinitionOffsetAddress + - aaIndex * HeaderSettings.LayerCount * layerDefSize + + aaIndex * LayerCount * layerDefSize + layerDefSize * layerIndex , SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, layerDef); @@ -2047,17 +2047,18 @@ namespace UVtools.Core.FileFormats } } - LayerDefinitions = new LayerDef[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; - var layerDefinitionsEx = HeaderSettings.Version >= 3 ? new LayerDefEx[HeaderSettings.LayerCount] : null; + LayerManager.Init(HeaderSettings.LayerCount, DecodeType == FileDecodeType.Partial); + LayerDefinitions = new LayerDef[HeaderSettings.AntiAliasLevel, LayerCount]; + var layerDefinitionsEx = HeaderSettings.Version >= 3 ? new LayerDefEx[LayerCount] : null; uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - progress.Reset(OperationProgress.StatusGatherLayers, HeaderSettings.AntiAliasLevel * HeaderSettings.LayerCount); + progress.Reset(OperationProgress.StatusGatherLayers, HeaderSettings.AntiAliasLevel * LayerCount); for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) { Debug.WriteLine($"-Image GROUP {aaIndex}-"); - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { progress.Token.ThrowIfCancellationRequested(); inputFile.Seek(layerOffset, SeekOrigin.Begin); @@ -2087,8 +2088,6 @@ namespace UVtools.Core.FileFormats } } - LayerManager.Init(HeaderSettings.LayerCount, DecodeType == FileDecodeType.Partial); - if (DecodeType == FileDecodeType.Full) { progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); @@ -2148,7 +2147,7 @@ namespace UVtools.Core.FileFormats uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { var layer = this[layerIndex]; LayerDefinitions[aaIndex, layerIndex].SetFrom(layer); @@ -2160,7 +2159,7 @@ namespace UVtools.Core.FileFormats if (HeaderSettings.Version >= 3) { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { outputFile.Seek(LayerDefinitions[0, layerIndex].DataAddress - 84, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, new LayerDefEx(LayerDefinitions[0, layerIndex], this[layerIndex])); diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index c39e592..f624301 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -993,6 +993,9 @@ namespace UVtools.Core.FileFormats ResolutionX = (uint) value.Width; ResolutionY = (uint) value.Height; RaisePropertyChanged(); + RaisePropertyChanged(nameof(Xppmm)); + RaisePropertyChanged(nameof(Yppmm)); + RaisePropertyChanged(nameof(Ppmm)); } } diff --git a/UVtools.Core/Layers/LayerManager.cs b/UVtools.Core/Layers/LayerManager.cs index 2b1a1bc..020e4a0 100644 --- a/UVtools.Core/Layers/LayerManager.cs +++ b/UVtools.Core/Layers/LayerManager.cs @@ -176,19 +176,28 @@ namespace UVtools.Core } } - public void Init(uint layerCount, bool initializeLayers = false) + public void Init(Layer[] layers) { - _layers = new Layer[layerCount]; - if (!initializeLayers) return; - for (uint layerIndex = 0; layerIndex < layerCount; layerIndex++) + var oldLayerCount = LayerCount; + _layers = layers; + if (LayerCount != oldLayerCount) { - this[layerIndex] = new Layer(layerIndex, this); + SlicerFile.LayerCount = LayerCount; } } - public void Init(Layer[] layers) + public void Init(uint layerCount, bool initializeLayers = false) { - _layers = layers; + var layers = new Layer[layerCount]; + if (initializeLayers) + { + for (uint layerIndex = 0; layerIndex < layerCount; layerIndex++) + { + layers[layerIndex] = new Layer(layerIndex, this); + } + } + + Init(layers); } public void Add(Layer layer) @@ -348,7 +357,7 @@ namespace UVtools.Core public LayerManager(uint layerCount, FileFormat slicerFile) : this(slicerFile) { SlicerFile = slicerFile; - _layers = new Layer[layerCount]; + Init(layerCount); } #endregion @@ -970,14 +979,24 @@ namespace UVtools.Core var oldLayerCount = LayerCount; int differenceLayerCount = (int)newLayerCount - Count; if (differenceLayerCount == 0) return; - Array.Resize(ref _layers, (int) newLayerCount); + var newLayers = new Layer[newLayerCount]; + + Array.Copy(_layers, 0, newLayers, 0, Math.Min(newLayerCount, newLayers.Length)); + if (differenceLayerCount > 0 && initBlack) { - Parallel.For(oldLayerCount, newLayerCount, CoreSettings.ParallelOptions, layerIndex => + using var blackMat = EmguExtensions.InitMat(SlicerFile.Resolution); + var pngBytes = blackMat.GetPngByes(); + for (var layerIndex = oldLayerCount; layerIndex < newLayerCount; layerIndex++) { - this[layerIndex] = new Layer((uint)layerIndex, EmguExtensions.InitMat(SlicerFile.Resolution), this); - }); + newLayers[layerIndex] = new Layer(layerIndex, pngBytes.ToArray(), this); + } } + + SlicerFile.SuppressRebuildPropertiesWork(() => + { + Layers = newLayers; + }); } /// <summary> @@ -986,34 +1005,41 @@ namespace UVtools.Core /// <returns></returns> public void ReallocateInsert(uint insertAtLayerIndex, uint layerCount, bool initBlack = false) { + if (layerCount == 0) return; + insertAtLayerIndex = Math.Min(insertAtLayerIndex, LayerCount); var newLayers = new Layer[LayerCount + layerCount]; - // Rearrange - for (uint layerIndex = 0; layerIndex < insertAtLayerIndex; layerIndex++) - { - newLayers[layerIndex] = _layers[layerIndex]; - } - - // Rearrange - for (uint layerIndex = insertAtLayerIndex; layerIndex < _layers.Length; layerIndex++) + // Copy from start to insert index + if(insertAtLayerIndex > 0) + Array.Copy(_layers, 0, newLayers, 0, insertAtLayerIndex); + + // Rearrange from last insert to end + if(insertAtLayerIndex < LayerCount) + Array.Copy( + _layers, insertAtLayerIndex, + newLayers, insertAtLayerIndex + layerCount, + LayerCount - insertAtLayerIndex); + /*for (uint layerIndex = insertAtLayerIndex; layerIndex < _layers.Length; layerIndex++) { newLayers[layerCount + layerIndex] = _layers[layerIndex]; newLayers[layerCount + layerIndex].Index = layerCount + layerIndex; - } + }*/ - // Allocate new layers + // Allocate new layers in between if (initBlack) { - Parallel.For(insertAtLayerIndex, insertAtLayerIndex + layerCount, CoreSettings.ParallelOptions, layerIndex => + using var blackMat = EmguExtensions.InitMat(SlicerFile.Resolution); + var pngBytes = blackMat.GetPngByes(); + for (var layerIndex = insertAtLayerIndex; layerIndex < insertAtLayerIndex + layerCount; layerIndex++) { - newLayers[layerIndex] = new Layer((uint) layerIndex, EmguExtensions.InitMat(SlicerFile.Resolution), this); - }); + newLayers[layerIndex] = new Layer(layerIndex, pngBytes.ToArray(), this); + } } - /*for (uint layerIndex = insertAtLayerIndex; layerIndex < insertAtLayerIndex + layerCount; layerIndex++) + + SlicerFile.SuppressRebuildPropertiesWork(() => { - Layers[layerIndex] = initBlack ? new Layer(layerIndex, EmguExtensions.InitMat(SlicerFile.Resolution), this) : null; - }*/ - _layers = newLayers; + Layers = newLayers; + }); } /// <summary> @@ -1021,18 +1047,22 @@ namespace UVtools.Core /// </summary> /// <param name="startLayerIndex"></param> /// <param name="endLayerIndex"></param> - public void ReallocateRange(uint startLayerIndex, uint endLayerIndex) + public void ReallocateKeepRange(uint startLayerIndex, uint endLayerIndex) { if ((int)(endLayerIndex - startLayerIndex) < 0) return; var newLayers = new Layer[1 + endLayerIndex - startLayerIndex]; - uint currentLayerIndex = 0; + Array.Copy(_layers, startLayerIndex, newLayers, 0, newLayers.Length); + /*uint currentLayerIndex = 0; for (uint layerIndex = startLayerIndex; layerIndex <= endLayerIndex; layerIndex++) { newLayers[currentLayerIndex++] = _layers[layerIndex]; - } + }*/ - _layers = newLayers; + SlicerFile.SuppressRebuildPropertiesWork(() => + { + Layers = newLayers; + }); } /// <summary> diff --git a/UVtools.Core/Managers/ClipboardManager.cs b/UVtools.Core/Managers/ClipboardManager.cs index 7c5de4b..0ab372f 100644 --- a/UVtools.Core/Managers/ClipboardManager.cs +++ b/UVtools.Core/Managers/ClipboardManager.cs @@ -10,6 +10,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Drawing; using System.Linq; using UVtools.Core.FileFormats; using UVtools.Core.Layers; @@ -29,9 +30,17 @@ namespace UVtools.Core.Managers /// </summary> public uint LayerCount { get; } + /// <summary> + /// Gets the LayerHeight for this clip + /// </summary> public float LayerHeight { get; } /// <summary> + /// Gets the Resolution for this clip + /// </summary> + public Size Resolution { get; } + + /// <summary> /// Gets the description of this operation /// </summary> public string Description { get; set; } @@ -39,7 +48,7 @@ namespace UVtools.Core.Managers public bool IsFullBackup { get; set; } - public Operations.Operation Operation + public Operation Operation { get => _operation; set @@ -52,19 +61,19 @@ namespace UVtools.Core.Managers #endregion #region Constructor - public ClipboardItem(FileFormat slicerFile, Operations.Operation operation, bool isFullBackup = false) : this(slicerFile) + public ClipboardItem(FileFormat slicerFile, Operation operation, bool isFullBackup = false) : this(slicerFile, string.Empty, isFullBackup) { Operation = operation; string description = operation.ToString(); if (!description.StartsWith(operation.Title)) description = $"{operation.Title}: {description}"; Description = description; - IsFullBackup = isFullBackup; } public ClipboardItem(FileFormat slicerFile, string description = null, bool isFullBackup = false) { LayerCount = slicerFile.LayerCount; LayerHeight = slicerFile.LayerHeight; + Resolution = slicerFile.Resolution; Description = description; IsFullBackup = isFullBackup; } @@ -142,6 +151,11 @@ namespace UVtools.Core.Managers SlicerFile.LayerHeight = clip.LayerHeight; } + if (SlicerFile.Resolution != clip.Resolution) + { + SlicerFile.Resolution = clip.Resolution; + } + SlicerFile.SuppressRebuildPropertiesWork(() => { SlicerFile.LayerManager.Layers = Layer.CloneLayers(layers); @@ -394,7 +408,7 @@ namespace UVtools.Core.Managers /// <summary> /// Collect differences and create a clip /// </summary> - public ClipboardItem Clip(Operations.Operation operation, Layer[] layersSnapshot = null, bool doFullBackup = false) + public ClipboardItem Clip(Operation operation, Layer[] layersSnapshot = null, bool doFullBackup = false) { string description = operation.ToString(); if (!description.StartsWith(operation.Title)) description = $"{operation.Title}: {description}"; diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs index 505978b..e2b88c9 100644 --- a/UVtools.Core/Operations/OperationChangeResolution.cs +++ b/UVtools.Core/Operations/OperationChangeResolution.cs @@ -11,6 +11,7 @@ using System.Drawing; using System.Text; using System.Threading.Tasks; using Emgu.CV; +using UVtools.Core.Extensions; using UVtools.Core.FileFormats; namespace UVtools.Core.Operations @@ -21,6 +22,8 @@ namespace UVtools.Core.Operations #region Members private uint _newResolutionX; private uint _newResolutionY; + private bool _fixRatio; + #endregion #region Subclasses @@ -81,9 +84,11 @@ namespace UVtools.Core.Operations sb.AppendLine($"The new resolution must be different from current resolution ({SlicerFile.ResolutionX} x {SlicerFile.ResolutionY})."); } - if (NewResolutionX < SlicerFile.BoundingRectangle.Width || NewResolutionY < SlicerFile.BoundingRectangle.Height) + var finalBoundsWidth = FinalBoundsWidth; + var finalBoundsHeight = FinalBoundsHeight; + if (NewResolutionX < finalBoundsWidth || NewResolutionY < finalBoundsHeight) { - sb.AppendLine($"The new resolution ({NewResolutionX} x {NewResolutionY}) is not large enough to hold the model volume ({SlicerFile.BoundingRectangle.Width} x {SlicerFile.BoundingRectangle.Height}), continuing operation would clip the model"); + sb.AppendLine($"The new resolution ({NewResolutionX} x {NewResolutionY}) is not large enough to hold the model volume ({finalBoundsWidth} x {finalBoundsHeight}), continuing operation would clip the model."); sb.AppendLine("To fix this, try to rotate the object and/or resize to fit on this new resolution."); } @@ -92,7 +97,7 @@ namespace UVtools.Core.Operations public override string ToString() { - var result = $"{_newResolutionX} x {_newResolutionY}"; + var result = $"{_newResolutionX} x {_newResolutionY} [Fix ratio: {_fixRatio}]"; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } @@ -103,16 +108,46 @@ namespace UVtools.Core.Operations public uint NewResolutionX { get => _newResolutionX; - set => RaiseAndSetIfChanged(ref _newResolutionX, value); + set + { + if(!RaiseAndSetIfChanged(ref _newResolutionX, value)) return; + RaisePropertyChanged(nameof(NewRatioX)); + RaisePropertyChanged(nameof(NewFixedRatioX)); + RaisePropertyChanged(nameof(FinalBoundsWidth)); + } } public uint NewResolutionY { get => _newResolutionY; - set => RaiseAndSetIfChanged(ref _newResolutionY, value); + set + { + RaiseAndSetIfChanged(ref _newResolutionY, value); + RaisePropertyChanged(nameof(NewRatioY)); + RaisePropertyChanged(nameof(NewFixedRatioY)); + RaisePropertyChanged(nameof(FinalBoundsHeight)); + } } - public Size VolumeBondsSize => SlicerFile.BoundingRectangle.Size; + public double NewRatioX => Math.Round((double)SlicerFile.ResolutionX / _newResolutionX, 2); + public double NewRatioY => Math.Round((double)SlicerFile.ResolutionY / _newResolutionY, 2); + + public double NewFixedRatioX => Math.Round((double)_newResolutionX / SlicerFile.ResolutionX, 2); + public double NewFixedRatioY => Math.Round((double)_newResolutionY / SlicerFile.ResolutionY, 2); + + public bool FixRatio + { + get => _fixRatio; + set + { + if(!RaiseAndSetIfChanged(ref _fixRatio, value)) return; + RaisePropertyChanged(nameof(FinalBoundsWidth)); + RaisePropertyChanged(nameof(FinalBoundsHeight)); + } + } + + public uint FinalBoundsWidth => (uint)(_fixRatio ? SlicerFile.BoundingRectangle.Width * NewFixedRatioX : SlicerFile.BoundingRectangle.Width); + public uint FinalBoundsHeight => (uint)(_fixRatio ? SlicerFile.BoundingRectangle.Height * NewFixedRatioY : SlicerFile.BoundingRectangle.Height); #endregion @@ -151,6 +186,9 @@ namespace UVtools.Core.Operations new Resolution(2560, 1620, "WQXGA"), new Resolution(3840, 2160, "4K UHD"), new Resolution(3840, 2400, "WQUXGA"), + new Resolution(4920, 2880, "5K UHD"), + new Resolution(5448, 3064, "6K"), + new Resolution(7680, 4320, "8K UHD"), }; } @@ -160,8 +198,12 @@ namespace UVtools.Core.Operations protected override bool ExecuteInternally(OperationProgress progress) { progress.ItemCount = SlicerFile.LayerCount; - var boundingRectangle = SlicerFile.BoundingRectangle; var newSize = new Size((int) NewResolutionX, (int) NewResolutionY); + var finalBoundsWidth = FinalBoundsWidth; + var finalBoundsHeight = FinalBoundsHeight; + + var finalBounds = new Size((int)finalBoundsWidth, (int)finalBoundsHeight); + Parallel.For(0, SlicerFile.LayerCount, CoreSettings.ParallelOptions, layerIndex => { if (progress.Token.IsCancellationRequested) return; @@ -170,14 +212,17 @@ namespace UVtools.Core.Operations if (mat.Size != newSize) { - using var matRoi = new Mat(mat, boundingRectangle); - using var matDst = new Mat(newSize, mat.Depth, mat.NumberOfChannels); - using var matDstRoi = new Mat(matDst, - new Rectangle((int) (NewResolutionX / 2 - boundingRectangle.Width / 2), - (int) NewResolutionY / 2 - boundingRectangle.Height / 2, - boundingRectangle.Width, boundingRectangle.Height)); - matRoi.CopyTo(matDstRoi); - //Execute(mat); + using var matDst = EmguExtensions.InitMat(newSize); + + var newFixedRatioX = NewFixedRatioX; + var newFixedRatioY = NewFixedRatioY; + if (_fixRatio && (newFixedRatioX != 1.0 || newFixedRatioY != 1.0)) + { + CvInvoke.Resize(mat, mat, SlicerFile.Resolution.Multiply(newFixedRatioX, newFixedRatioY)); + } + + mat.CopyCenterToCenter(finalBounds, matDst); + SlicerFile[layerIndex].LayerMat = matDst; } @@ -212,7 +257,7 @@ namespace UVtools.Core.Operations private bool Equals(OperationChangeResolution other) { - return _newResolutionX == other._newResolutionX && _newResolutionY == other._newResolutionY; + return _newResolutionX == other._newResolutionX && _newResolutionY == other._newResolutionY && _fixRatio == other._fixRatio; } public override bool Equals(object obj) @@ -222,10 +267,7 @@ namespace UVtools.Core.Operations public override int GetHashCode() { - unchecked - { - return ((int) _newResolutionX * 397) ^ (int) _newResolutionY; - } + return HashCode.Combine(_newResolutionX, _newResolutionY, _fixRatio); } #endregion diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index fe5e77d..4b1bf6d 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -35,8 +35,8 @@ namespace UVtools.Core.Operations Replace, [Description("Stack: Stacks and combine imported layers in the current layers")] Stack, - [Description("Merge: Merges current layers with the imported content by summing value of layer pixels")] - Merge, + [Description("MergeSum: Merges current layers with the imported content by summing value of layer pixels")] + MergeSum, [Description("MergeMax: Merges current layers with the imported content by using the maximum value of layer pixels")] MergeMax, [Description("Subtract: Subtracts current layers with the imported content")] @@ -158,7 +158,7 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _extendBeyondLayerCount, value); } - public bool IsExtendBeyondLayerCountVisible => _importType is ImportTypes.Replace or ImportTypes.Stack or ImportTypes.Merge or ImportTypes.MergeMax; + public bool IsExtendBeyondLayerCountVisible => _importType is ImportTypes.Replace or ImportTypes.Stack or ImportTypes.MergeSum or ImportTypes.MergeMax; public bool DiscardUnmodifiedLayers { @@ -354,7 +354,7 @@ namespace UVtools.Core.Operations } break; - case ImportTypes.Merge: + case ImportTypes.MergeSum: case ImportTypes.MergeMax: if (SlicerFile.Resolution != fileFormat.Resolution) continue; if (_extendBeyondLayerCount) @@ -393,7 +393,7 @@ namespace UVtools.Core.Operations } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.NewRoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); + using var layerRoi = layer.NewMatFromCenterRoi(SlicerFile.Resolution, fileFormatBoundingRectangle); SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; @@ -408,7 +408,7 @@ namespace UVtools.Core.Operations } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.NewRoiFromCenter(SlicerFile.Resolution, fileFormatBoundingRectangle); + using var layerRoi = layer.NewMatFromCenterRoi(SlicerFile.Resolution, fileFormatBoundingRectangle); SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; } @@ -424,7 +424,7 @@ namespace UVtools.Core.Operations break; } - case ImportTypes.Merge: + case ImportTypes.MergeSum: { if (layerIndex >= SlicerFile.LayerCount) return; using var originalMat = SlicerFile[layerIndex].LayerMat; @@ -512,7 +512,7 @@ namespace UVtools.Core.Operations if (lastProcessedLayerIndex + 1 < SlicerFile.LayerCount && _discardUnmodifiedLayers) { - SlicerFile.LayerManager.ReallocateRange(0, (uint)lastProcessedLayerIndex); + SlicerFile.LayerManager.Reallocate((uint)lastProcessedLayerIndex + 1); } return true; diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 3d59718..7550887 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.25.0</Version> + <Version>2.25.1</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> @@ -49,7 +49,7 @@ <ItemGroup> <PackageReference Include="AnimatedGif" Version="1.0.5" /> - <PackageReference Include="BinarySerializer" Version="8.6.1.1" /> + <PackageReference Include="BinarySerializer" Version="8.6.1.2" /> <PackageReference Include="Emgu.CV" Version="4.5.4.4788" /> <PackageReference Include="KdTree" Version="1.4.1" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" /> diff --git a/UVtools.WPF/Assets/Styles/Styles.xaml b/UVtools.WPF/Assets/Styles/Styles.xaml index 798c16f..96ccc9c 100644 --- a/UVtools.WPF/Assets/Styles/Styles.xaml +++ b/UVtools.WPF/Assets/Styles/Styles.xaml @@ -29,6 +29,12 @@ <Setter Property="AcceptsTab" Value="True" /> </Style> + <Style Selector="NumericUpDown.ReadOnly"> + <Setter Property="IsReadOnly" Value="True" /> + <Setter Property="AllowSpin" Value="False" /> + <Setter Property="ShowButtonSpinner" Value="False" /> + </Style> + <Style Selector="TextBox.NumericUpDownValueLabel"> <Setter Property="IsEnabled" Value="False" /> diff --git a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml index 54153f9..5cc041e 100644 --- a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml @@ -7,33 +7,73 @@ <StackPanel Orientation="Vertical" Spacing="10"> <TextBlock Text="{Binding Operation.SlicerFile.Resolution, StringFormat=Current resolution (X/Y): {0}}"/> <TextBlock Text="{Binding Operation.SlicerFile.PixelSizeMicrons, StringFormat=Current pixel pitch (X/Y): {0}µm}"/> - <TextBlock Text="{Binding Operation.VolumeBondsSize, StringFormat=Object volume (X/Y): {0}}"/> - - <StackPanel Orientation="Horizontal" Spacing="10"> - <TextBlock VerticalAlignment="Center" Text="New X/Y:"/> - <NumericUpDown - MinWidth="120" - Minimum="1" - Maximum="50000" - Width="150" - Value="{Binding Operation.NewResolutionX}"/> - - <TextBlock VerticalAlignment="Center" Text="x"/> - - <NumericUpDown - MinWidth="120" - Minimum="1" - Maximum="50000" - Width="150" - Value="{Binding Operation.NewResolutionY}" - /> - - <ComboBox - MinWidth="250" - SelectedItem="{Binding SelectedPresetItem}" - Items="{Binding Operation.Presets}" - PlaceholderText="Resolution presets"/> - </StackPanel> + <TextBlock Text="{Binding SlicerFile.BoundingRectangle.Size, StringFormat=Object volume (X/Y): {0}}"/> + <TextBlock FontWeight="Bold"> + <TextBlock.Text> + <MultiBinding StringFormat="Resulting pixel ratio: {{X={0}x, Y={1}x}}"> + <Binding Path="Operation.NewRatioX"/> + <Binding Path="Operation.NewRatioY"/> + </MultiBinding> + </TextBlock.Text> + </TextBlock> + + <Grid RowDefinitions="Auto,10,Auto" + ColumnDefinitions="Auto,10,Auto,5,Auto,5,Auto,10,Auto"> + + <TextBlock Grid.Row="0" Grid.Column="0" + VerticalAlignment="Center" Text="New X/Y:"/> + + <NumericUpDown Grid.Row="0" Grid.Column="2" + MinWidth="120" + Minimum="1" + Maximum="50000" + Width="150" + Value="{Binding Operation.NewResolutionX}"/> + + <TextBlock Grid.Row="0" Grid.Column="4" VerticalAlignment="Center" Text="x"/> + + <NumericUpDown Grid.Row="0" Grid.Column="6" + MinWidth="120" + Minimum="1" + Maximum="50000" + Width="150" + Value="{Binding Operation.NewResolutionY}"/> + + <ComboBox Grid.Row="0" Grid.Column="8" + MinWidth="250" + SelectedItem="{Binding SelectedPresetItem}" + Items="{Binding Operation.Presets}" + PlaceholderText="Resolution presets"/> + + <TextBlock Grid.Row="2" Grid.Column="0" + VerticalAlignment="Center" Text="Fix pixel ratio:"/> + + <NumericUpDown Grid.Row="2" Grid.Column="2" + Classes="ValueLabel ValueLabel_times ReadOnly" + FormatString="F2" + MinWidth="120" + Width="150" + IsEnabled="{Binding Operation.FixRatio}" + Value="{Binding Operation.NewFixedRatioX}"/> + + <TextBlock Grid.Row="2" Grid.Column="4" VerticalAlignment="Center" Text="x"/> + + <NumericUpDown Grid.Row="2" Grid.Column="6" + Classes="ValueLabel ValueLabel_times ReadOnly" + FormatString="F2" + MinWidth="120" + Width="150" + IsEnabled="{Binding Operation.FixRatio}" + Value="{Binding Operation.NewFixedRatioY}"/> + + <CheckBox Grid.Row="2" Grid.Column="8" + Content="Resize layers with proposed ratio" + IsChecked="{Binding Operation.FixRatio}" + ToolTip.Tip="Fix the pixel ratio by resize the layers images with the proposed ratio to match the new resolution. +
Only use this option when both source and target display have the same dimensions / build volume. +Otherwise, the new display size must be taken into account and you need to manually resize after this."/> + </Grid> + </StackPanel> </UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index f2bfefa..0c20c4b 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -159,7 +159,7 @@ namespace UVtools.WPF.Controls.Tools { RaisePropertyChanged(nameof(InfoLayerHeightStr)); RaisePropertyChanged(nameof(InfoImportResult)); - ParentWindow.ButtonOkEnabled = Operation.Files.Count > 0; + if(ParentWindow is not null) ParentWindow.ButtonOkEnabled = Operation.Files.Count > 0; } public async void AddFiles() diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index d5cfa96..fd40cfd 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -2160,7 +2160,7 @@ namespace UVtools.WPF var size = EmguExtensions.GetTextSizeExtended(text, DrawingPixelText.Font, DrawingPixelText.FontScale, DrawingPixelText.Thickness, ref baseLine, DrawingPixelText.LineAlignment); //var rotatedSize = size.Rotate(DrawingPixelText.Angle); //Point point = (rotatedSize.Inflate(rotatedSize)).Rotate(DrawingPixelText.Angle, rotatedSize.ToPoint()); - cursor = EmguExtensions.InitMat(size.Inflate(), 4); + cursor = EmguExtensions.InitMat(size.Add(), 4); //CvInvoke.Rectangle(cursor, new Rectangle(Point.Empty, size), _pixelEditorCursorColor, -1, DrawingPixelText.LineType); //_pixelEditorCursorColor.V3 = 255; //CvInvoke.Rectangle(cursor, new Rectangle(new Point(size.Width, 0), size), _pixelEditorCursorColor, 1, DrawingPixelText.LineType); diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index d2b2ac3..4034c2f 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -416,7 +416,7 @@ <TextBlock VerticalAlignment="Center"> <TextBlock.Text> - <MultiBinding StringFormat="\{0\}/\{1\}"> + <MultiBinding StringFormat="{}{0}/{1}"> <Binding Path="VisibleThumbnailIndex"/> <Binding Path="SlicerFile.CreatedThumbnailsCount"/> </MultiBinding> @@ -1521,7 +1521,7 @@ <TextBlock VerticalAlignment="Center"> <TextBlock.Text> - <MultiBinding StringFormat="\{0\}/\{1\}"> + <MultiBinding StringFormat="{}{0}/{1}"> <Binding Path="Clipboard.CurrentIndexCountStr"/> <Binding Path="Clipboard.Items.Count"/> </MultiBinding> diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index b5f54ea..6531bf2 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -1807,6 +1807,17 @@ namespace UVtools.WPF if (e.PropertyName == nameof(SlicerFile.Thumbnails)) { RefreshThumbnail(); + return; + } + if (e.PropertyName == nameof(SlicerFile.Resolution)) + { + RaisePropertyChanged(nameof(LayerResolutionStr)); + return; + } + if (e.PropertyName == nameof(SlicerFile.Ppmm)) + { + RaisePropertyChanged(nameof(LayerZoomStr)); + return; } } diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index f231c37..11e2b57 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>2.25.0</Version> + <Version>2.25.1</Version> <Platforms>AnyCPU;x64</Platforms> </PropertyGroup> diff --git a/UVtools.WPF/Windows/AboutWindow.axaml b/UVtools.WPF/Windows/AboutWindow.axaml index d9cc68b..342a733 100644 --- a/UVtools.WPF/Windows/AboutWindow.axaml +++ b/UVtools.WPF/Windows/AboutWindow.axaml @@ -90,8 +90,7 @@ </Border> <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,*" - Margin="20" - > + Margin="20"> <TextBlock Grid.Row="0" Text="{Binding Software}" FontWeight="Bold"/> <TextBlock Grid.Row="2" Text="{Binding Version}"/> |