From 518e7a0ec1eca0bdf041e39e87d3305aa4bd23e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Thu, 17 Sep 2020 23:21:38 +0100 Subject: [PATCH] WPF settings --- CHANGELOG.md | 3 + CreateRelease.ps1 | 2 +- UVtools.Core/FileFormats/UVJFile.cs | 2 + UVtools.Core/Objects/BindableBase.cs | 6 + UVtools.Core/UVtools.Core.csproj | 6 +- UVtools.GUI/Properties/AssemblyInfo.cs | 4 +- UVtools.WPF/App.axaml.cs | 22 +- UVtools.WPF/Assets/Icons/UVtools.ico | Bin 0 -> 124548 bytes .../{ => Assets}/Themes/UVtools.themes | 0 .../{ => Assets}/Themes/UVtoolsDark.xaml | 0 .../{ => Assets}/Themes/UVtoolsLight.xaml | 0 UVtools.WPF/Extensions/BitmapExtension.cs | 178 +++++++++----- UVtools.WPF/Extensions/MatrixHelper.cs | 131 ---------- .../Extensions/PrimitivesExtensions.cs | 10 +- UVtools.WPF/Extensions/WindowExtensions.cs | 11 +- UVtools.WPF/MainWindow.axaml | 14 +- UVtools.WPF/MainWindow.axaml.cs | 56 ++--- UVtools.WPF/UVtools.WPF.csproj | 28 ++- UVtools.WPF/UserSettings.cs | 231 ++++++++++++++++++ UVtools.WPF/ViewModels/MainWindowViewModel.cs | 23 +- UVtools.WPF/Windows/ProgressWindow.axaml | 25 ++ UVtools.WPF/Windows/ProgressWindow.axaml.cs | 26 ++ UVtools.WPF/Windows/SettingsWindow.axaml | 167 +++++++++++++ UVtools.WPF/Windows/SettingsWindow.axaml.cs | 19 ++ 24 files changed, 719 insertions(+), 245 deletions(-) create mode 100644 UVtools.WPF/Assets/Icons/UVtools.ico rename UVtools.WPF/{ => Assets}/Themes/UVtools.themes (100%) rename UVtools.WPF/{ => Assets}/Themes/UVtoolsDark.xaml (100%) rename UVtools.WPF/{ => Assets}/Themes/UVtoolsLight.xaml (100%) delete mode 100644 UVtools.WPF/Extensions/MatrixHelper.cs create mode 100644 UVtools.WPF/UserSettings.cs create mode 100644 UVtools.WPF/Windows/ProgressWindow.axaml create mode 100644 UVtools.WPF/Windows/ProgressWindow.axaml.cs create mode 100644 UVtools.WPF/Windows/SettingsWindow.axaml create mode 100644 UVtools.WPF/Windows/SettingsWindow.axaml.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c097926..6bdf59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## /09/2020 - v0.8.2.3 + + ## 16/09/2020 - v0.8.2.2 * (Add) Support for PHZ zip files when renamed to .zip diff --git a/CreateRelease.ps1 b/CreateRelease.ps1 index 03f5a5a..45dc54b 100644 --- a/CreateRelease.ps1 +++ b/CreateRelease.ps1 @@ -1,7 +1,7 @@ cd $PSScriptRoot $version = (Get-Command UVtools.GUI\bin\Release\UVtools.Core.dll).FileVersionInfo.FileVersion -Remove-Item "$PSScriptRoot\UVtools.GUI\bin\Release\Logs" -Recurse +Remove-Item "$PSScriptRoot\UVtools.GUI\bin\Release\Logs" -Recurse -ErrorAction Ignore Add-Type -A System.IO.Compression.FileSystem [IO.Compression.ZipFile]::CreateFromDirectory("$PSScriptRoot\UVtools.GUI\bin\Release", "$PSScriptRoot\UVtools.GUI\bin\UVtools_v$version.zip") diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs index 4b56d0c..79d232b 100644 --- a/UVtools.Core/FileFormats/UVJFile.cs +++ b/UVtools.Core/FileFormats/UVJFile.cs @@ -350,6 +350,8 @@ namespace UVtools.Core.FileFormats public override void Decode(string fileFullPath, OperationProgress progress = null) { base.Decode(fileFullPath, progress); + if(progress is null) progress = new OperationProgress(); + progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); FileFullPath = fileFullPath; using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) diff --git a/UVtools.Core/Objects/BindableBase.cs b/UVtools.Core/Objects/BindableBase.cs index 5f9447b..946b5ca 100644 --- a/UVtools.Core/Objects/BindableBase.cs +++ b/UVtools.Core/Objects/BindableBase.cs @@ -49,6 +49,12 @@ namespace UVtools.Core.Objects return true; } + protected bool SetProperty([CallerMemberName] string propertyName = null) + { + OnPropertyChanged(propertyName); + return true; + } + /// /// Notifies listeners that a property value has changed. /// diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index f39e99f..9d1d0f1 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,12 +10,12 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, repair, conversion and manipulation - 0.8.2.2 + 0.8.2.3 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 - 0.8.2.2 - 0.8.2.2 + 0.8.2.3 + 0.8.2.3 diff --git a/UVtools.GUI/Properties/AssemblyInfo.cs b/UVtools.GUI/Properties/AssemblyInfo.cs index 442aef9..e011fe6 100644 --- a/UVtools.GUI/Properties/AssemblyInfo.cs +++ b/UVtools.GUI/Properties/AssemblyInfo.cs @@ -35,5 +35,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.8.2.2")] -[assembly: AssemblyFileVersion("0.8.2.2")] +[assembly: AssemblyVersion("0.8.2.3")] +[assembly: AssemblyFileVersion("0.8.2.3")] diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index d4730d7..85895f8 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -1,11 +1,15 @@ -using System.Diagnostics; +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.ThemeManager; -using MessageBox.Avalonia.DTO; -using MessageBox.Avalonia.Enums; -using MessageBox.Avalonia.Models; using UVtools.Core.FileFormats; namespace UVtools.WPF @@ -25,16 +29,16 @@ namespace UVtools.WPF { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - Selector = ThemeSelector.Create("Themes"); - Selector.LoadSelectedTheme("UVtools.theme"); + UserSettings.Load(); + + Selector = ThemeSelector.Create("Assets/Themes"); + Selector.LoadSelectedTheme("Assets/selected.theme"); desktop.MainWindow = new MainWindow { //DataContext = Selector }; desktop.Exit += (sender, e) - => Selector.SaveSelectedTheme("UVtools.theme"); - - Debug.WriteLine(Selector.Themes[1].Name); + => Selector.SaveSelectedTheme("Assets/selected.theme"); } base.OnFrameworkInitializationCompleted(); diff --git a/UVtools.WPF/Assets/Icons/UVtools.ico b/UVtools.WPF/Assets/Icons/UVtools.ico new file mode 100644 index 0000000000000000000000000000000000000000..2a642ecd2f19dc0fb8bcac979331a8d0ccb21af7 GIT binary patch literal 124548 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

XfA} z4g1RUNbj+3j$bbioigoxom(@E_4L}-9k{h6rhRT@@L<`|L}_pU&cGc$IQ&?t62`?b z7c4m?m8%bb$bsrF)9mdJ|ai$qFW)KZ&nd}nQo?`C-tT6)KCOt0E z`m@6f>#k8BidP?;zD7TD+r9R5Qg%g)-UIH-MK5M|o|2LxcC3t(gQzZ1J;oYx*d3GZ zA+(0*G`EHxG+-Se*4mr(y_bpq6Su-VN}PcEIflLG=5J2UWfWG&(?JjMHRTe7QL9=ImVeq@fMzAP7`Q=Z58hA zWA65tY$e$mk_nHixMNR$sn)FkcZmh|{{8!Gap&m}t4kyYarlZ}lXC7oaQ~GZzlHY> z9y}-!{-A@?CE9SwYeyDsTo&|9rRSwcz+GbeF~6PXu_Q-MmxzX({3W3bwq20Q{s4E0 z3C8?vN$?gvlc7t3_CbiR@IUIy%MVg@3V{1-N#!M+hbF`^r~g^mB_Zzk-wd-m+%V9lB} zndW|G=#m)UyE)APN4!GH1l;>d`i^k^E>B16)~#7^a4TOHFn0|06?MFC-@e>j zWvod6otU#Gl-+NC>9^mCI*YyfWJtXKCb9pFIWoezz~F7CPMtU$r%s*9gzGLbk31+S zC_~@2ZQGV<&KcwY-}@k~6O?hCH19u4)qmN%d2p8{`h52J19_HrdgqI}uA)~8TbDTPqW&BVe1H?!r?gT+^VUuA0CBakN zGyVJbXAuz*Le_};pJV)1KtngmKq{l~?%g|XZHc6GpuS^m4Qv5oF*zjQ0-QuuM(jDB zBamqPf}YEiFdr8F587e02dEeDk1*#Ib`)#KP~WjGMnc<<214TT3!Wf)N2c*3Au+I1 zsOPbl1^5o!r5ZmH&;U6=ZW7lAdN!{T%9L1Vc$`I#lm}=)okra+sD!dm zrYJfxog*KDf8_YJkl=JW&`z%;kSg9z&u5pA7xF~j!XoqKKguA0&j%k6eymismYzq# zJ9rm)Ag_GEHM>kvCIKJAMqg+-!8wB01n^USr33!s8t&nlz62HocsIKua=gcA9?=ZS zg3$yea~vZu3yhB5m{vqbwI^_Ka&#_-nf11xATBf*j;5MK3TE_PbvPmc-lRnTn^c;HfM;g91KFhMr`#y!OHn<%S15C9$on+UQ@;&8yYJN7`-vtLB}qVDAO#Wm`%kB*19u}I7v zy7?m8=6e$rh_Yd;a|4vq0p>_LjX*s%vC_NBx=oZvy)4C*_ZTYe!%8?{PL1WDcNzL}Ec^Xt`L-m>@)7G6_N_B;pfLr| zXhQl19l$&iJ^RJn{0!&=?g7@E%2u>|J0`Y#^vkfM-<8c{WuE$10O#24nBN|lxT?^Z-29G ze_(qN6Z7%$;cNlM8nGV-Vo-?5BL2kF&*KFgzJARShfPE~*9Oxu%l4P9t zrf@MZj1M5j3i*rf3kN)Mz#*gk6|})({STB6JIL?bi`YNLlQ5p=;NXxU3!Zl25zl0i zGWj3rIEeMX`1gSW#!-axlZ0i7{>Rt_VuoVUAO0671p1+8#rmJZJYKwb!HqG9<2m$X z0PWd>J)IEa6O*#^KLt2s>woikV!jh}^W@2s?AWnm-1_(E=xF}7xMPm~w*UvZ^*;nS zKp&tdV*OHa1d6$acacXXf158Orz5=wosdiaQ;1tS9=xvzo6Y+zXg>jbJI32{JS)s1 z%m2(IcuDe$KG<@od)q@9bVgDu0VV;#n>aIs#|p$t4f^UK{VH{hot+lbQFu%lv2_yNKz) zJ3PG^IA5o}O_lU-h@szuZ@w5ipWx>5uA^@U3KHJod6jV);|}Zg-{$f_p9%9%F`p3g zm}^cXzbP{*JfAb(_x1JV+MN|UuChwQUUA>&!2D-?p8<4|Xz)9abbgeheSSs0f5z(u_x{tTPr3IozXSFf zdx>D5EzkmE`Cf`2u*qXV0G8xcIs~*TnfE$Opb9e5`BN zt_fb{bX{B>K%N=22wzD5k9&9~)4P0ar0cvs^LoY0m*?{z^*4fIq#+bH^_IdK;aR+c GcmD@otJ=u` literal 0 HcmV?d00001 diff --git a/UVtools.WPF/Themes/UVtools.themes b/UVtools.WPF/Assets/Themes/UVtools.themes similarity index 100% rename from UVtools.WPF/Themes/UVtools.themes rename to UVtools.WPF/Assets/Themes/UVtools.themes diff --git a/UVtools.WPF/Themes/UVtoolsDark.xaml b/UVtools.WPF/Assets/Themes/UVtoolsDark.xaml similarity index 100% rename from UVtools.WPF/Themes/UVtoolsDark.xaml rename to UVtools.WPF/Assets/Themes/UVtoolsDark.xaml diff --git a/UVtools.WPF/Themes/UVtoolsLight.xaml b/UVtools.WPF/Assets/Themes/UVtoolsLight.xaml similarity index 100% rename from UVtools.WPF/Themes/UVtoolsLight.xaml rename to UVtools.WPF/Assets/Themes/UVtoolsLight.xaml diff --git a/UVtools.WPF/Extensions/BitmapExtension.cs b/UVtools.WPF/Extensions/BitmapExtension.cs index deee547..d82b89c 100644 --- a/UVtools.WPF/Extensions/BitmapExtension.cs +++ b/UVtools.WPF/Extensions/BitmapExtension.cs @@ -1,4 +1,11 @@ -using System; +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ +using System; using System.Diagnostics; using System.Runtime.InteropServices; using Avalonia; @@ -16,6 +23,55 @@ namespace UVtools.WPF.Extensions /// public static class BitmapExtension { + public static int GetStep(this WriteableBitmap bitmap) + => (int)(bitmap.Size.Width * 4); + + ///

+ /// Gets the total length of this + /// + /// + /// The total length of this + public static int GetLength(this WriteableBitmap bitmap) + => (int) (bitmap.Size.Width * 4 * bitmap.Size.Height); + + public static int GetPixelPos(this WriteableBitmap bitmap, int x, int y) + => (int)(bitmap.Size.Width * 4 * y + x); + + /// + /// Gets a single pixel span to manipulate or read pixels + /// + /// Pixel type + /// Input + /// A containing all pixels in data memory + public static unsafe Span GetPixelSpan(this WriteableBitmap bitmap) + { + using var l = bitmap.Lock(); + return new Span(l.Address.ToPointer(), bitmap.GetLength()); + } + + + public static unsafe Span GetPixelSpan(this WriteableBitmap bitmap, int length, int offset = 0) + { + using var l = bitmap.Lock(); + return new Span(IntPtr.Add(l.Address, offset).ToPointer(), length); + } + + public static Span GetSinglePixelSpan(this WriteableBitmap bitmap, int x, int y, int length = 3) + { + using var l = bitmap.Lock(); + return bitmap.GetPixelSpan(length, bitmap.GetPixelPos(x, y)); + } + + public static Span GetSinglePixelPosSpan(this WriteableBitmap bitmap, int pos, int length = 3) + => bitmap.GetPixelSpan(length, pos); + + + public static unsafe Span GetPixelRowSpan(this WriteableBitmap bitmap, int y, int length = 0, int offset = 0) + { + using var l = bitmap.Lock(); + return new Span(IntPtr.Add(l.Address, (int) (bitmap.Size.Width * 4 * y + offset)).ToPointer(), (int) (length == 0 ? bitmap.Size.Width * 4 : length)); + } + public static SKBitmap ToSkBitmap(this Mat mat) { SKBitmap bitmap; @@ -52,65 +108,77 @@ namespace UVtools.WPF.Extensions return SKImage.FromBitmap(bitmap); } - public static Bitmap ToBitmap(this Mat mat) + public static WriteableBitmap ToBitmap(this Mat mat) { - if (mat.NumberOfChannels == 1) - { - var writeableBitmap = new WriteableBitmap(new PixelSize(mat.Width, mat.Height), new Vector(72, 72), - PixelFormat.Rgba8888, AlphaFormat.Unpremul); - var span = mat.GetPixelSpan(); - var bytes = new[] {0, 0, 0, 255}; - using (var lockBuffer = writeableBitmap.Lock()) - { - for (var i = 1; i < span.Length; i++) - { - bytes[0] = bytes[1] = bytes[2] = span[i]; - Marshal.Copy(bytes, 0, - new IntPtr(lockBuffer.Address.ToInt64() + i * 4), bytes.Length); - } - } + var dataCount = mat.Width * mat.Height; - return writeableBitmap; + var writableBitmap = new WriteableBitmap(new PixelSize(mat.Width, mat.Height), new Vector(96, 96), + PixelFormat.Bgra8888, AlphaFormat.Unpremul); + using var lockBuffer = writableBitmap.Lock(); + + unsafe + { + var targetPixels = (uint*) (void*) lockBuffer.Address; + switch (mat.NumberOfChannels) + { + //Stopwatch sw = Stopwatch.StartNew(); + // Method 1 (Span copy) + case 1: + var srcPixels1 = (byte*) (void*) mat.DataPointer; + for (var i = 0; i < dataCount; i++) + { + var color = srcPixels1[i]; + targetPixels[i] = (uint) (color | color << 8 | color << 16 | 0xff << 24); + } + + break; + case 3: + var srcPixels2 = (byte*) (void*) mat.DataPointer; + uint pixel = 0; + for (uint i = 0; i < dataCount; i++) + { + targetPixels[i] = (uint)(srcPixels2[pixel++] | srcPixels2[pixel++] << 8 | srcPixels2[pixel++] << 16 | 0xff << 24); + } + + break; + case 4: + var srcPixels4 = (uint*) (void*) mat.DataPointer; + for (uint i = 0; i < dataCount; i++) + { + targetPixels[i] = srcPixels4[i]; + } + + break; + } } - return null; - } - /*PixelFormat targetPixelFormat = PixelFormat.Bgra8888; - - switch (mat.NumberOfChannels) - { - case 3: - targetPixelFormat = PixelFormat.Rgb565; - break; - case 4: - targetPixelFormat = PixelFormat.Bgra8888; - break; - default: - throw new Exception("Unknown color type"); - } + return writableBitmap; + /*Debug.WriteLine($"Method 1 (Span copy): {sw.ElapsedMilliseconds}ms"); + + // Method 2 (OpenCV Convertion + Copy Marshal) + sw.Restart(); + CvInvoke.CvtColor(mat, target, ColorConversion.Bgr2Bgra); + var buffer = target.GetBytes(); + Marshal.Copy(buffer, 0, lockBuffer.Address, buffer.Length); + Debug.WriteLine($"Method 2 (OpenCV Convertion + Copy Marshal): {sw.ElapsedMilliseconds}ms"); - - var writeableBitmap = new WriteableBitmap(new PixelSize(mat.Width, mat.Height), new Vector(72, 72), targetPixelFormat, AlphaFormat.Unpremul); - using var lockBuffer = writeableBitmap.Lock(); - var buffer = mat.GetBytes(); - for (var y = 0; y < mat.Height; y++) - { - Marshal.Copy(buffer, y * lockBuffer.RowBytes, new IntPtr(lockBuffer.Address.ToInt64() + y * lockBuffer.RowBytes), lockBuffer.RowBytes); + + //sw.Restart(); + CvInvoke.CvtColor(mat, target, ColorConversion.Bgr2Bgra); + unsafe + { + var srcAddress = (uint*)(void*)target.DataPointer; + var targetAddress = (uint*)(void*)lockBuffer.Address; + *targetAddress = *srcAddress; + } + //Debug.WriteLine($"Method 3 (OpenCV Convertion + Set Address): {sw.ElapsedMilliseconds}ms"); + + return writableBitmap; + */ + /* for (var y = 0; y < mat.Height; y++) + { + Marshal.Copy(buffer, y * lockBuffer.RowBytes, new IntPtr(lockBuffer.Address.ToInt64() + y * lockBuffer.RowBytes), lockBuffer.RowBytes); + }*/ } - //Marshal.Copy(buffer, 0, lockBuffer.Address, buffer.Length); - - SKBitmap bitmap = new SKBitmap(new SKImageInfo(mat.Width, mat.Height, SKColorType.Gray8)); - bitmap.SetPixels(mat.DataPointer); - Debug.WriteLine(bitmap.Info.ColorType); - - bitmap = SKBitmap.Decode(buffer.AsSpan(), new SKImageInfo - { - Width = mat.Width, - Height = mat.Height, - ColorType = SKColorType.Gray8, - });*/ - - /*return writeableBitmap; - }*/ } } \ No newline at end of file diff --git a/UVtools.WPF/Extensions/MatrixHelper.cs b/UVtools.WPF/Extensions/MatrixHelper.cs deleted file mode 100644 index f8b1e78..0000000 --- a/UVtools.WPF/Extensions/MatrixHelper.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using Avalonia; - -namespace UVtools.WPF.Extensions -{ - /// - /// Avalonia Matrix helper methods. - /// - public static class MatrixHelper - { - /// - /// Creates a translation matrix using the specified offsets. - /// - /// X-coordinate offset. - /// Y-coordinate offset. - /// The created translation matrix. - public static Matrix Translate(double offsetX, double offsetY) - { - return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY); - } - - /// - /// Prepends a translation around the center of provided matrix. - /// - /// The matrix to prepend translation. - /// X-coordinate offset. - /// Y-coordinate offset. - /// The created translation matrix. - public static Matrix TranslatePrepend(Matrix matrix, double offsetX, double offsetY) - { - return Translate(offsetX, offsetY) * matrix; - } - - /// - /// Creates a matrix that scales along the x-axis and y-axis. - /// - /// Scaling factor that is applied along the x-axis. - /// Scaling factor that is applied along the y-axis. - /// The created scaling matrix. - public static Matrix Scale(double scaleX, double scaleY) - { - return new Matrix(scaleX, 0, 0, scaleY, 0.0, 0.0); - } - - /// - /// Creates a matrix that is scaling from a specified center. - /// - /// Scaling factor that is applied along the x-axis. - /// Scaling factor that is applied along the y-axis. - /// The center X-coordinate of the scaling. - /// The center Y-coordinate of the scaling. - /// The created scaling matrix. - public static Matrix ScaleAt(double scaleX, double scaleY, double centerX, double centerY) - { - return new Matrix(scaleX, 0, 0, scaleY, centerX - (scaleX * centerX), centerY - (scaleY * centerY)); - } - - /// - /// Prepends a scale around the center of provided matrix. - /// - /// The matrix to prepend scale. - /// Scaling factor that is applied along the x-axis. - /// Scaling factor that is applied along the y-axis. - /// The center X-coordinate of the scaling. - /// The center Y-coordinate of the scaling. - /// The created scaling matrix. - public static Matrix ScaleAtPrepend(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY) - { - return ScaleAt(scaleX, scaleY, centerX, centerY) * matrix; - } - - /// - /// Creates a skew matrix. - /// - /// Angle of skew along the X-axis in radians. - /// Angle of skew along the Y-axis in radians. - /// When the method completes, contains the created skew matrix. - public static Matrix Skew(float angleX, float angleY) - { - return new Matrix(1.0, Math.Tan(angleX), Math.Tan(angleY), 1.0, 0.0, 0.0); - } - - /// - /// Creates a matrix that rotates. - /// - /// Angle of rotation in radians. Angles are measured clockwise when looking along the rotation axis. - /// The created rotation matrix. - public static Matrix Rotation(double radians) - { - double cos = Math.Cos(radians); - double sin = Math.Sin(radians); - return new Matrix(cos, sin, -sin, cos, 0, 0); - } - - /// - /// Creates a matrix that rotates about a specified center. - /// - /// Angle of rotation in radians. - /// The center X-coordinate of the rotation. - /// The center Y-coordinate of the rotation. - /// The created rotation matrix. - public static Matrix Rotation(double angle, double centerX, double centerY) - { - return Translate(-centerX, -centerY) * Rotation(angle) * Translate(centerX, centerY); - } - - /// - /// Creates a matrix that rotates about a specified center. - /// - /// Angle of rotation in radians. - /// The center of the rotation. - /// The created rotation matrix. - public static Matrix Rotation(double angle, Vector center) - { - return Translate(-center.X, -center.Y) * Rotation(angle) * Translate(center.X, center.Y); - } - - /// - /// Transforms a point by this matrix. - /// - /// The matrix to use as a transformation matrix. - /// >The original point to apply the transformation. - /// The result of the transformation for the input point. - public static Point TransformPoint(Matrix matrix, Point point) - { - return new Point( - (point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.M31, - (point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.M32); - } - } -} diff --git a/UVtools.WPF/Extensions/PrimitivesExtensions.cs b/UVtools.WPF/Extensions/PrimitivesExtensions.cs index d4ff66c..7c99cf4 100644 --- a/UVtools.WPF/Extensions/PrimitivesExtensions.cs +++ b/UVtools.WPF/Extensions/PrimitivesExtensions.cs @@ -1,4 +1,12 @@ -using Avalonia; +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using Avalonia; namespace UVtools.WPF.Extensions { diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index 191ae84..84d5ad3 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -1,11 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Text; +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ using System.Threading.Tasks; using Avalonia.Controls; using MessageBox.Avalonia.DTO; using MessageBox.Avalonia.Enums; -using MessageBox.Avalonia.Models; namespace UVtools.WPF.Extensions { diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 6a55a48..a89a854 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -7,9 +7,11 @@ xmlns:idc="clr-namespace:Dock.Avalonia.Controls;assembly=Dock.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.MainWindow" - Title="UVtools"> + Title="UVtools" + Icon="/Assets/Icons/UVtools.ico" + > - + @@ -57,6 +59,14 @@ + + + + + + + + diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 01cc19c..2de8811 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -1,18 +1,23 @@ -using System; +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ +using System; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using MessageBox.Avalonia.DTO; -using MessageBox.Avalonia.Enums; -using MessageBox.Avalonia.Models; -using SkiaSharp; using UVtools.Core.FileFormats; using UVtools.WPF.Controls; using UVtools.WPF.Extensions; using UVtools.WPF.ViewModels; +using UVtools.WPF.Windows; namespace UVtools.WPF { @@ -71,9 +76,6 @@ namespace UVtools.WPF - - - public AdvancedImageBox LayerImage; public Button ZoomToFitButton; public Button CenterButton; @@ -133,6 +135,8 @@ namespace UVtools.WPF App.SlicerFile = FileFormat.FindByExtension(fileName, true, true); if (App.SlicerFile is null) return; + ViewModel.IsGUIEnabled = false; + var task = Task.Factory.StartNew(() => { try @@ -153,37 +157,23 @@ namespace UVtools.WPF } }); + //ProgressWindow progressWindow = new ProgressWindow(); + //progressWindow.ShowDialog(this); + + + task.Wait(); var mat = App.SlicerFile[0].LayerMat; var matRgb = App.SlicerFile[0].BrgMat; - var skbitmapGray = mat.ToSkBitmap(); - using (var image = SKImage.FromBitmap(skbitmapGray)) - using (var data = image.Encode(SKEncodedImageFormat.Png, 80)) - { - // save the data to a stream - using (var stream = File.OpenWrite("D:\\gray.png")) - { - data.SaveTo(stream); - } - } - + Debug.WriteLine("4K grayscale - BGRA convertion:"); + var bitmap = mat.ToBitmap(); + Debug.WriteLine("4K BGR - BGRA convertion:"); + var bitmapRgb = matRgb.ToBitmap(); + LayerImage.Image = bitmapRgb; - - var skbitmapRBG = matRgb.ToSkBitmap(); - using (var image = SKImage.FromBitmap(skbitmapRBG)) - using (var data = image.Encode(SKEncodedImageFormat.Png, 80)) - { - // save the data to a stream - using (var stream = File.OpenWrite("D:\\rgb.png")) - { - data.SaveTo(stream); - } - } - - //Avalonia.Skia.SkiaSharpExtensions. - //LayerImage.Image = bitmap; + ViewModel.IsGUIEnabled = true; } } } diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 31d7731..9ae0c0d 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -4,6 +4,20 @@ netcoreapp3.1 UVtools UVtools.ico + Tiago Conceição + PTRTECH + MSLA/DLP, file analysis, repair, conversion and manipulation + Copyright © 2020 PTRTECH + https://github.com/sn4k3/UVtools + LICENSE + https://github.com/sn4k3/UVtools + Git + + + true + + + true @@ -13,6 +27,7 @@ + @@ -20,6 +35,9 @@ + + + MSBuild:Compile @@ -27,14 +45,18 @@ MSBuild:Compile - + PreserveNewest - + PreserveNewest - + PreserveNewest + + True + + diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs new file mode 100644 index 0000000..e0e5f6d --- /dev/null +++ b/UVtools.WPF/UserSettings.cs @@ -0,0 +1,231 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Diagnostics; +using System.IO; +using System.Xml.Serialization; +using Avalonia.Media; + +namespace UVtools.WPF +{ + [Serializable] + public sealed class UserSettings + { + #region Sub classes + [Serializable] + public sealed class GeneralUserSettings + { + public bool StartMaximized { get; set; } = true; + public bool CheckForUpdatesOnStartup { get; set; } = true; + public byte DefaultOpenFileExtensionIndex { get; set; } + public string DefaultDirectoryOpenFile { get; set; } + public string DefaultDirectorySaveFile { get; set; } + public string DefaultDirectoryExtractFile { get; set; } + public string DefaultDirectoryConvertFile { get; set; } + public bool PromptOverwriteFileSave { get; set; } = true; + public string FileSaveNamePrefix { get; set; } + public string FileSaveNameSuffix { get; set; } = "_copy"; + } + + [Serializable] + public sealed class LayerPreviewUserSettings + { + public Color TooltipOverlayBackgroundColor { get; set; } = new Color(210, 255, 255, 192); + + public bool TooltipOverlay { get; set; } = true; + + public Color VolumeBoundsOutlineColor { get; set; } = new Color(210, 0, 255, 0); + public byte VolumeBoundsOutlineThickness { get; set; } = 3; + public bool VolumeBoundsOutline { get; set; } = true; + + public Color LayerBoundsOutlineColor { get; set; } = new Color(210, 0, 255, 0); + public byte LayerBoundsOutlineThickness { get; set; } = 3; + public bool LayerBoundsOutline { get; set; } = true; + + public Color HollowOutlineColor { get; set; } = new Color(210, 255, 165, 0); + public byte HollowOutlineLineThickness { get; set; } = 3; + public bool HollowOutline { get; set; } = true; + + public Color PreviousLayerDifferenceColor { get; set; } = new Color(255, 255, 0, 255); + public Color NextLayerDifferenceColor { get; set; } = new Color(255, 0, 255, 255); + public Color BothLayerDifferenceColor { get; set; } = new Color(255, 255, 0, 0); + public bool ShowLayerDifference { get; set; } = true; + + public Color IslandColor { get; set; } = new Color(255, 255,215, 0); + public Color IslandHighlightColor { get; set; } = new Color(255, 255,255, 0); + public Color ResinTrapColor { get; set; } = new Color(255, 244, 164, 96); + public Color ResinTrapHighlightColor { get; set; } = new Color(255, 255, 165, 0); + public Color TouchingBoundsColor { get; set; } = new Color(255, 255, 0, 0); + public Color CrosshairColor { get; set; } = new Color(255, 255, 0, 0); + + public bool ZoomToFitPrintVolumeBounds { get; set; } = true; + public byte ZoomLockLevelIndex { get; set; } = 7; + public bool ZoomIssues { get; set; } = true; + + public bool CrosshairShowOnlyOnSelectedIssues { get; set; } = false; + public byte CrosshairFadeLevelIndex { get; set; } = 5; + public uint CrosshairLength { get; set; } = 20; + public byte CrosshairMargin { get; set; } = 5; + + public bool AutoRotateLayerBestView { get; set; } = true; + public bool LayerZoomToFitOnLoad { get; set; } = true; + + } + + [Serializable] + public sealed class IssuesUserSettings + { + public bool ComputeIssuesOnLoad { get; set; } = false; + public bool ComputeIssuesOnClickTab { get; set; } = true; + public bool ComputeIslands { get; set; } = true; + public bool ComputeResinTraps { get; set; } = true; + public bool ComputeTouchingBounds { get; set; } = true; + public bool ComputeEmptyLayers { get; set; } = true; + + public bool IslandAllowDiagonalBonds { get; set; } = false; + public byte IslandBinaryThreshold { get; set; } = 0; + public byte IslandRequiredAreaToProcessCheck { get; set; } = 1; + public byte IslandRequiredPixelsToSupport { get; set; } = 10; + public byte IslandRequiredPixelBrightnessToSupport { get; set; } = 150; + + + public byte ResinTrapBinaryThreshold { get; set; } = 127; + public byte ResinTrapRequiredAreaToProcessCheck { get; set; } = 17; + public byte ResinTrapRequiredBlackPixelsToDrain { get; set; } = 10; + public byte ResinTrapMaximumPixelBrightnessToDrain { get; set; } = 30; + + } + + [Serializable] + public sealed class PixelEditorUserSettings + { + public Color AddPixelColor { get; set; } = new Color(255, 144, 238, 144); + public Color AddPixelHighlightColor { get; set; } = new Color(255, 0, 255, 0); + + public Color RemovePixelColor { get; set; } = new Color(255, 219, 112, 147); + public Color RemovePixelHighlightColor { get; set; } = new Color(255, 139, 0, 0); + + public Color SupportsColor { get; set; } = new Color(255, 0, 255, 255); + public Color SupportsHighlightColor { get; set; } = new Color(255, 0, 139, 139); + + public Color DrainHolesColor { get; set; } = new Color(255, 142, 69, 133); + public Color DrainHolesHighlightColor { get; set; } = new Color(255, 159, 0, 197); + + public bool PartialUpdateIslandsOnEditing { get; set; } = true; + public bool CloseEditorOnApply { get; set; } = false; + } + + [Serializable] + public sealed class LayerRepairUserSettings + { + + public byte ClosingIterations { get; set; } = 2; + public byte OpeningIterations { get; set; } = 0; + public byte RemoveIslandsBelowEqualPixels { get; set; } = 10; + + public bool RepairIslands { get; set; } = true; + public bool RepairResinTraps { get; set; } = true; + public bool RemoveEmptyLayers { get; set; } = true; + } + + #endregion + + #region Singleton + /// + /// Default filepath for store + /// + private const string FilePath = "Assets/usersettings.xml"; + + + private static UserSettings _instance; + /// + /// Instance of (singleton) + /// + public static UserSettings Instance => _instance ??= new UserSettings(); + #endregion + + #region Properties + + public GeneralUserSettings General { get; set; } = new GeneralUserSettings(); + public LayerPreviewUserSettings LayerPreview { get; set; } = new LayerPreviewUserSettings(); + public IssuesUserSettings Issues { get; set; } = new IssuesUserSettings(); + public PixelEditorUserSettings PixelEditor { get; set; } = new PixelEditorUserSettings(); + public LayerRepairUserSettings LayerRepair { get; set; } = new LayerRepairUserSettings(); + + /// + /// Gets or sets the number of times this file has been saved + /// + public uint SavesCount { get; set; } + + /// + /// Gets or sets the last time this file has been modified + /// + public DateTime ModifiedDateTime { get; set; } + #endregion + + #region Constructor + private UserSettings() + { } + #endregion + + #region Static Methods + /// + /// Reset settings to defaults + /// + /// True to save settings on file, otherwise false + public static void Reset(bool save = false) + { + _instance = new UserSettings(); + if(save) Save(); + } + + /// + /// Load settings from file + /// + public static void Load() + { + if (!File.Exists(FilePath)) + { + return; + } + + var serializer = new XmlSerializer(typeof(UserSettings)); + using StreamReader myXmlReader = new StreamReader(FilePath); + try + { + _instance = (UserSettings)serializer.Deserialize(myXmlReader); + } + catch (Exception e) + { + Debug.WriteLine(e.Message); + } + } + + + /// + /// Save settings to file + /// + public static void Save() + { + Instance.SavesCount++; + _instance.ModifiedDateTime = DateTime.Now; + var serializer = new XmlSerializer(_instance.GetType()); + using StreamWriter myXmlWriter = new StreamWriter(FilePath); + try + { + serializer.Serialize(myXmlWriter, _instance); + } + catch (Exception e) + { + Debug.WriteLine(e.Message); + } + } + #endregion + } +} diff --git a/UVtools.WPF/ViewModels/MainWindowViewModel.cs b/UVtools.WPF/ViewModels/MainWindowViewModel.cs index c69bf7f..2583d5f 100644 --- a/UVtools.WPF/ViewModels/MainWindowViewModel.cs +++ b/UVtools.WPF/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Reflection; using Avalonia.Controls; using JetBrains.Annotations; using MessageBox.Avalonia.DTO; @@ -6,13 +7,26 @@ using MessageBox.Avalonia.Enums; using MessageBox.Avalonia.Models; using UVtools.Core.Objects; using UVtools.WPF.Extensions; +using UVtools.WPF.Windows; namespace UVtools.WPF.ViewModels { public class MainWindowViewModel : BindableBase { private MainWindow Parent; - public bool IsFileLoaded => !ReferenceEquals(App.SlicerFile, null); + + private bool _isGUIEnabled = true; + public bool IsGUIEnabled + { + get => _isGUIEnabled; + set => SetProperty(ref _isGUIEnabled, value); + } + + public bool IsFileLoaded + { + get => !ReferenceEquals(App.SlicerFile, null); + set => SetProperty(); + } public MainWindowViewModel(MainWindow parent) { @@ -28,5 +42,12 @@ namespace UVtools.WPF.ViewModels var files = await dialog.ShowAsync(Parent); Parent.ProcessFiles(files); } + + public async void MenuFileSettingsClicked() + { + SettingsWindow settingsWindow = new SettingsWindow(); + settingsWindow.Title += $" [v{Assembly.GetEntryAssembly().GetName().Version}]"; + await settingsWindow.ShowDialog(Parent); + } } } diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml b/UVtools.WPF/Windows/ProgressWindow.axaml new file mode 100644 index 0000000..fbcc4a2 --- /dev/null +++ b/UVtools.WPF/Windows/ProgressWindow.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml.cs b/UVtools.WPF/Windows/ProgressWindow.axaml.cs new file mode 100644 index 0000000..b9744e6 --- /dev/null +++ b/UVtools.WPF/Windows/ProgressWindow.axaml.cs @@ -0,0 +1,26 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace UVtools.WPF.Windows +{ + public class ProgressWindow : Window + { + public ProgressWindow() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + } + } +} diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml new file mode 100644 index 0000000..6ff2278 --- /dev/null +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml.cs b/UVtools.WPF/Windows/SettingsWindow.axaml.cs new file mode 100644 index 0000000..dee093d --- /dev/null +++ b/UVtools.WPF/Windows/SettingsWindow.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace UVtools.WPF.Windows +{ + public class SettingsWindow : Window + { + public SettingsWindow() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +}