diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-01-05 04:10:46 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-01-05 04:10:46 +0300 |
commit | 702b73d5e205a20933d2c2f8d89e9496f8873492 (patch) | |
tree | aad3d486a8d2e10de8aa72f3a00a8ec5328d5cac | |
parent | 1a1d40b736159e241db8864fdcef9d332e07fdf6 (diff) |
v2.27.5v2.27.5
- **Pixel Arithmetic:**
- (Add) Corode: Noise pixel area, defaulting to 3px2
- (Change) Corode: Cryptonumeric random to normal random to speed up calculation
- (Change) Fuzzy skin preset: Set a ignore threshold area of 5000px2
- (Improvement) Masking performance and auto crop the layer to speed up the processing when using an "Apply to" other than "All"
- (Fix) Some "Apply to" methods was creating a wrong mask with some operators
- **CXDLP V3:**
- (Fix) Checksum (CRC32) (#389)
- (Fix) Software name and material name serialization
-rw-r--r-- | CHANGELOG.md | 12 | ||||
-rw-r--r-- | Scripts/010 Editor/cxdlp.bt (renamed from Scripts/010 Editor/cxdlp_v2.bt) | 47 | ||||
-rw-r--r-- | UVtools.Core/Converters/NullTerminatedConverter.cs | 24 | ||||
-rw-r--r-- | UVtools.Core/Converters/NullTerminatedLengthConverter.cs | 31 | ||||
-rw-r--r-- | UVtools.Core/Extensions/CryptExtensions.cs | 4 | ||||
-rw-r--r-- | UVtools.Core/Extensions/EmguExtensions.cs | 30 | ||||
-rw-r--r-- | UVtools.Core/FileFormats/CXDLPFile.cs | 145 | ||||
-rw-r--r-- | UVtools.Core/FileFormats/PhotonWorkshopFile.cs | 2 | ||||
-rw-r--r-- | UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs | 61 | ||||
-rw-r--r-- | UVtools.Core/Objects/ValueDescription.cs | 1 | ||||
-rw-r--r-- | UVtools.Core/Operations/Operation.cs | 33 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationPixelArithmetic.cs | 258 | ||||
-rw-r--r-- | UVtools.Core/UVtools.Core.csproj | 2 | ||||
-rw-r--r-- | UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml | 90 | ||||
-rw-r--r-- | UVtools.WPF/UVtools.WPF.csproj | 2 |
15 files changed, 470 insertions, 272 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index da5862e..0279b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 05/01/2022 - v2.27.5 + +- **Pixel Arithmetic:** + - (Add) Corode: Noise pixel area, defaulting to 3px2 + - (Change) Corode: Cryptonumeric random to normal random to speed up calculation + - (Change) Fuzzy skin preset: Set a ignore threshold area of 5000px2 + - (Improvement) Masking performance and auto crop the layer to speed up the processing when using an "Apply to" other than "All" + - (Fix) Some "Apply to" methods was creating a wrong mask with some operators +- **CXDLP V3:** + - (Fix) Checksum (CRC32) (#389) + - (Fix) Software name and material name serialization + ## 26/12/2021 - v2.27.4 - **UI:** diff --git a/Scripts/010 Editor/cxdlp_v2.bt b/Scripts/010 Editor/cxdlp.bt index 02f6754..2512246 100644 --- a/Scripts/010 Editor/cxdlp_v2.bt +++ b/Scripts/010 Editor/cxdlp.bt @@ -5,6 +5,7 @@ // Authors: Julien Delnatte //------------------------------------------------ // CHANGELOG: +// Add version 3 // Add uint16 unknown after header (Set to 2) // Add uint32 modelSize and ubyte model[modelSize] after unknown // Add ubyte offset[64] after resolutionY (Zeros) @@ -47,7 +48,10 @@ struct HEADER { uint16 ResolutionY <fgcolor=cBlack, bgcolor=cWhite>; ubyte Offset[64]; +} header; + +struct PREVIEWS { RgbPreviewImageRawData preview(116*116*2); ubyte rn0[2] <fgcolor=cBlack, bgcolor=cRed>; @@ -56,7 +60,9 @@ struct HEADER { RgbPreviewImageRawData preview2(290*290*2); ubyte rn2[2] <fgcolor=cBlack, bgcolor=cRed>; +} previews; +struct SLICER_INFO { uint32 PlateformXLength <fgcolor=cBlack, bgcolor=cWhite>; wchar_t plateformX[PlateformXLength/2]; @@ -78,7 +84,7 @@ struct HEADER { uint16 BottomLightPWM <fgcolor=cBlack, bgcolor=cWhite>; uint16 LightPWM <fgcolor=cBlack, bgcolor=cWhite>; -} header; +} slicerInfo; struct LAYER_DEF { local int i; @@ -88,25 +94,23 @@ struct LAYER_DEF { ubyte rn3[2] <fgcolor=cBlack, bgcolor=cRed>; } layerDefs; -local uint versionIdentifier = ReadUInt(); -//Printf( "%d\n", layerDefs.ID[0].layerArea ); -if(header.Version >= 2 && versionIdentifier != layerDefs.ID[0].layerArea){ - struct SLICER_INFO { +if(header.Version >= 3){ + struct SLICER_INFO_V3 { uint32 SoftwareNameSize; char SoftwareName[SoftwareNameSize]; uint32 MaterialNameSize; char MaterialName[MaterialNameSize]; //ubyte offset[67-SoftwareNameSize-MaterialNameSize]; //ubyte offset[20]; - uint Padding; - uint Padding; - uint Padding; - uint Padding; - ubyte Padding; + uint Unknown; + uint Unknown; + uint Unknown; + uint Unknown; + ubyte Unknown; ubyte LightPWM; - ushort Padding; + ushort Unknown; ubyte rn0[2] <fgcolor=cBlack, bgcolor=cRed>; - } slicerInfo; + } slicerInfoV3; } @@ -120,6 +124,21 @@ struct LAYERS { struct FOOTER { uint32 FooterSize <fgcolor=cBlack, bgcolor=cWhite>; char Marker[FooterSize] <fgcolor=cBlack, bgcolor=cRed>; +} footer; + + +//ubyte CheckSum <fgcolor=cBlack, bgcolor=cWhite>; +//ubyte CheckSum <fgcolor=cBlack, bgcolor=cWhite>; +//ubyte CheckSum <fgcolor=cBlack, bgcolor=cWhite>; +//ubyte CheckSum <fgcolor=cBlack, bgcolor=cWhite>; +uint CheckSum <fgcolor=cBlack, bgcolor=cWhite>; + +/* +local ubyte calculatedChecksum = 0; +local ulong i = 0; +for(i = 0; i < FileSize()-4; i++ ){ + calculatedChecksum ^= ReadByte(i); +} - uint CheckSum <fgcolor=cBlack, bgcolor=cWhite>; -} footer;
\ No newline at end of file +ubyte CalculatedChecksum = calculatedChecksum; +*/
\ No newline at end of file diff --git a/UVtools.Core/Converters/NullTerminatedConverter.cs b/UVtools.Core/Converters/NullTerminatedConverter.cs new file mode 100644 index 0000000..077e87f --- /dev/null +++ b/UVtools.Core/Converters/NullTerminatedConverter.cs @@ -0,0 +1,24 @@ +/* + * 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 BinarySerialization; + +namespace UVtools.Core.Converters +{ + public class NullTerminatedConverter : IValueConverter + { + public object Convert(object value, object converterParameter, BinarySerializationContext context) + { + return value.ToString()?.TrimEnd(char.MinValue); + } + + public object ConvertBack(object value, object converterParameter, BinarySerializationContext context) + { + return value is null ? null : $"{value}{char.MinValue}"; + } + } +} diff --git a/UVtools.Core/Converters/NullTerminatedLengthConverter.cs b/UVtools.Core/Converters/NullTerminatedLengthConverter.cs new file mode 100644 index 0000000..d097e00 --- /dev/null +++ b/UVtools.Core/Converters/NullTerminatedLengthConverter.cs @@ -0,0 +1,31 @@ +/* + * 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 BinarySerialization; + +namespace UVtools.Core.Converters +{ + public class NullTerminatedLengthConverter : IValueConverter + { + // Read + public object Convert(object value, object converterParameter, BinarySerializationContext context) + { + //var uintValue = System.Convert.ToUInt32(value); + //if (uintValue == 0) return 0; + //return uintValue - 1; + return value; + } + + // Write + public object ConvertBack(object value, object converterParameter, BinarySerializationContext context) + { + var uintValue = System.Convert.ToUInt32(value); + if (uintValue == 0) return 0; + return uintValue + 1; + } + } +} diff --git a/UVtools.Core/Extensions/CryptExtensions.cs b/UVtools.Core/Extensions/CryptExtensions.cs index 4d9c027..268d8e1 100644 --- a/UVtools.Core/Extensions/CryptExtensions.cs +++ b/UVtools.Core/Extensions/CryptExtensions.cs @@ -14,13 +14,13 @@ namespace UVtools.Core.Extensions { public static class CryptExtensions { - public static SHA1CryptoServiceProvider SHA1 { get; } = new(); + public static readonly SHA1CryptoServiceProvider SHA1 = new(); public static string ComputeSHA1Hash(byte[] input) { return Convert.ToBase64String(SHA1.ComputeHash(input)); } - public static SHA256 SHA256 { get; } = SHA256.Create(); + public static readonly SHA256 SHA256 = SHA256.Create(); public static byte[] ComputeSHA256Hash(byte[] input) { return SHA256.ComputeHash(input); diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index dc1ec2e..a56da53 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -600,6 +600,25 @@ namespace UVtools.Core.Extensions } /// <summary> + /// Gets a Roi at x=0 and y=0 given a size, but return source when roi is empty or have same size as source + /// </summary> + /// <param name="mat"></param> + /// <param name="size"></param> + /// <returns></returns> + public static Mat Roi(this Mat mat, Size size) + { + return size.IsEmpty || size == mat.Size ? mat : new Mat(mat, new(Point.Empty, size)); + } + + /// <summary> + /// Gets a Roi from a mat size, but return source when roi is empty or have same size as source + /// </summary> + /// <param name="mat"></param> + /// <param name="fromMat"></param> + /// <returns></returns> + public static Mat Roi(this Mat mat, Mat fromMat) => mat.Roi(fromMat.Size); + + /// <summary> /// Gets a Roi from center, but return source when have same size as source /// </summary> /// <param name="mat"></param> @@ -1304,5 +1323,16 @@ namespace UVtools.Core.Extensions return CvInvoke.GetStructuringElement(elementShape, new Size(size, size), new Point(-1, -1)); } #endregion + + #region Disposes + /// <summary> + /// Dispose this <see cref="Mat"/> if it's a sub matrix / roi + /// </summary> + /// <param name="mat">Mat to dispose</param> + public static void DisposeIfSubMatrix(this Mat mat) + { + if(mat.IsSubmatrix) mat.Dispose(); + } + #endregion } } diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs index d766379..469a556 100644 --- a/UVtools.Core/FileFormats/CXDLPFile.cs +++ b/UVtools.Core/FileFormats/CXDLPFile.cs @@ -20,6 +20,7 @@ using Emgu.CV; using Emgu.CV.Structure; using UVtools.Core.Extensions; using UVtools.Core.Layers; +using UVtools.Core.Objects; using UVtools.Core.Operations; namespace UVtools.Core.FileFormats @@ -238,22 +239,27 @@ namespace UVtools.Core.FileFormats public sealed class SlicerInfoV3 { - [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint SoftwareNameSize { get; set; } = (uint)About.SoftwareWithVersion.Length; + //[FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint SoftwareNameSize { get; set; } = (uint)About.SoftwareWithVersion.Length + 1; - [FieldOrder(1)] [FieldLength(nameof(SoftwareNameSize))] public string SoftwareName { get; set; } = About.SoftwareWithVersion; + [FieldOrder(0)] public NullTerminatedUintStringBigEndian SoftwareName { get; set; } = new(About.SoftwareWithVersion); - [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public uint MaterialNameSize { get; set; } + //[FieldOrder(2)] [FieldEndianness(Endianness.Big)] public uint MaterialNameSize { get; set; } - [FieldOrder(3)] [FieldLength(nameof(MaterialNameSize))] public string MaterialName { get; set; } + [FieldOrder(1)] public NullTerminatedUintStringBigEndian MaterialName { get; set; } = new(); - [FieldOrder(4)] public uint Padding1 { get; set; } - [FieldOrder(5)] public uint Padding2 { get; set; } - [FieldOrder(6)] public uint Padding3 { get; set; } - [FieldOrder(7)] public uint Padding4 { get; set; } - [FieldOrder(8)] public byte Padding5 { get; set; } - [FieldOrder(9)] public byte LightPWM { get; set; } = byte.MaxValue; - [FieldOrder(10)] public ushort MyControl { get; set; } - [FieldOrder(11)] public PageBreak PageBreak { get; set; } = new(); + [FieldOrder(2)] public uint Unknown1 { get; set; } + [FieldOrder(3)] public uint Unknown2 { get; set; } + [FieldOrder(4)] public uint Unknown3 { get; set; } + [FieldOrder(5)] public uint Unknown4 { get; set; } + [FieldOrder(6)] public byte Unknown5 { get; set; } = 1; + [FieldOrder(7)] public byte LightPWM { get; set; } = byte.MaxValue; + [FieldOrder(8)] public ushort Unknown6 { get; set; } = 2; + [FieldOrder(9)] public PageBreak PageBreak { get; set; } = new(); + + public override string ToString() + { + return $"{nameof(SoftwareName)}: {SoftwareName}, {nameof(MaterialName)}: {MaterialName}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}, {nameof(Unknown5)}: {Unknown5}, {nameof(LightPWM)}: {LightPWM}, {nameof(Unknown6)}: {Unknown6}, {nameof(PageBreak)}: {PageBreak}"; + } } #endregion @@ -426,16 +432,7 @@ namespace UVtools.Core.FileFormats get => HeaderSettings.Version; set { - if (base.Version == 3) - { - base.Version = 2; - SlicerInfoV3Settings.MyControl = 1; - } - else - { - base.Version = value; - } - + base.Version = value; HeaderSettings.Version = (ushort)base.Version; } } @@ -609,8 +606,8 @@ namespace UVtools.Core.FileFormats public override string MaterialName { - get => SlicerInfoV3Settings.MaterialName; - set => base.MaterialName = SlicerInfoV3Settings.MaterialName = value; + get => SlicerInfoV3Settings.MaterialName.Value; + set => base.MaterialName = SlicerInfoV3Settings.MaterialName.Value = value; } public override object[] Configs => new object[] { HeaderSettings, SlicerInfoSettings, SlicerInfoV3Settings, FooterSettings }; @@ -702,7 +699,7 @@ namespace UVtools.Core.FileFormats //Helpers.SerializeWriteFileStream(outputFile, pageBreak); outputFile.WriteBytes(pageBreak); - if (HeaderSettings.Version >= 2 && SlicerInfoV3Settings.MyControl > 0) + if (HeaderSettings.Version >= 3) { Helpers.SerializeWriteFileStream(outputFile, SlicerInfoV3Settings); } @@ -845,6 +842,7 @@ namespace UVtools.Core.FileFormats Helpers.SerializeWriteFileStream(outputFile, FooterSettings); + progress.Reset("Calculating checksum"); uint checkSum = CalculateCheckSum(outputFile, false); outputFile.Write(BitExtensions.ToBytesBigEndian(checkSum)); @@ -866,19 +864,26 @@ namespace UVtools.Core.FileFormats var position = inputFile.Position; + progress.Reset("Validating checksum"); var expectedCheckSum = CalculateCheckSum(inputFile, false, -4); - inputFile.Seek(3, SeekOrigin.Current); - byte checkSum = (byte) inputFile.ReadByte(); - - inputFile.Seek(position, SeekOrigin.Begin); - + uint checkSum; + if (HeaderSettings.Version <= 2) + { + inputFile.Seek(3, SeekOrigin.Current); + checkSum = (uint) inputFile.ReadByte(); + } + else + { + checkSum = inputFile.ReadUIntBigEndian(); + } if (expectedCheckSum != checkSum) { throw new FileLoadException($"Checksum fails, expecting: {expectedCheckSum} but got: {checkSum}.\n" + $"Try to reslice the file.", FileFullPath); } - + + inputFile.Seek(position, SeekOrigin.Begin); var previews = new byte[ThumbnailsOriginalSize.Length][]; for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) { @@ -898,15 +903,12 @@ namespace UVtools.Core.FileFormats Debug.WriteLine(SlicerInfoSettings); LayerManager.Init(HeaderSettings.LayerCount, DecodeType == FileDecodeType.Partial); - uint firstPreLayerArea = inputFile.ReadUIntBigEndian(); - inputFile.Seek(LayerCount * 4 + 2 - 4, SeekOrigin.Current); // Skip pre layers - uint afterPreLayersUint = inputFile.ReadUIntBigEndian(); - inputFile.Seek(-4, SeekOrigin.Current); + inputFile.Seek(LayerCount * 4 + 2, SeekOrigin.Current); // Skip pre layers - if (HeaderSettings.Version >= 2 && firstPreLayerArea != afterPreLayersUint) // New informative header v3 + if (HeaderSettings.Version >= 3) // New informative header v3 { SlicerInfoV3Settings = Helpers.Deserialize<SlicerInfoV3>(inputFile); - SlicerInfoV3Settings.MyControl = 1; // To know v3 is present + Debug.WriteLine(SlicerInfoV3Settings); } @@ -993,34 +995,85 @@ namespace UVtools.Core.FileFormats outputFile.Seek(offset, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + if (HeaderSettings.Version >= 3) + { + outputFile.Seek(LayerCount * 4 + 2, SeekOrigin.Current); // Skip pre layers + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoV3Settings); + } + uint checkSum = CalculateCheckSum(outputFile, false, -4); outputFile.WriteBytes(BitExtensions.ToBytesBigEndian(checkSum)); } - private byte CalculateCheckSum(FileStream fs, bool restorePosition = true, int offsetSize = 0) + private uint CalculateCheckSum(FileStream fs, bool restorePosition = true, int offsetSize = 0) { - byte checkSum = 0; + uint checkSum = 0; var position = fs.Position; var dataSize = fs.Length + offsetSize; const int bufferSize = 50 * 1024 * 1024; fs.Seek(0, SeekOrigin.Begin); - for ( - int chunkSize = (int)Math.Min(bufferSize, dataSize - fs.Position); - chunkSize > 0; - chunkSize = (int)Math.Min(chunkSize, dataSize - fs.Position)) + if (HeaderSettings.Version >= 3) { - var bytes = fs.ReadBytes(chunkSize); - for (int i = 0; i < bytes.Length; i++) + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.Table.cs + var table = new uint[256]; + + for (uint i = 0; i < 256; i++) { - checkSum ^= bytes[i]; + uint val = i; + + for (int j = 0; j < 8; j++) + { + if ((val & 0b0000_0001) == 0) + { + val >>= 1; + } + else + { + val = (val >> 1) ^ 0xEDB88320u; + } + } + + table[i] = val; + } + + for ( + int chunkSize = (int)Math.Min(bufferSize, dataSize - fs.Position); + chunkSize > 0; + chunkSize = (int)Math.Min(chunkSize, dataSize - fs.Position)) + { + var bytes = fs.ReadBytes(chunkSize); + for (int i = 0; i < bytes.Length; i++) + { + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs + byte idx = (byte)checkSum; + idx ^= bytes[i]; + checkSum = table[idx] ^ (checkSum >> 8); + } } } + else + { + for ( + int chunkSize = (int)Math.Min(bufferSize, dataSize - fs.Position); + chunkSize > 0; + chunkSize = (int)Math.Min(chunkSize, dataSize - fs.Position)) + { + var bytes = fs.ReadBytes(chunkSize); + for (int i = 0; i < bytes.Length; i++) + { + checkSum ^= bytes[i]; + } + } + } + if (restorePosition) fs.Seek(position, SeekOrigin.Begin); return checkSum; + + } #endregion diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs index 322907f..afcb3f3 100644 --- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs +++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs @@ -43,7 +43,7 @@ namespace UVtools.Core.FileFormats 0 }; - // CRC-16-ANSI (aka CRC-16-IMB) Polynomial: x^16 + x^15 + x^2 + 1 + // CRC-16-ANSI (aka CRC-16-IBM) Polynomial: x^16 + x^15 + x^2 + 1 public static readonly int[] CRC16Table = { 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, diff --git a/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs b/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs new file mode 100644 index 0000000..ff71690 --- /dev/null +++ b/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs @@ -0,0 +1,61 @@ +/* + * 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 BinarySerialization; + +namespace UVtools.Core.Objects +{ + /// <summary> + /// A string that always end with 0x00 if not null + /// It contains the string length as uint + /// </summary> + public sealed class NullTerminatedUintStringBigEndian + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] + public uint SerializedLength { get; set; } + + [FieldOrder(1)] [FieldLength(nameof(SerializedLength))] + public string SerializedValue { get; set; } + + [Ignore] + public string Value + { + get => SerializedValue?.TrimEnd(char.MinValue); + set => SerializedValue = value is null ? null : $"{value}{char.MinValue}"; + } + + public NullTerminatedUintStringBigEndian() { } + + public NullTerminatedUintStringBigEndian(string value) + { + Value = value; + } + + public override string ToString() => Value; + + private bool Equals(NullTerminatedUintStringBigEndian other) + { + return SerializedValue == other.SerializedValue; + } + + public bool Equals(string value) + { + return Value == value; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is NullTerminatedUintStringBigEndian other && Equals(other); + } + + public override int GetHashCode() + { + return (SerializedValue != null ? SerializedValue.GetHashCode() : 0); + } + } +} diff --git a/UVtools.Core/Objects/ValueDescription.cs b/UVtools.Core/Objects/ValueDescription.cs index 3f7372c..84bfe3d 100644 --- a/UVtools.Core/Objects/ValueDescription.cs +++ b/UVtools.Core/Objects/ValueDescription.cs @@ -7,7 +7,6 @@ */ using System; -using System.Collections.Generic; namespace UVtools.Core.Objects { diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index 8bc2db6..cee3c13 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -41,6 +41,7 @@ namespace UVtools.Core.Operations #region Members private FileFormat _slicerFile; + private Rectangle _originalBoundingRectangle; private OperationImportFrom _importedFrom = OperationImportFrom.None; private Rectangle _roi = Rectangle.Empty; private Point[][] _maskPoints; @@ -71,11 +72,19 @@ namespace UVtools.Core.Operations set { if(!RaiseAndSetIfChanged(ref _slicerFile, value)) return; + OriginalBoundingRectangle = _slicerFile.BoundingRectangle; InitWithSlicerFile(); } } [XmlIgnore] + public Rectangle OriginalBoundingRectangle + { + get => _originalBoundingRectangle; + private set => RaiseAndSetIfChanged(ref _originalBoundingRectangle, value); + } + + [XmlIgnore] public object Tag { get; set; } /// <summary> @@ -287,6 +296,7 @@ namespace UVtools.Core.Operations protected Operation(FileFormat slicerFile) : this() { _slicerFile = slicerFile; + OriginalBoundingRectangle = _slicerFile.BoundingRectangle; SelectAllLayers(); InitWithSlicerFile(); } @@ -431,16 +441,29 @@ namespace UVtools.Core.Operations } public Size GetRoiSizeOrDefault() => GetRoiSizeOrDefault(SlicerFile.Resolution); - public Size GetRoiSizeOrDefault(Mat defaultMat) => GetRoiSizeOrDefault(defaultMat.Size); - - public Size GetRoiSizeOrDefault(Size defaultSize) + public Size GetRoiSizeOrDefault(Mat defaultMat) => defaultMat is null ? GetRoiSizeOrDefault() : GetRoiSizeOrDefault(defaultMat.Size); + public Size GetRoiSizeOrDefault(Rectangle fallbackRectangle) => GetRoiSizeOrDefault(fallbackRectangle.Size); + public Size GetRoiSizeOrDefault(Size fallbackSize) { - return HaveROI && defaultSize != _roi.Size ? _roi.Size : defaultSize; + return HaveROI ? _roi.Size : fallbackSize; } + public Mat GetRoiOrDefault(Mat defaultMat) { - return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat; + return HaveROI && defaultMat.Size != _roi.Size ? defaultMat.Roi(_roi) : defaultMat; + } + + public Mat GetRoiOrDefault(Mat defaultMat, Rectangle fallbackRoi) + { + if (HaveROI && defaultMat.Size != _roi.Size) return defaultMat.Roi(_roi); + if (fallbackRoi.IsEmpty) return defaultMat; + return defaultMat.Size != fallbackRoi.Size ? defaultMat.Roi(fallbackRoi) : defaultMat; + } + + public Mat GetRoiOrVolumeBounds(Mat defaultMat) + { + return GetRoiOrDefault(defaultMat, _originalBoundingRectangle); } public void ClearMasks() diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs index 536fe36..a351d01 100644 --- a/UVtools.Core/Operations/OperationPixelArithmetic.cs +++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs @@ -10,7 +10,6 @@ using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; -using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; @@ -76,6 +75,7 @@ namespace UVtools.Core.Operations private short _noiseMinOffset = -128; private short _noiseMaxOffset = 128; private byte _noiseThreshold; + private ushort _noisePixelArea = 3; #endregion @@ -363,6 +363,12 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _noiseThreshold, value); } + public ushort NoisePixelArea + { + get => _noisePixelArea; + set => RaiseAndSetIfChanged(ref _noisePixelArea, value); + } + public byte Value { get => _value; @@ -505,6 +511,16 @@ namespace UVtools.Core.Operations #region Methods + private Size GetMatSizeCropped(Mat mat = null) + { + return _applyMethod == PixelArithmeticApplyMethod.All ? GetRoiSizeOrDefault(mat) : GetRoiSizeOrDefault(OriginalBoundingRectangle); + } + + private Mat GetMatRoiCropped(Mat mat) + { + return _applyMethod == PixelArithmeticApplyMethod.All ? GetRoiOrDefault(mat) : GetRoiOrVolumeBounds(mat); + } + protected override bool ExecuteInternally(OperationProgress progress) { Mat patternMat = null; @@ -536,16 +552,15 @@ namespace UVtools.Core.Operations _patternAlternate ??= _pattern; - using var blankMat = new Mat(SlicerFile.Resolution, DepthType.Cv8U, 1); - patternMat = blankMat.NewBlank(); - patternAlternateMat = blankMat.NewBlank(); - var target = GetRoiOrDefault(blankMat); + var target = new Mat(GetMatSizeCropped(), DepthType.Cv8U, 1); + patternMat = target.NewBlank(); + patternAlternateMat = target.NewBlank(); - CvInvoke.Repeat(_pattern, target.Rows / _pattern.Rows + 1, target.Cols / _pattern.Cols + 1, patternMat); - CvInvoke.Repeat(_patternAlternate, target.Rows / _patternAlternate.Rows + 1, target.Cols / _patternAlternate.Cols + 1, patternAlternateMat); + CvInvoke.Repeat(_pattern, (int)Math.Ceiling((double)target.Rows / _pattern.Rows), (int)Math.Ceiling((double)target.Cols / _pattern.Cols), patternMat); + CvInvoke.Repeat(_patternAlternate, (int)Math.Ceiling((double)target.Rows / _patternAlternate.Rows), (int)Math.Ceiling((double)target.Cols / _patternAlternate.Cols), patternAlternateMat); - patternMatMask = new Mat(patternMat, new Rectangle(0, 0, target.Width, target.Height)); - patternAlternateMatMask = new Mat(patternAlternateMat, new Rectangle(0, 0, target.Width, target.Height)); + patternMatMask = patternMat.Roi(target); + patternAlternateMatMask = patternAlternateMat.Roi(target); /*if (_patternInvert) { @@ -553,24 +568,21 @@ namespace UVtools.Core.Operations CvInvoke.BitwiseNot(patternAlternateMatMask, patternAlternateMatMask); }*/ } - else if (_operator is not PixelArithmeticOperators.BitwiseNot - and not PixelArithmeticOperators.KeepRegion - and not PixelArithmeticOperators.DiscardRegion) + else if (IsUsePatternVisible) { - patternMatMask = EmguExtensions.InitMat(HaveROI ? ROI.Size : SlicerFile.Resolution, new MCvScalar(_value)); + patternMatMask = EmguExtensions.InitMat(GetMatSizeCropped(), new MCvScalar(_value)); } Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.ParallelOptions, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using (var mat = SlicerFile[layerIndex].LayerMat) + var layer = SlicerFile[layerIndex]; + using (var mat = layer.LayerMat) { - //Execute(mat, tempMat); - using var original = mat.Clone(); - var originalRoi = GetRoiOrDefault(original); - var target = GetRoiOrDefault(mat); + var originalRoi = GetMatRoiCropped(original); + var target = GetMatRoiCropped(mat); Mat tempMat; if (_usePattern && IsUsePatternVisible) @@ -599,13 +611,13 @@ namespace UVtools.Core.Operations applyMask = null; break; case PixelArithmeticApplyMethod.Model: - applyMask = target; + applyMask = target.Clone(); break; case PixelArithmeticApplyMethod.ModelSurface: case PixelArithmeticApplyMethod.ModelSurfaceAndInset: if (layerIndex == SlicerFile.LastLayerIndex) { - applyMask = target; + applyMask = target.Clone(); } else { @@ -613,7 +625,7 @@ namespace UVtools.Core.Operations // Difference using var nextMat = SlicerFile[layerIndex + 1].LayerMat; - var nextMatRoi = GetRoiOrDefault(nextMat); + var nextMatRoi = GetMatRoiCropped(nextMat); CvInvoke.Subtract(target, nextMatRoi, applyMask); // 1px walls @@ -638,10 +650,16 @@ namespace UVtools.Core.Operations break; case PixelArithmeticApplyMethod.ModelInner: { - applyMask = wallThickness <= 0 ? target : new Mat(); + if (wallThickness <= 0) + { + applyMask = target.Clone(); + break; + } + + applyMask = new Mat(); int iterations = wallThickness; - var kernel = Kernel.GetKernel(ref iterations); - CvInvoke.Erode(target, applyMask, kernel, anchor, iterations, BorderType.Reflect101, default); + var kernel = Kernel.GetKernel(ref iterations); + CvInvoke.Erode(target, applyMask, kernel, anchor, iterations, BorderType.Reflect101, default); break; } case PixelArithmeticApplyMethod.ModelWalls: @@ -739,13 +757,49 @@ namespace UVtools.Core.Operations break; case PixelArithmeticOperators.Threshold: var tempThreshold = _thresholdType; - if (_thresholdType is ThresholdType.Otsu or ThresholdType.Triangle) tempThreshold |= ThresholdType.Binary; + if (_thresholdType is ThresholdType.Otsu or ThresholdType.Triangle) tempThreshold = ThresholdType.Binary | tempThreshold; CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, tempThreshold); if (_applyMethod != PixelArithmeticApplyMethod.All) ApplyMask(originalRoi, target, applyMask); break; case PixelArithmeticOperators.Corrode: var span = mat.GetDataByteSpan(); - if (HaveROI) + var random = new Random(); + + var bounds = HaveROI ? ROI : layer.BoundingRectangle; + + for (var y = bounds.Y; y < bounds.Bottom; y += _noisePixelArea) + for (var x = bounds.X; x < bounds.Right; x += _noisePixelArea) + { + byte zoneBrightness = 0; + for (var y1 = y; y1 < y + _noisePixelArea && y1 < bounds.Bottom && zoneBrightness < byte.MaxValue; y1++) + { + var pixelPos = mat.GetPixelPos(x, y1); + for (var x1 = x; x1 < x + _noisePixelArea && x1 < bounds.Right && zoneBrightness < byte.MaxValue; x1++) + { + zoneBrightness = Math.Max(zoneBrightness, span[pixelPos++]); + } + } + + if (zoneBrightness <= _noiseThreshold) continue; + byte brightness = (byte)Math.Clamp(random.Next(_noiseMinOffset, _noiseMaxOffset + 1) + zoneBrightness, byte.MinValue, byte.MaxValue); + //byte brightness = (byte)Math.Clamp(RandomNumberGenerator.GetInt32(_noiseMinOffset, _noiseMaxOffset + 1) + zoneBrightness, byte.MinValue, byte.MaxValue); + for (var y1 = y; y1 < y + _noisePixelArea && y1 < bounds.Bottom; y1++) + { + var pixelPos = mat.GetPixelPos(x, y1); + for (var x1 = x; x1 < x + _noisePixelArea && x1 < bounds.Right; x1++) + { + + if (span[pixelPos] <= _noiseThreshold) continue; + span[pixelPos++] = brightness; + } + } + } + + if (_applyMethod is not PixelArithmeticApplyMethod.All and not PixelArithmeticApplyMethod.Model) ApplyMask(originalRoi, target, applyMask); + + + // old method + /*if (HaveROI) { for (var y = ROI.Y; y < ROI.Bottom; y++) for (var x = ROI.X; x < ROI.Right; x++) @@ -766,11 +820,12 @@ namespace UVtools.Core.Operations for (var i = 0; i < span.Length; i++) { - if (span[i] <= _noiseThreshold || spanMask[i] == 0) continue; - span[i] = (byte)Math.Clamp(RandomNumberGenerator.GetInt32(_noiseMinOffset, _noiseMaxOffset + 1) + span[i], byte.MinValue, byte.MaxValue); + //if (span[i] <= _noiseThreshold || spanMask[i] == 0) continue; + //span[i] = (byte)Math.Clamp(RandomNumberGenerator.GetInt32(_noiseMinOffset, _noiseMaxOffset + 1) + span[i], byte.MinValue, byte.MaxValue); + span[i] = (byte)Math.Clamp(random.Next(_noiseMinOffset, _noiseMaxOffset + 1) + span[i], byte.MinValue, byte.MaxValue); } - } - + }*/ + break; case PixelArithmeticOperators.KeepRegion: { @@ -818,132 +873,6 @@ namespace UVtools.Core.Operations public bool IsAlternatePattern(uint layerIndex) => !IsNormalPattern(layerIndex); - /*public override bool Execute(Mat mat, params object[] arguments) - { - using var original = mat.Clone(); - var target = GetRoiOrDefault(mat); - - Mat tempMat; - bool needDispose = false; - if (arguments is not null && arguments.Length > 0) - { - tempMat = arguments[0] as Mat; - } - else - { - tempMat = GetTempMat(); - needDispose = true; - } - - Mat applyMask; - var anchor = new Point(-1, -1); - var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - int wallThickness = LayerManager.MutateGetIterationChamfer( - layerIndex, - LayerIndexStart, - LayerIndexEnd, - (int)_wallThicknessStart, - (int)_wallThicknessEnd, - _wallChamfer - ); - - switch (_applyMethod) - { - case PixelArithmeticApplyMethod.All: - applyMask = null; - break; - case PixelArithmeticApplyMethod.Model: - applyMask = target.Clone(); - break; - case PixelArithmeticApplyMethod.ModelInner: - CvInvoke.Erode(target, applyMask, kernel, anchor, wallThickness, BorderType.Reflect101, default); - applyMask = target; - break; - case PixelArithmeticApplyMethod.ModelWalls: - applyMask = target; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - switch (_operator) - { - case PixelArithmeticOperators.Set: - tempMat.CopyTo(target, applyMask); - break; - case PixelArithmeticOperators.Add: - CvInvoke.Add(target, tempMat, target, applyMask); - break; - case PixelArithmeticOperators.Subtract: - CvInvoke.Subtract(target, tempMat, target, applyMask); - break; - case PixelArithmeticOperators.Multiply: - CvInvoke.Multiply(target, tempMat, target); - break; - case PixelArithmeticOperators.Divide: - CvInvoke.Divide(target, tempMat, target); - break; - //case PixelArithmeticOperators.Exponential: - // CvInvoke.Pow(target, _value, tempMat); - // if(!_affectBackPixels) ApplyMask(original, mat, original); - // break; - case PixelArithmeticOperators.Minimum: - CvInvoke.Min(target, tempMat, target); - if (_applyMethod != PixelArithmeticApplyMethod.All) ApplyMask(original, target, original); - break; - case PixelArithmeticOperators.Maximum: - CvInvoke.Max(target, tempMat, target); - if (_applyMethod != PixelArithmeticApplyMethod.All) ApplyMask(original, target, original); - break; - case PixelArithmeticOperators.BitwiseNot: - CvInvoke.BitwiseNot(target, target, applyMask); - break; - case PixelArithmeticOperators.BitwiseAnd: - CvInvoke.BitwiseAnd(target, tempMat, target, applyMask); - break; - case PixelArithmeticOperators.BitwiseOr: - CvInvoke.BitwiseOr(target, tempMat, target, applyMask); - break; - case PixelArithmeticOperators.BitwiseXor: - CvInvoke.BitwiseXor(target, tempMat, target, applyMask); - break; - case PixelArithmeticOperators.Threshold: - CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, _thresholdType); - break; - case PixelArithmeticOperators.AbsDiff: - CvInvoke.AbsDiff(target, tempMat, target); - if (_applyMethod != PixelArithmeticApplyMethod.All) ApplyMask(original, target, original); - break; - case PixelArithmeticOperators.KeepRegion: - { - using var targetClone = target.Clone(); - original.SetTo(EmguExtensions.BlackColor); - mat.SetTo(EmguExtensions.BlackColor); - targetClone.CopyTo(target); - break; - } - case PixelArithmeticOperators.DiscardRegion: - target.SetTo(EmguExtensions.BlackColor); - break; - default: - throw new NotImplementedException(); - } - - ApplyMask(original, target); - - if (needDispose) - { - tempMat?.Dispose(); - } - - return true; - }*/ - - public Mat GetTempMat() => _operator - is not PixelArithmeticOperators.BitwiseNot - and not PixelArithmeticOperators.KeepRegion - and not PixelArithmeticOperators.DiscardRegion ? EmguExtensions.InitMat(HaveROI ? ROI.Size : SlicerFile.Resolution, new MCvScalar(_value)) : null; - public void PresetElephantFootCompensation() { SelectBottomLayers(); @@ -977,6 +906,8 @@ namespace UVtools.Core.Operations NoiseMinOffset = -200; NoiseMaxOffset = 127; WallThickness = 6; + IgnoreAreaOperator = PixelArithmeticIgnoreAreaOperator.SmallerThan; + IgnoreAreaThreshold = 5000; } public void PresetStripAntiAliasing() @@ -1326,7 +1257,7 @@ namespace UVtools.Core.Operations protected bool Equals(OperationPixelArithmetic other) { - return _operator == other._operator && _applyMethod == other._applyMethod && _wallThicknessStart == other._wallThicknessStart && _wallThicknessEnd == other._wallThicknessEnd && _wallChamfer == other._wallChamfer && _ignoreAreaOperator == other._ignoreAreaOperator && _ignoreAreaThreshold == other._ignoreAreaThreshold && _value == other._value && _usePattern == other._usePattern && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _patternAlternatePerLayersNumber == other._patternAlternatePerLayersNumber && _patternInvert == other._patternInvert && _patternText == other._patternText && _patternTextAlternate == other._patternTextAlternate && Equals(_pattern, other._pattern) && Equals(_patternAlternate, other._patternAlternate) && _patternGenMinBrightness == other._patternGenMinBrightness && _patternGenBrightness == other._patternGenBrightness && _patternGenInfillThickness == other._patternGenInfillThickness && _patternGenInfillSpacing == other._patternGenInfillSpacing && _noiseMinOffset == other._noiseMinOffset && _noiseMaxOffset == other._noiseMaxOffset && _noiseThreshold == other._noiseThreshold; + return _operator == other._operator && _applyMethod == other._applyMethod && _wallThicknessStart == other._wallThicknessStart && _wallThicknessEnd == other._wallThicknessEnd && _wallChamfer == other._wallChamfer && _ignoreAreaOperator == other._ignoreAreaOperator && _ignoreAreaThreshold == other._ignoreAreaThreshold && _value == other._value && _usePattern == other._usePattern && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _patternAlternatePerLayersNumber == other._patternAlternatePerLayersNumber && _patternInvert == other._patternInvert && _patternText == other._patternText && _patternTextAlternate == other._patternTextAlternate && _patternGenMinBrightness == other._patternGenMinBrightness && _patternGenBrightness == other._patternGenBrightness && _patternGenInfillThickness == other._patternGenInfillThickness && _patternGenInfillSpacing == other._patternGenInfillSpacing && _noiseMinOffset == other._noiseMinOffset && _noiseMaxOffset == other._noiseMaxOffset && _noiseThreshold == other._noiseThreshold && _noisePixelArea == other._noisePixelArea; } public override bool Equals(object obj) @@ -1334,29 +1265,27 @@ namespace UVtools.Core.Operations if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((OperationPixelArithmetic)obj); + return Equals((OperationPixelArithmetic) obj); } public override int GetHashCode() { var hashCode = new HashCode(); - hashCode.Add((int)_operator); - hashCode.Add((int)_applyMethod); + hashCode.Add((int) _operator); + hashCode.Add((int) _applyMethod); hashCode.Add(_wallThicknessStart); hashCode.Add(_wallThicknessEnd); hashCode.Add(_wallChamfer); - hashCode.Add((int)_ignoreAreaOperator); + hashCode.Add((int) _ignoreAreaOperator); hashCode.Add(_ignoreAreaThreshold); hashCode.Add(_value); hashCode.Add(_usePattern); - hashCode.Add((int)_thresholdType); + hashCode.Add((int) _thresholdType); hashCode.Add(_thresholdMaxValue); hashCode.Add(_patternAlternatePerLayersNumber); hashCode.Add(_patternInvert); hashCode.Add(_patternText); hashCode.Add(_patternTextAlternate); - hashCode.Add(_pattern); - hashCode.Add(_patternAlternate); hashCode.Add(_patternGenMinBrightness); hashCode.Add(_patternGenBrightness); hashCode.Add(_patternGenInfillThickness); @@ -1364,6 +1293,7 @@ namespace UVtools.Core.Operations hashCode.Add(_noiseMinOffset); hashCode.Add(_noiseMaxOffset); hashCode.Add(_noiseThreshold); + hashCode.Add(_noisePixelArea); return hashCode.ToHashCode(); } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 5b360be..8ce455b 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.27.4</Version> + <Version>2.27.5</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml index 27a4bb6..193a74b 100644 --- a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml @@ -102,47 +102,63 @@ <StackPanel Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="9" IsVisible="{Binding Operation.IsCorrodeVisible}" - Spacing="10" Orientation="Horizontal"> + Spacing="10" Orientation="Vertical"> - <TextBlock - VerticalAlignment="Center" - Text="Noise range:"/> - - <TextBlock - VerticalAlignment="Center" - Text="Min:"/> + <StackPanel Spacing="10" Orientation="Horizontal"> - <NumericUpDown - Minimum="-1000" - Maximum="1000" - Width="80" - Value="{Binding Operation.NoiseMinOffset}" - ToolTip.Tip="Minimum value of random noise offset"/> - - <TextBlock + <TextBlock + VerticalAlignment="Center" + Text="Noise range:"/> + + <TextBlock VerticalAlignment="Center" - Text="Max:"/> - - <NumericUpDown - Minimum="-1000" - Maximum="1000" - Width="80" - Value="{Binding Operation.NoiseMaxOffset}" - ToolTip.Tip="Maximum value of random noise offset"/> - - <TextBlock - VerticalAlignment="Center" - Text="Threshold:"/> - - <NumericUpDown - Classes="ValueLabel ValueLabel_sun" - Minimum="0" - Maximum="255" - Width="80" - Value="{Binding Operation.NoiseThreshold}" - ToolTip.Tip="Only the pixels with brightness above this threshold are processed"/> + Text="Min:"/> - </StackPanel> + <NumericUpDown + Minimum="-1000" + Maximum="1000" + Width="80" + Value="{Binding Operation.NoiseMinOffset}" + ToolTip.Tip="Minimum value of random noise offset"/> + + <TextBlock + VerticalAlignment="Center" + Text="Max:"/> + + <NumericUpDown + Minimum="-1000" + Maximum="1000" + Width="80" + Value="{Binding Operation.NoiseMaxOffset}" + ToolTip.Tip="Maximum value of random noise offset"/> + + <TextBlock + VerticalAlignment="Center" + Text="Threshold:"/> + + <NumericUpDown + Classes="ValueLabel ValueLabel_sun" + Minimum="0" + Maximum="255" + Width="80" + Value="{Binding Operation.NoiseThreshold}" + ToolTip.Tip="Only the pixels with brightness above this threshold are processed"/> + </StackPanel> + + <StackPanel Spacing="10" Orientation="Horizontal"> + <TextBlock + VerticalAlignment="Center" + Text="Noise pixel area: "/> + + <NumericUpDown + Classes="ValueLabel ValueLabel_px2" + Minimum="1" + Maximum="65535" + Width="80" + Value="{Binding Operation.NoisePixelArea}"/> + </StackPanel> + + </StackPanel> <TextBlock Grid.Row="8" Grid.Column="0" VerticalAlignment="Center" diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 717d6a9..e59a5d2 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.27.4</Version> + <Version>2.27.5</Version> <Platforms>AnyCPU;x64</Platforms> <PackageIcon>UVtools.png</PackageIcon> <PackageReadmeFile>README.md</PackageReadmeFile> |