From fbe866f7b26c10bb54d72c029f8c628988a90be2 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Tue, 24 Dec 2019 16:45:22 +0100 Subject: Started on the resizing step of the pipeline + pipeline design. --- Avalar.csproj | 8 ++++- MainWindow.xaml | 2 +- MainWindow.xaml.cs | 15 +++++++-- Models/ImageModel.cs | 56 ++++++++++++++++++++++++++++++++ Models/Interfaces/IImageModel.cs | 9 +++++ Models/Interfaces/SettingsModel.cs | 12 +++++++ Services/IProcessor.cs | 9 +++++ Services/Resizer/IResizer.cs | 7 ++++ Services/Resizer/ImageMagickResizer.cs | 26 +++++++++++++++ ViewModels/Image/ImageViewModel.cs | 13 +++++--- ViewModels/Settings/SettingsViewModel.cs | 2 +- Views/AvalarViewService.cs | 2 +- Views/Image/ImageControl.xaml.cs | 8 +++-- Views/Settings/Settings.xaml | 2 +- Views/Settings/Settings.xaml.cs | 4 +-- 15 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 Models/ImageModel.cs create mode 100644 Models/Interfaces/IImageModel.cs create mode 100644 Models/Interfaces/SettingsModel.cs create mode 100644 Services/IProcessor.cs create mode 100644 Services/Resizer/IResizer.cs create mode 100644 Services/Resizer/ImageMagickResizer.cs diff --git a/Avalar.csproj b/Avalar.csproj index 93ac3a3..06e24a5 100644 --- a/Avalar.csproj +++ b/Avalar.csproj @@ -1,6 +1,6 @@  - WinExe + Exe netcoreapp3.0 false @@ -25,6 +25,12 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/MainWindow.xaml b/MainWindow.xaml index b4a5565..d42cef5 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -13,6 +13,6 @@ - + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 154ee31..b2a5090 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1,8 +1,11 @@ +using Avalar.Models; +using Avalar.Services.Resizer; using Avalar.Viewmodels.Image; using Avalar.ViewModels.Settings; using Avalar.Views; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; namespace Avalar { @@ -11,14 +14,20 @@ namespace Avalar private ImageViewModel ImageVm { get; } private SettingsViewModel SettingsVm { get; } - private IAvalarViewService ViewService { get; } + private ImageModel ImageM { get; } + public MainWindow() { InitializeComponent(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(typeof(IResizer), typeof(ImageMagickResizer)); + serviceCollection.AddScoped(typeof(IAvalarViewService), (sp) => new AvalarViewService(this)); + + var serviceProvider = serviceCollection.BuildServiceProvider(); - ViewService = new AvalarViewService(this); + ImageM = new ImageModel(); - ImageVm = new ImageViewModel(ViewService); + ImageVm = new ImageViewModel(ImageM, serviceProvider); SettingsVm = new SettingsViewModel(); DataContext = this; diff --git a/Models/ImageModel.cs b/Models/ImageModel.cs new file mode 100644 index 0000000..e0bb1af --- /dev/null +++ b/Models/ImageModel.cs @@ -0,0 +1,56 @@ +using Avalar.Models.Interfaces; +using Avalonia.Media.Imaging; +using ReactiveUI; +using System; + +namespace Avalar.Models +{ + public class ImageModel : ReactiveObject, IImageModel, ISettingsModel, IDisposable + { + + private IBitmap m_OriginalBitmap; + + private IBitmap m_LatestResult; + + private double m_Width; + public double Width { + get => m_Width; + set => this.RaiseAndSetIfChanged(ref m_Width, value); + } + + private double m_Height; + public double Height + { + get => m_Height; + set => this.RaiseAndSetIfChanged(ref m_Height, value); + } + + public IBitmap LoadImage(string imagePath) + { + m_OriginalBitmap = new Bitmap(imagePath); + m_LatestResult = m_OriginalBitmap; + return m_LatestResult; + } + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) return; + + if (disposing) + { + m_OriginalBitmap.Dispose(); + m_LatestResult.Dispose(); + } + + disposed = true; + } + } +} diff --git a/Models/Interfaces/IImageModel.cs b/Models/Interfaces/IImageModel.cs new file mode 100644 index 0000000..897a221 --- /dev/null +++ b/Models/Interfaces/IImageModel.cs @@ -0,0 +1,9 @@ +using Avalonia.Media.Imaging; + +namespace Avalar.Models.Interfaces +{ + public interface IImageModel + { + IBitmap LoadImage(string imagePath); + } +} diff --git a/Models/Interfaces/SettingsModel.cs b/Models/Interfaces/SettingsModel.cs new file mode 100644 index 0000000..6149c1e --- /dev/null +++ b/Models/Interfaces/SettingsModel.cs @@ -0,0 +1,12 @@ + +using Avalonia; + +namespace Avalar.Models.Interfaces +{ + interface ISettingsModel + { + public double Width { get; set; } + + public double Height { get; set; } + } +} diff --git a/Services/IProcessor.cs b/Services/IProcessor.cs new file mode 100644 index 0000000..11e83ab --- /dev/null +++ b/Services/IProcessor.cs @@ -0,0 +1,9 @@ +using Avalonia.Media.Imaging; + +namespace Avalar.Services +{ + public interface IProcessor + { + IBitmap Process(IBitmap bitmap); + } +} diff --git a/Services/Resizer/IResizer.cs b/Services/Resizer/IResizer.cs new file mode 100644 index 0000000..e71d4f1 --- /dev/null +++ b/Services/Resizer/IResizer.cs @@ -0,0 +1,7 @@ + +namespace Avalar.Services.Resizer +{ + public interface IResizer : IProcessor + { + } +} diff --git a/Services/Resizer/ImageMagickResizer.cs b/Services/Resizer/ImageMagickResizer.cs new file mode 100644 index 0000000..e48e576 --- /dev/null +++ b/Services/Resizer/ImageMagickResizer.cs @@ -0,0 +1,26 @@ + +using Avalonia.Media.Imaging; +using ImageMagick; +using System.IO; + +namespace Avalar.Services.Resizer +{ + public class ImageMagickResizer : IResizer + { + public IBitmap Process(IBitmap bitmap) + { + using (var stream = new MemoryStream()) + { + bitmap?.Save(stream); + using (var image = new MagickImage(stream)) + { + image.Resize(300, 300); + using (var stream2 = new MemoryStream()) + { + return new Bitmap(stream2); + } + } + } + } + } +} diff --git a/ViewModels/Image/ImageViewModel.cs b/ViewModels/Image/ImageViewModel.cs index 030a95b..3b41919 100644 --- a/ViewModels/Image/ImageViewModel.cs +++ b/ViewModels/Image/ImageViewModel.cs @@ -1,22 +1,27 @@  +using Avalar.Models.Interfaces; using Avalar.ViewModels.Image; using Avalar.Views; using Avalonia.Media.Imaging; using ReactiveUI; +using System; using System.Linq; namespace Avalar.Viewmodels.Image { public class ImageViewModel : ReactiveObject { - public ImageViewModel(IAvalarViewService viewService) + public ImageViewModel(IImageModel imageModel, IServiceProvider serviceProvider) { - ViewService = viewService; + ViewService = serviceProvider?.GetService(typeof(IAvalarViewService)) as IAvalarViewService; + ImageModel = imageModel; ChildViewModel = new ImageNotLoadedViewModel(ReactiveCommand.Create(OpenFile)); } private IAvalarViewService ViewService { get; } + private IImageModel ImageModel { get; } + private object m_ChildViewModel; public object ChildViewModel @@ -29,10 +34,10 @@ namespace Avalar.Viewmodels.Image public async void OpenFile() { - var result = await ViewService.ShowOpenImageFileDialog(); + var result = await ViewService.ShowOpenImageFileDialog().ConfigureAwait(true); if (result.Length != 1) return; - ChildViewModel = new ImageLoadedViewModel(new Bitmap(result.First())); + ChildViewModel = new ImageLoadedViewModel(ImageModel.LoadImage(result.First())); } } } diff --git a/ViewModels/Settings/SettingsViewModel.cs b/ViewModels/Settings/SettingsViewModel.cs index b9f076b..730b662 100644 --- a/ViewModels/Settings/SettingsViewModel.cs +++ b/ViewModels/Settings/SettingsViewModel.cs @@ -1,7 +1,7 @@  namespace Avalar.ViewModels.Settings { - class SettingsViewModel + public class SettingsViewModel { } } diff --git a/Views/AvalarViewService.cs b/Views/AvalarViewService.cs index 994f88b..7b0cc0b 100644 --- a/Views/AvalarViewService.cs +++ b/Views/AvalarViewService.cs @@ -25,7 +25,7 @@ namespace Avalar.Views openFileDialog.Title = "Open Image"; - var result = await openFileDialog.ShowAsync(m_Window); + var result = await openFileDialog.ShowAsync(m_Window).ConfigureAwait(false); return result; } } diff --git a/Views/Image/ImageControl.xaml.cs b/Views/Image/ImageControl.xaml.cs index 2189bb0..e806d6f 100644 --- a/Views/Image/ImageControl.xaml.cs +++ b/Views/Image/ImageControl.xaml.cs @@ -21,7 +21,11 @@ namespace Avalar.Views.Image { InitializeComponent(); - Image = this.FindResource("ImageBrush") as ImageBrush ?? throw new System.ArgumentException("ImageBrush"); + var brushName = "ImageBrush"; + string message = $"The brush with name {brushName} could not be found in the Xaml Resources"; +#pragma warning disable CA1303 // Do not pass literals as localized parameters + Image = (this).FindResource(brushName) as ImageBrush ?? throw new ArgumentException(message); +#pragma warning restore CA1303 // Do not pass literals as localized parameters ImageCanvas = this.FindControl("ImageCanvas"); } @@ -73,7 +77,7 @@ namespace Avalar.Views.Image public void OnPointerWheelChanged(object sender, PointerWheelEventArgs e) { var invOldZoom = InvCurrentZoom; - var zoomFactor = e.Delta.Y * ZoomTick + 1.0; + var zoomFactor = (e?.Delta.Y ?? 0) * ZoomTick + 1.0; CurrentZoom = Math.Clamp(CurrentZoom * zoomFactor, 1, double.PositiveInfinity); var sourceRect = Image.SourceRect.Rect; diff --git a/Views/Settings/Settings.xaml b/Views/Settings/Settings.xaml index dfc8602..fb547e7 100644 --- a/Views/Settings/Settings.xaml +++ b/Views/Settings/Settings.xaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Avalar.Views.Settings.Settings" + x:Class="Avalar.Views.Settings.SettingsControl" MinWidth="400"> Welcome to Avalonia! diff --git a/Views/Settings/Settings.xaml.cs b/Views/Settings/Settings.xaml.cs index 4504460..1197c39 100644 --- a/Views/Settings/Settings.xaml.cs +++ b/Views/Settings/Settings.xaml.cs @@ -3,9 +3,9 @@ using Avalonia.Markup.Xaml; namespace Avalar.Views.Settings { - public class Settings : UserControl + public class SettingsControl : UserControl { - public Settings() + public SettingsControl() { InitializeComponent(); } -- cgit v1.2.3-70-g09d2