diff --git a/UVtools.GUI/App.config b/UVtools.GUI/App.config index ba44a8f..919979b 100644 --- a/UVtools.GUI/App.config +++ b/UVtools.GUI/App.config @@ -216,8 +216,7 @@ True - + 10 diff --git a/UVtools.GUI/UVtools.GUI.csproj b/UVtools.GUI/UVtools.GUI.csproj index ce2e5d3..b1269db 100644 --- a/UVtools.GUI/UVtools.GUI.csproj +++ b/UVtools.GUI/UVtools.GUI.csproj @@ -133,7 +133,7 @@ True - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-preview.8.20407.11\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-rc.1.20451.14\lib\net45\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll diff --git a/UVtools.GUI/packages.config b/UVtools.GUI/packages.config index da264e0..fa04d2d 100644 --- a/UVtools.GUI/packages.config +++ b/UVtools.GUI/packages.config @@ -12,7 +12,7 @@ - + diff --git a/UVtools.WPF/App.axaml b/UVtools.WPF/App.axaml index 922d3b3..9b64e5d 100644 --- a/UVtools.WPF/App.axaml +++ b/UVtools.WPF/App.axaml @@ -4,5 +4,6 @@ + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index f553192..7e288f9 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -17,7 +17,7 @@ namespace UVtools.WPF public class App : Application { public static IThemeSelector? ThemeSelector { get; set; } - + public static MainWindow MainWindow; public static FileFormat SlicerFile = null; public override void Initialize() @@ -30,10 +30,11 @@ namespace UVtools.WPF if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { UserSettings.Load(); + UserSettings.SetVersion(); ThemeSelector = Avalonia.ThemeManager.ThemeSelector.Create("Assets/Themes"); ThemeSelector.LoadSelectedTheme("Assets/selected.theme"); - desktop.MainWindow = new MainWindow + desktop.MainWindow = MainWindow = new MainWindow { //DataContext = Selector }; diff --git a/UVtools.WPF/AppSettings.cs b/UVtools.WPF/AppSettings.cs new file mode 100644 index 0000000..9b35e1f --- /dev/null +++ b/UVtools.WPF/AppSettings.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace UVtools.WPF +{ + public static class AppSettings + { + public static Version Version => Assembly.GetEntryAssembly().GetName().Version; + // Supported ZoomLevels for Layer Preview. + // These settings eliminate very small zoom factors from the ImageBox default values, + // while ensuring that 4K/5K build plates can still easily fit on screen. + public static readonly int[] ZoomLevels = + {20, 25, 30, 50, 75, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1200, 1600}; + + // Count of the bottom portion of the full zoom range which will be skipped for + // assignable actions such as auto-zoom level, and crosshair fade level. If values + // are added/removed from ZoomLevels above, this value may also need to be adjusted. + public const byte ZoomLevelSkipCount = 7; // Start at 2x which is index 7. + + /// + /// Returns the zoom level at which the crosshairs will fade and no longer be displayed + /// + public static int CrosshairFadeLevel => ZoomLevels[UserSettings.Instance.LayerPreview.CrosshairFadeLevelIndex + ZoomLevelSkipCount]; + + /// + /// Returns the zoom level that will be used for autozoom actions + /// + public const ushort LockedZoomLevel = 1200; + + + /// + /// Minimum Zoom level to which autozoom can be locked. + /// + public const byte MinLockedZoomLevel = 200; + } +} diff --git a/UVtools.WPF/Controls/AdvancedImageBox.cs b/UVtools.WPF/Controls/AdvancedImageBox.cs index 98c6ba2..513e11d 100644 --- a/UVtools.WPF/Controls/AdvancedImageBox.cs +++ b/UVtools.WPF/Controls/AdvancedImageBox.cs @@ -605,6 +605,7 @@ namespace UVtools.WPF.Controls private void FillContainerOnPointerWheelChanged(object? sender, PointerWheelEventArgs e) { + if (Image is null) return; e.Handled = true; if (AllowZoom && SizeMode == SizeModes.Normal) { diff --git a/UVtools.WPF/Controls/WindowEx.cs b/UVtools.WPF/Controls/WindowEx.cs new file mode 100644 index 0000000..9d7a09e --- /dev/null +++ b/UVtools.WPF/Controls/WindowEx.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls; + +namespace UVtools.WPF.Controls +{ + public class WindowEx : Window + { + public DialogResults DialogResult { get; set; } = DialogResults.Unknown; + public enum DialogResults + { + Unknown, + OK, + Cancel + } + + public void CloseWithResult() + { + Close(DialogResult); + } + + public void ResetDataContext() + { + var old = DataContext; + DataContext = new object(); + DataContext = old; + } + } +} diff --git a/UVtools.WPF/Extensions/BitmapExtension.cs b/UVtools.WPF/Extensions/BitmapExtension.cs index d82b89c..c050bb8 100644 --- a/UVtools.WPF/Extensions/BitmapExtension.cs +++ b/UVtools.WPF/Extensions/BitmapExtension.cs @@ -114,10 +114,16 @@ namespace UVtools.WPF.Extensions 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) { diff --git a/UVtools.WPF/Extensions/DrawingExtensions.cs b/UVtools.WPF/Extensions/DrawingExtensions.cs new file mode 100644 index 0000000..179d97a --- /dev/null +++ b/UVtools.WPF/Extensions/DrawingExtensions.cs @@ -0,0 +1,23 @@ +/* + * 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. + */ + +namespace UVtools.WPF.Extensions +{ + public static class DrawingExtensions + { + public static Avalonia.Media.Color ToAvalonia(this System.Drawing.Color color) + { + return new Avalonia.Media.Color(color.A, color.R, color.G, color.B); + } + + public static System.Drawing.Color ToDotNet(this Avalonia.Media.Color color) + { + return System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B); + } + } +} diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index 84d5ad3..55ecdf8 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -39,7 +39,14 @@ namespace UVtools.WPF.Extensions => await window.MessageBoxGeneric(message, title ?? $"{window.Title} Error", buttons, Icon.Error, WindowStartupLocation.CenterOwner, style); public static async Task MessageBoxQuestion(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.YesNo, Style style = Style.None) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} Question", buttons, Icon.Info, WindowStartupLocation.CenterOwner, style); + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} Question", buttons, Icon.Setting, WindowStartupLocation.CenterOwner, style); + + public static void ResetDataContext(this Window window) + { + var old = window.DataContext; + window.DataContext = new object(); + window.DataContext = old; + } } } diff --git a/UVtools.WPF/LayerCache.cs b/UVtools.WPF/LayerCache.cs new file mode 100644 index 0000000..53660fc --- /dev/null +++ b/UVtools.WPF/LayerCache.cs @@ -0,0 +1,93 @@ +/* + * 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 Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Util; + +namespace UVtools.WPF +{ + public sealed class LayerCache + { + private Mat _image; + private Array _layerHierarchyJagged; + private VectorOfVectorOfPoint _layerContours; + private Mat _layerHierarchy; + + public Mat Image + { + get => _image; + set + { + Clear(); + _image = value; + ImageBgr = new Mat(); + CvInvoke.CvtColor(value, ImageBgr, ColorConversion.Gray2Bgr); + } + } + + public Mat ImageBgr { get; set; } + + public VectorOfVectorOfPoint LayerContours + { + get + { + if (_layerContours is null) CacheContours(); + return _layerContours; + } + private set => _layerContours = value; + } + + public Mat LayerHierarchy + { + get + { + if (_layerHierarchy is null) CacheContours(); + return _layerHierarchy; + } + private set => _layerHierarchy = value; + } + + public Array LayerHierarchyJagged + { + get + { + if(_layerHierarchyJagged is null) CacheContours(); + return _layerHierarchyJagged; + } + private set => _layerHierarchyJagged = value; + } + + public void CacheContours(bool refresh = false) + { + if(refresh) Clear(); + if (!ReferenceEquals(_layerContours, null)) return; + _layerContours = new VectorOfVectorOfPoint(); + _layerHierarchy = new Mat(); + CvInvoke.FindContours(Image, _layerContours, _layerHierarchy, RetrType.Ccomp, + ChainApproxMethod.ChainApproxSimple); + _layerHierarchyJagged = _layerHierarchy.GetData(); + } + + + /// + /// Clears the cache + /// + public void Clear() + { + _image?.Dispose(); + ImageBgr?.Dispose(); + _layerContours?.Dispose(); + _layerContours = null; + _layerHierarchy?.Dispose(); + _layerHierarchy = null; + _layerHierarchyJagged = null; + } + } +} diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 1b5c877..c918c45 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -11,35 +11,35 @@ Icon="/Assets/Icons/UVtools.ico" > - + - + - + - + - + - + - + @@ -47,7 +47,7 @@ - + @@ -69,7 +69,12 @@ - + @@ -81,7 +86,9 @@ - + @@ -93,7 +100,7 @@ - + @@ -126,17 +133,74 @@ - - - Up - - Down + + + + + + + + + - + + + - + @@ -145,7 +209,9 @@ --> - + @@ -164,7 +230,8 @@ Name="Layer.Image" /> - + diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 3ad375e..cd27114 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -11,17 +11,24 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core; +using UVtools.Core.Extensions; using UVtools.Core.FileFormats; +using UVtools.Core.PixelEditor; using UVtools.WPF.Controls; using UVtools.WPF.Extensions; -using UVtools.WPF.ViewModels; using UVtools.WPF.Windows; namespace UVtools.WPF { - public class MainWindow : Window, INotifyPropertyChanged + public class MainWindow : WindowEx { #region BindableBase /// @@ -72,41 +79,112 @@ namespace UVtools.WPF } #endregion - public MainWindowViewModel ViewModel { get; } + #region Properties + #region Redirects + public UserSettings Settings => UserSettings.Instance; + public FileFormat SlicerFile => App.SlicerFile; + #endregion + #region Controls + public AdvancedImageBox LayerImageBox; + public Slider LayerSlider; + #endregion + #region Members + private uint _actualLayer; + private bool _isGUIEnabled = true; + #endregion - public AdvancedImageBox LayerImage; - public Button ZoomToFitButton; - public Button CenterButton; + #region GUI Models + public bool IsGUIEnabled + { + get => _isGUIEnabled; + set => SetProperty(ref _isGUIEnabled, value); + } + + public bool IsFileLoaded + { + get => !ReferenceEquals(SlicerFile, null); + set => OnPropertyChanged(); + } + + public string MinimumLayerString => SlicerFile is null ? "???" : $"{SlicerFile.LayerHeight}mm\n0"; + public string MaximumLayerString => SlicerFile is null ? "???" : $"{SlicerFile.TotalHeight}mm\n{SlicerFile.LayerCount - 1}"; + + public uint SliderMaximumValue => SlicerFile?.LayerCount - 1 ?? 0; + + public bool CanGoUp => _actualLayer < SliderMaximumValue; + public bool CanGoDown => _actualLayer > 0; + #endregion + + private uint ActualLayer + { + get => _actualLayer; + set + { + if (!SetProperty(ref _actualLayer, value)) return; + LayerSlider.Value = ActualLayer; + OnPropertyChanged(nameof(CanGoDown)); + OnPropertyChanged(nameof(CanGoUp)); + ShowLayer(); + } + } + + public LayerCache LayerCache = new LayerCache(); + #endregion + + #region Constructors public MainWindow() { + DataContext = this; InitializeComponent(); #if DEBUG //this.AttachDevTools(); #endif App.ThemeSelector?.EnableThemes(this); + LayerImageBox = this.FindControl("Layer.Image"); + LayerSlider = this.FindControl("Layer.Navigation.Slider"); + //LayerSlider.PropertyChanged += (sender, args) => Debug.WriteLine($"{args.Property}: {args.NewValue}") + //PropertyChanged += OnPropertyChanged; + } - ViewModel = new MainWindowViewModel(this); - DataContext = ViewModel; - - LayerImage = this.FindControl("Layer.Image"); - ZoomToFitButton = this.FindControl + + + + + + + + diff --git a/UVtools.WPF/Windows/ColorPickerWindow.axaml.cs b/UVtools.WPF/Windows/ColorPickerWindow.axaml.cs new file mode 100644 index 0000000..64027c1 --- /dev/null +++ b/UVtools.WPF/Windows/ColorPickerWindow.axaml.cs @@ -0,0 +1,35 @@ +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using UVtools.WPF.Controls; + +namespace UVtools.WPF.Windows +{ + public class ColorPickerWindow : WindowEx + { + public Color ResultColor { get; set; } + + public ColorPickerWindow() + { + DataContext = this; + InitializeComponent(); + + } + public ColorPickerWindow(Color defaultColor) + { + ResultColor = defaultColor; + DataContext = this; + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void OnClickOk() + { + DialogResult = DialogResults.OK; + CloseWithResult(); + } + } +} diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 42e86b7..fb27ed8 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:cp="clr-namespace:ThemeEditor.Controls.ColorPicker;assembly=ThemeEditor.Controls.ColorPicker" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" x:Class="UVtools.WPF.Windows.SettingsWindow" WindowStartupLocation="CenterOwner" @@ -12,7 +13,7 @@ Icon="/Assets/Icons/UVtools.ico" > - + - - + + @@ -46,63 +47,95 @@ VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" Text="File open dialog filters:"/> - - - - - - - - - - - - - - + - + - + - @@ -133,12 +166,18 @@ Padding="10" BorderBrush="Black" BorderThickness="2" - VerticalAlignment="Center"/> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.TooltipOverlayBackgroundBrush}" + Command="{Binding SelectColor}" + CommandParameter="TooltipOverlayBackgroundColor" + /> + Content="Show by default" + IsChecked="{Binding Settings.LayerPreview.TooltipOverlay}" + /> @@ -151,12 +190,18 @@ Padding="10" BorderBrush="Black" BorderThickness="2" - VerticalAlignment="Center"/> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.VolumeBoundsOutlineBrush}" + Command="{Binding SelectColor}" + CommandParameter="VolumeBoundsOutlineColor" + /> + Maximum="50" + Value="{Binding Settings.LayerPreview.VolumeBoundsOutlineThickness}" + /> + Content="Show by default" + IsChecked="{Binding Settings.LayerPreview.VolumeBoundsOutline}" + /> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.LayerBoundsOutlineBrush}" + Command="{Binding SelectColor}" + CommandParameter="LayerBoundsOutlineColor" + /> + Maximum="50" + Value="{Binding Settings.LayerPreview.LayerBoundsOutlineThickness}" + /> + Content="Show by default" + IsChecked="{Binding Settings.LayerPreview.LayerBoundsOutline}" + /> @@ -202,12 +257,18 @@ Padding="10" BorderBrush="Black" BorderThickness="2" - VerticalAlignment="Center"/> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.HollowOutlineBrush}" + Command="{Binding SelectColor}" + CommandParameter="HollowOutlineColor" + /> + Maximum="50" + Value="{Binding Settings.LayerPreview.HollowOutlineLineThickness}" + /> + Content="Show by default" + IsChecked="{Binding Settings.LayerPreview.HollowOutline}" + /> @@ -231,7 +294,11 @@ Padding="10" BorderBrush="Black" BorderThickness="2" - VerticalAlignment="Center"/> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.PreviousLayerDifferenceBrush}" + Command="{Binding SelectColor}" + CommandParameter="PreviousLayerDifferenceColor" + /> + VerticalAlignment="Center" + Background="{Binding Settings.LayerPreview.IslandBrush}" + Command="{Binding SelectColor}" + CommandParameter="IslandColor" + /> -