using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Media; using Avalonia.Threading; using PicView.Avalonia.FileSystem; using PicView.Avalonia.ImageHandling; using PicView.Avalonia.Resizing; using PicView.Avalonia.UI; using PicView.Avalonia.ViewModels; using PicView.Core.ImageDecoding; using R3; namespace PicView.Avalonia.Views; public partial class SingleImageResizeView : UserControl { private double _aspectRatio; private readonly CompositeDisposable _imageUpdateSubscription = new(); private bool _isKeepingAspectRatio = true; public SingleImageResizeView() { InitializeComponent(); Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object? sender, EventArgs e) { if (DataContext is not MainViewModel vm) { return; } if (!Settings.Theme.Dark || Settings.Theme.GlassTheme) { BgPanel.Background = Brushes.Transparent; } _aspectRatio = (double)vm.PicViewer.PixelWidth.CurrentValue / vm.PicViewer.PixelHeight.CurrentValue; RegisterEventHandlers(vm); Observable.EveryValueChanged(vm.PicViewer, x => x.FileInfo.Value, UIHelper.GetFrameProvider) .Subscribe(_ => { UpdateQualitySliderState(); ShowCancelButton(); }).AddTo(_imageUpdateSubscription); } private void OnUnloaded(object? sender, EventArgs e) { _imageUpdateSubscription?.Dispose(); } private void RegisterEventHandlers(MainViewModel vm) { UpdateQualitySliderState(); QualitySlider.ValueChanged += (_, _) => ShowResetButton(); SaveButton.Click += async (_, _) => await SaveImage(vm).ConfigureAwait(false); SaveAsButton.Click += async (_, _) => await SaveImageAs(vm).ConfigureAwait(false); PixelWidthTextBox.KeyDown += async (_, e) => await SaveImageOnEnter(e, vm); PixelHeightTextBox.KeyDown += async (_, e) => await SaveImageOnEnter(e, vm); PixelWidthTextBox.KeyUp += (_, _) => AdjustAspectRatio(PixelWidthTextBox); PixelHeightTextBox.KeyUp += (_, _) => AdjustAspectRatio(PixelHeightTextBox); ConversionComboBox.SelectionChanged += (_, _) => { UpdateQualitySliderState(); ShowResetButton(); }; ResetButton.Click += (_, _) => ResetSettings(vm); CancelButton.Click += (_, _) => (VisualRoot as Window)?.Close(); LinkChainButton.Click += (_, _) => ToggleAspectRatio(); } private void ShowResetButton() { CancelButton.IsVisible = false; ResetButton.IsVisible = true; } private void ShowCancelButton() { CancelButton.IsVisible = true; ResetButton.IsVisible = false; } private void AdjustAspectRatio(TextBox sender) { if (!_isKeepingAspectRatio) { return; } AspectRatioHelper.SetAspectRatioForTextBox( PixelWidthTextBox, PixelHeightTextBox, sender == PixelWidthTextBox, _aspectRatio, DataContext as MainViewModel); ShowResetButton(); } private void UpdateQualitySliderState() { if (DataContext is not MainViewModel vm) { return; } try { if (IsConversionToQualityFormat()) { QualitySlider.IsEnabled = true; QualitySlider.Value = 75; } else if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.CurrentValue.Extension)) { QualitySlider.IsEnabled = true; QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.CurrentValue.FullName); } else { QualitySlider.IsEnabled = false; } } catch (Exception e) { #if DEBUG Console.WriteLine(e); #endif } } private bool IsConversionToQualityFormat() => JpgItem.IsSelected || PngItem.IsSelected; private static bool IsOriginalFileQualityFormat(string ext) => ext.Equals(".jpg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".png", StringComparison.OrdinalIgnoreCase); private async Task SaveImageOnEnter(KeyEventArgs e, MainViewModel vm) { if (e.Key == Key.Enter) { await SaveImage(vm).ConfigureAwait(false); } } private async Task SaveImageAs(MainViewModel vm) { if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || desktop.MainWindow?.StorageProvider is not { } provider) { return; } var fileInfoFullName = vm.PicViewer.FileInfo.CurrentValue.FullName; var ext = GetSelectedFileExtension(vm, ref fileInfoFullName); var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.CurrentValue.FullName, ext); if (file is null) { return; } await DoSaveImage(vm, file).ConfigureAwait(false); } private async Task SaveImage(MainViewModel vm) { await DoSaveImage(vm, vm.PicViewer.FileInfo.CurrentValue.FullName).ConfigureAwait(false); } private async Task DoSaveImage(MainViewModel vm, string destination) { if (!uint.TryParse(PixelWidthTextBox.Text, out var width) || !uint.TryParse(PixelHeightTextBox.Text, out var height)) { return; } await Dispatcher.UIThread.InvokeAsync(() => SetLoadingState(true)); var path = vm.PicViewer.FileInfo.CurrentValue.FullName; var ext = GetSelectedFileExtension(vm, ref destination); destination = Path.ChangeExtension(destination, ext); var sameFile = path.Equals(destination, StringComparison.OrdinalIgnoreCase); var quality = GetQualityValue(ext, destination); await SaveImageHandler.SaveImageWithPossibleNavigation(vm, path, destination, sameFile, ext, width, height, quality, null, _isKeepingAspectRatio); await Dispatcher.UIThread.InvokeAsync(() => SetLoadingState(false)); } private void SetLoadingState(bool isLoading) { ParentContainer.Opacity = isLoading ? 0.1 : 1; ParentContainer.IsHitTestVisible = !isLoading; SpinWaiter.IsVisible = isLoading; } private string GetSelectedFileExtension(MainViewModel vm, ref string destination) { var ext = vm.PicViewer.FileInfo.CurrentValue.Extension; if (NoConversion.IsSelected) { return ext; } ext = GetExtensionFromSelectedItem() ?? ext; destination = Path.ChangeExtension(destination, ext); return ext; } private string? GetExtensionFromSelectedItem() { if (PngItem.IsSelected) { return ".png"; } if (JpgItem.IsSelected) { return ".jpg"; } if (WebpItem.IsSelected) { return ".webp"; } if (AvifItem.IsSelected) { return ".avif"; } if (HeicItem.IsSelected) { return ".heic"; } if (JxlItem.IsSelected) { return ".jxl"; } return null; } private uint? GetQualityValue(string ext, string destination) { if (QualitySlider.IsEnabled && ( ext.Equals(".jpg", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(destination).Equals(".jpg", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(destination).Equals(".jpeg", StringComparison.OrdinalIgnoreCase))) { return (uint)QualitySlider.Value; } return null; } private void ResetSettings(MainViewModel vm) { PixelWidthTextBox.Text = vm.PicViewer.PixelWidth.ToString(); PixelHeightTextBox.Text = vm.PicViewer.PixelHeight.ToString(); if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.CurrentValue.Extension)) { QualitySlider.IsEnabled = true; QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.CurrentValue.FullName); } else { QualitySlider.IsEnabled = false; } ConversionComboBox.SelectedItem = NoConversion; _isKeepingAspectRatio = true; LinkChainImage.IsVisible = true; UnlinkChainImage.IsVisible = false; ShowCancelButton(); } private void ToggleAspectRatio() { _isKeepingAspectRatio = !_isKeepingAspectRatio; LinkChainImage.IsVisible = _isKeepingAspectRatio; UnlinkChainImage.IsVisible = !_isKeepingAspectRatio; if (_isKeepingAspectRatio) { AdjustAspectRatio(PixelWidthTextBox); } if (!_isKeepingAspectRatio) { ShowResetButton(); } } ~SingleImageResizeView() { _imageUpdateSubscription?.Dispose(); } }