diff --git a/src/PicView.Avalonia.Win32/Views/SingleImageResizeWindow.axaml b/src/PicView.Avalonia.Win32/Views/SingleImageResizeWindow.axaml index b78f0109..cb8674e7 100644 --- a/src/PicView.Avalonia.Win32/Views/SingleImageResizeWindow.axaml +++ b/src/PicView.Avalonia.Win32/Views/SingleImageResizeWindow.axaml @@ -79,7 +79,7 @@ Height="28" LineHeight="28" Padding="30,0,0,0" - Text="{CompiledBinding Resize, + Text="{CompiledBinding ResizeImage, Mode=OneWay}" TextAlignment="Center" x:Name="TitleText" /> @@ -93,8 +93,6 @@ diff --git a/src/PicView.Avalonia/Navigation/ImageIterator.cs b/src/PicView.Avalonia/Navigation/ImageIterator.cs index f027509b..fc2e27e5 100644 --- a/src/PicView.Avalonia/Navigation/ImageIterator.cs +++ b/src/PicView.Avalonia/Navigation/ImageIterator.cs @@ -30,7 +30,7 @@ public sealed class ImageIterator : IDisposable private PreLoader PreLoader { get; } = new(); private static FileSystemWatcher? _watcher; - private static bool _isRunning; + public bool IsRunning { get; private set; } private readonly MainViewModel? _vm; #endregion @@ -100,13 +100,13 @@ private async Task OnFileAdded(FileSystemEventArgs e) } var retries = 0; - while (_isRunning && retries < 10) + while (IsRunning && retries < 10) { await Task.Delay(200); retries++; } - _isRunning = true; + IsRunning = true; var newList = await Task.FromResult(_vm.PlatformService.GetFiles(fileInfo)); if (newList.Count == 0) @@ -126,7 +126,7 @@ private async Task OnFileAdded(FileSystemEventArgs e) ImagePaths = newList; - _isRunning = false; + IsRunning = false; var index = ImagePaths.IndexOf(e.FullPath); if (index < 0) @@ -192,12 +192,12 @@ private async Task OnFileDeleted(FileSystemEventArgs e) return; } - if (_isRunning) + if (IsRunning) { return; } - _isRunning = true; + IsRunning = true; var index = ImagePaths.IndexOf(e.FullPath); if (index < 0) { @@ -268,7 +268,7 @@ private async Task OnFileDeleted(FileSystemEventArgs e) FileHistoryNavigation.Remove(e.FullPath); - _isRunning = false; + IsRunning = false; SetTitleHelper.SetTitle(_vm); if (cleared) @@ -289,12 +289,12 @@ private async Task OnFileRenamed(RenamedEventArgs e) return; } - if (_isRunning) + if (IsRunning) { return; } - _isRunning = true; + IsRunning = true; var oldIndex = ImagePaths.IndexOf(e.OldFullPath); var sameFile = CurrentIndex == oldIndex; @@ -336,7 +336,7 @@ private async Task OnFileRenamed(RenamedEventArgs e) SetTitleHelper.SetTitle(_vm); - _isRunning = false; + IsRunning = false; FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath); GalleryFunctions.RemoveGalleryItem(oldIndex, _vm); await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm); @@ -416,6 +416,12 @@ public async Task ReloadFileList() InitiateFileSystemWatcher(InitialFileInfo); } + public async Task QuickReload() + { + RemoveCurrentItemFromPreLoader(); + await IterateToIndex(CurrentIndex).ConfigureAwait(false); + } + public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false) { int next; diff --git a/src/PicView.Avalonia/Navigation/NavigationHelper.cs b/src/PicView.Avalonia/Navigation/NavigationHelper.cs index 4668568d..fe8d0679 100644 --- a/src/PicView.Avalonia/Navigation/NavigationHelper.cs +++ b/src/PicView.Avalonia/Navigation/NavigationHelper.cs @@ -266,6 +266,18 @@ public static async Task LoadPicFromFile(string fileName, MainViewModel vm, File { if (fileInfo.DirectoryName == vm.ImageIterator.InitialFileInfo.DirectoryName) { + // Need to wait for the file watching to add it to the list + var retries = 0; + while (vm.ImageIterator.IsRunning && retries < 10) + { + await Task.Delay(50).ConfigureAwait(false); + retries++; + if (retries > 10) + { + await ErrorHandling.ReloadAsync(vm); + return; + } + } var index = vm.ImageIterator.ImagePaths.IndexOf(fileName); if (index != -1) { diff --git a/src/PicView.Avalonia/Navigation/Preloader.cs b/src/PicView.Avalonia/Navigation/Preloader.cs index 3d0ce0e2..b2cb5c4d 100644 --- a/src/PicView.Avalonia/Navigation/Preloader.cs +++ b/src/PicView.Avalonia/Navigation/Preloader.cs @@ -407,6 +407,7 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, false); } private void Dispose(bool disposing) diff --git a/src/PicView.Avalonia/PicViewTheme/Classes.axaml b/src/PicView.Avalonia/PicViewTheme/Classes.axaml index f665bce1..dffcb9ca 100644 --- a/src/PicView.Avalonia/PicViewTheme/Classes.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Classes.axaml @@ -65,9 +65,10 @@ - + + \ No newline at end of file diff --git a/src/PicView.Avalonia/PicViewTheme/Controls/Button.axaml b/src/PicView.Avalonia/PicViewTheme/Controls/Button.axaml index a04094c5..b899d7f1 100644 --- a/src/PicView.Avalonia/PicViewTheme/Controls/Button.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Controls/Button.axaml @@ -54,6 +54,39 @@ + + + + + + + diff --git a/src/PicView.Avalonia/PicViewTheme/Controls/MenuFlyoutPresenter.axaml b/src/PicView.Avalonia/PicViewTheme/Controls/MenuFlyoutPresenter.axaml index a45eaddf..69a0a568 100644 --- a/src/PicView.Avalonia/PicViewTheme/Controls/MenuFlyoutPresenter.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Controls/MenuFlyoutPresenter.axaml @@ -20,5 +20,8 @@ + \ No newline at end of file diff --git a/src/PicView.Avalonia/PicViewTheme/Controls/RadioButton.axaml b/src/PicView.Avalonia/PicViewTheme/Controls/RadioButton.axaml index d7e4b590..df59382d 100644 --- a/src/PicView.Avalonia/PicViewTheme/Controls/RadioButton.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Controls/RadioButton.axaml @@ -1,22 +1,89 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + - - + + + + + + + + + + + + + + + + + + + + + + + + + Margin="4,0,0,0" + Name="PART_ContentPresenter" + RecognizesAccessKey="True" + VerticalAlignment="Center" /> diff --git a/src/PicView.Avalonia/PicViewTheme/Controls/SplitButton.axaml b/src/PicView.Avalonia/PicViewTheme/Controls/SplitButton.axaml index 60eb18b0..828d807d 100644 --- a/src/PicView.Avalonia/PicViewTheme/Controls/SplitButton.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Controls/SplitButton.axaml @@ -131,10 +131,11 @@ VerticalContentAlignment="Center" x:Name="PART_SecondaryButton"> + Width="12" + x:Name="PART_ChevronIcon" /> @@ -189,6 +190,70 @@ + + + + @@ -204,5 +269,9 @@ + + diff --git a/src/PicView.Avalonia/PicViewTheme/Controls/TextBox.axaml b/src/PicView.Avalonia/PicViewTheme/Controls/TextBox.axaml index 31b2f2c0..715fc7d9 100644 --- a/src/PicView.Avalonia/PicViewTheme/Controls/TextBox.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Controls/TextBox.axaml @@ -7,7 +7,7 @@ - + diff --git a/src/PicView.Avalonia/PicViewTheme/Icons.axaml b/src/PicView.Avalonia/PicViewTheme/Icons.axaml index 3781efff..2994dca9 100644 --- a/src/PicView.Avalonia/PicViewTheme/Icons.axaml +++ b/src/PicView.Avalonia/PicViewTheme/Icons.axaml @@ -22,6 +22,7 @@ F1M27.008,0L27.01,31.062 0,15.529z F1M0.002,31.062L0,0 27.01,15.534z M112 328l144-144 144 144 + M98 190.06l139.78 163.12a24 24 0 0036.44 0L414 190.06c13.34-15.57 2.28-39.62-18.22-39.62h-279.6c-20.5 0-31.56 24.05-18.18 39.62z M50.75 333.25c-12 12-18.75 28.28-18.75 45.26V424L0 480l32 32 56-32h45.49c16.97 0 33.25-6.74 45.25-18.74l126.64-126.62-128-128L50.75 333.25zM483.88 28.12c-37.47-37.5-98.28-37.5-135.75 0l-77.09 77.09-13.1-13.1c-9.44-9.44-24.65-9.31-33.94 0l-40.97 40.97c-9.37 9.37-9.37 24.57 0 33.94l161.94 161.94c9.44 9.44 24.65 9.31 33.94 0L419.88 288c9.37-9.37 9.37-24.57 0-33.94l-13.1-13.1 77.09-77.09c37.51-37.48 37.51-98.26.01-135.75z M48 32C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H48zm0 32h106c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H38c-3.3 0-6-2.7-6-6V80c0-8.8 7.2-16 16-16zm426 96H38c-3.3 0-6-2.7-6-6v-36c0-3.3 2.7-6 6-6h138l30.2-45.3c1.1-1.7 3-2.7 5-2.7H464c8.8 0 16 7.2 16 16v74c0 3.3-2.7 6-6 6zM256 424c-66.2 0-120-53.8-120-120s53.8-120 120-120 120 53.8 120 120-53.8 120-120 120zm0-208c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88zm-48 104c-8.8 0-16-7.2-16-16 0-35.3 28.7-64 64-64 8.8 0 16 7.2 16 16s-7.2 16-16 16c-17.6 0-32 14.4-32 32 0 8.8-7.2 16-16 16z M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z @@ -32,6 +33,40 @@ M192,96v64h248c4.4,0,8,3.6,8,8v240c0,4.4-3.6,8-8,8H136c-4.4,0-8-3.6-8-8v-48c0-4.4,3.6-8,8-8h248V224H192v64L64,192 L192,96z M488 352h-40V109.25l59.31-59.31c6.25-6.25 6.25-16.38 0-22.63L484.69 4.69c-6.25-6.25-16.38-6.25-22.63 0L402.75 64H192v96h114.75L160 306.75V24c0-13.26-10.75-24-24-24H88C74.75 0 64 10.74 64 24v40H24C10.75 64 0 74.74 0 88v48c0 13.25 10.75 24 24 24h40v264c0 13.25 10.75 24 24 24h232v-96H205.25L352 205.25V488c0 13.25 10.75 24 24 24h48c13.25 0 24-10.75 24-24v-40h40c13.25 0 24-10.75 24-24v-48c0-13.26-10.75-24-24-24z M512 1536h768v-384h-768v384zm896 0h128v-896q0-14-10-38.5t-20-34.5l-281-281q-10-10-34-20t-39-10v416q0 40-28 68t-68 28h-576q-40 0-68-28t-28-68v-416h-128v1280h128v-416q0-40 28-68t68-28h832q40 0 68 28t28 68v416zm-384-928v-320q0-13-9.5-22.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 22.5v320q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5-9.5t9.5-22.5zm640 32v928q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h928q40 0 88 20t76 48l280 280q28 28 48 76t20 88z + + + + + + + + + + + + + + + + + + + diff --git a/src/PicView.Avalonia/Navigation/QuickLoad.cs b/src/PicView.Avalonia/StartUp/QuickLoad.cs similarity index 92% rename from src/PicView.Avalonia/Navigation/QuickLoad.cs rename to src/PicView.Avalonia/StartUp/QuickLoad.cs index 07aa8e7e..4b604aa6 100644 --- a/src/PicView.Avalonia/Navigation/QuickLoad.cs +++ b/src/PicView.Avalonia/StartUp/QuickLoad.cs @@ -1,6 +1,7 @@ using Avalonia.Threading; using PicView.Avalonia.Gallery; using PicView.Avalonia.ImageHandling; +using PicView.Avalonia.Navigation; using PicView.Avalonia.UI; using PicView.Avalonia.ViewModels; using PicView.Avalonia.WindowBehavior; @@ -8,7 +9,7 @@ using PicView.Core.FileHandling; using PicView.Core.Gallery; -namespace PicView.Avalonia.Navigation; +namespace PicView.Avalonia.StartUp; public static class QuickLoad { @@ -67,6 +68,13 @@ await Dispatcher.UIThread.InvokeAsync(() => if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide) { SetTitleHelper.SetSideBySideTitle(vm, imageModel, secondaryPreloadValue?.ImageModel); + + // Sometimes the images are not rendered in side by side, this fixes it + // TODO: Improve and fix side by side and remove this hack + Dispatcher.UIThread.Post(() => + { + vm.ImageViewer?.MainImage?.InvalidateVisual(); + }); } else { diff --git a/src/PicView.Avalonia/ViewModels/MainViewModel.cs b/src/PicView.Avalonia/ViewModels/MainViewModel.cs index 48527270..22516a4d 100644 --- a/src/PicView.Avalonia/ViewModels/MainViewModel.cs +++ b/src/PicView.Avalonia/ViewModels/MainViewModel.cs @@ -1390,8 +1390,10 @@ private async Task ResizeImageByPercentage(int percentage) var success = await ConversionHelper.ResizeImageByPercentage(FileInfo, percentage); if (success) { - ImageIterator?.RemoveCurrentItemFromPreLoader(); - await ImageIterator?.IterateToIndex(ImageIterator.CurrentIndex); + if (ImageIterator is not null) + { + await ImageIterator.QuickReload().ConfigureAwait(false); + } } else { diff --git a/src/PicView.Avalonia/ViewModels/ViewModelBase.cs b/src/PicView.Avalonia/ViewModels/ViewModelBase.cs index 4c56413e..ff040071 100644 --- a/src/PicView.Avalonia/ViewModels/ViewModelBase.cs +++ b/src/PicView.Avalonia/ViewModels/ViewModelBase.cs @@ -231,10 +231,46 @@ public void UpdateLanguage() Center = TranslationHelper.Translation.Center; Tile = TranslationHelper.Translation.Tile; Fit = TranslationHelper.Translation.Fit; + Pixels = TranslationHelper.Translation.Pixels; + Percentage = TranslationHelper.Translation.Percentage; + Quality = TranslationHelper.Translation.Quality; + SaveAs = TranslationHelper.Translation.SaveAs; } #region Strings + private string? _saveAs; + + public string? SaveAs + { + get => _saveAs; + set => this.RaiseAndSetIfChanged(ref _saveAs, value); + } + + private string? _quality; + + public string? Quality + { + get => _quality; + set => this.RaiseAndSetIfChanged(ref _quality, value); + } + + private string? _percentage; + + public string? Percentage + { + get => _percentage; + set => this.RaiseAndSetIfChanged(ref _percentage, value); + } + + private string? _pixels; + + public string? Pixels + { + get => _pixels.FirstCharToUpper(); + set => this.RaiseAndSetIfChanged(ref _pixels, value); + } + private string? _fit; public string? Fit diff --git a/src/PicView.Avalonia/Views/ExifView.axaml b/src/PicView.Avalonia/Views/ExifView.axaml index d5310f80..c99a09aa 100644 --- a/src/PicView.Avalonia/Views/ExifView.axaml +++ b/src/PicView.Avalonia/Views/ExifView.axaml @@ -19,9 +19,8 @@ @@ -44,6 +43,7 @@ Mode=OneWay}" Width="100" /> @@ -205,7 +205,7 @@ Mode=OneWay}" Width="100" /> @@ -424,7 +424,7 @@ - + @@ -457,7 +457,7 @@ ElementName=ConversionComboBox}" HorizontalAlignment="Center" Margin="0,12" - Width="130"> + Width="140"> @@ -514,7 +514,7 @@ ElementName=PercentageComboBox}" HorizontalAlignment="Center" Margin="0,12" - Width="130"> + Width="140"> - + - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs b/src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs index 1faefb27..faab2a2f 100644 --- a/src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs +++ b/src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs @@ -1,4 +1,7 @@ using Avalonia.Controls; +using PicView.Avalonia.Navigation; +using PicView.Avalonia.ViewModels; +using PicView.Core.ImageDecoding; namespace PicView.Avalonia.Views; @@ -7,5 +10,115 @@ public partial class SingleImageResizeView : UserControl public SingleImageResizeView() { InitializeComponent(); + Loaded += delegate + { + if (DataContext is not MainViewModel vm) + return; + + SaveButton.Click += async (_, _) => await SaveImage(vm).ConfigureAwait(false); + SaveAsButton.Click += async (_, _) => await SaveImageAs(vm).ConfigureAwait(false); + }; + } + + private async Task SaveImageAs(MainViewModel vm) + { + throw new NotImplementedException(); + } + + private async Task SaveImage(MainViewModel vm) + { + await DoSaveImage(vm, vm.FileInfo.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; + } + + if (width == vm.PixelWidth) + { + if (height == vm.PixelHeight) + { + + } + else + { + width = 0; + } + } + + if (height == vm.PixelHeight) + { + if (width == vm.PixelWidth) + { + + } + else + { + height = 0; + } + } + + var file = vm.FileInfo.FullName; + uint? quality = null; + if (QualitySlider.IsEnabled) + { + quality = (uint)QualitySlider.Value; + } + var ext = vm.FileInfo.Extension; + if (!NoConversion.IsSelected) + { + if (PngItem.IsSelected) + { + ext = ".png"; + } + else if (JpgItem.IsSelected) + { + ext = ".jpg"; + } + else if (WebpItem.IsSelected) + { + ext = ".webp"; + } + else if (AvifItem.IsSelected) + { + ext = ".avif"; + } + else if (HeicItem.IsSelected) + { + ext = ".heic"; + } + else if (JxlItem.IsSelected) + { + ext = ".jxl"; + } + } + var success = await SaveImageFileHelper.SaveImageAsync(null, file, destination, width, height, quality, ext, vm.RotationAngle).ConfigureAwait(false); + if (!success) + { + // TODO: show error + return; + } + if (destination == file) + { + if (!NavigationHelper.CanNavigate(vm)) + { + return; + } + + if (vm.ImageIterator is not null) + { + await vm.ImageIterator.QuickReload().ConfigureAwait(false); + } + } + else + { + if (Path.GetDirectoryName(file) == Path.GetDirectoryName(destination)) + { + await NavigationHelper.LoadPicFromFile(destination, vm).ConfigureAwait(false); + } + } } } diff --git a/src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml b/src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml index 555f47ef..a161165d 100644 --- a/src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml +++ b/src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml @@ -256,9 +256,10 @@ Background="{DynamicResource MenuButtonColor}" Canvas.Left="7" Canvas.Top="104" - Classes="ButtonBorder altHover" + Classes="ButtonBorder altHover up" Command="{CompiledBinding StartSlideShowTask}" CommandParameter="0" + Foreground="{DynamicResource MainTextColor}" Height="46" ToolTip.Tip="{CompiledBinding Slideshow, Mode=OneWay}" diff --git a/src/PicView.Core/Config/Languages/da.json b/src/PicView.Core/Config/Languages/da.json index 23e5919a..c202063d 100644 --- a/src/PicView.Core/Config/Languages/da.json +++ b/src/PicView.Core/Config/Languages/da.json @@ -283,6 +283,7 @@ "Rotated": "Roteret", "Saturation": "Farvemætning", "Save": "Gem", + "SaveAs": "Gem som", "SaveImage": "Gem billede", "SavingFileFailed": "Gemning af fil mislykkedes", "ScrollAndRotate": "Scroll og roter", diff --git a/src/PicView.Core/Config/Languages/de.json b/src/PicView.Core/Config/Languages/de.json index ac3b0608..536ac480 100644 --- a/src/PicView.Core/Config/Languages/de.json +++ b/src/PicView.Core/Config/Languages/de.json @@ -283,6 +283,7 @@ "Rotated": "Gedreht", "Saturation": "Sättigung", "Save": "Speichern", + "SaveAs": "Speichern unter", "SaveImage": "Bild speichern", "SavingFileFailed": "Speichern der Datei fehlgeschlagen", "ScrollAndRotate": "Scrollen und drehen", diff --git a/src/PicView.Core/Config/Languages/en.json b/src/PicView.Core/Config/Languages/en.json index 62d929bb..e171cc84 100644 --- a/src/PicView.Core/Config/Languages/en.json +++ b/src/PicView.Core/Config/Languages/en.json @@ -283,6 +283,7 @@ "Rotated": "Rotated", "Saturation": "Saturation", "Save": "Save", + "SaveAs": "Save as", "SaveImage": "Save image", "SavingFileFailed": "Saving file failed", "ScrollAndRotate": "Scroll and rotate", diff --git a/src/PicView.Core/Config/Languages/es.json b/src/PicView.Core/Config/Languages/es.json index 7d5dc885..360041af 100644 --- a/src/PicView.Core/Config/Languages/es.json +++ b/src/PicView.Core/Config/Languages/es.json @@ -282,6 +282,7 @@ "Rotated": "Rotado", "Saturation": "Saturación", "Save": "Guardar", + "SaveAs": "Guardar como", "SaveImage": "Guardar Imagen", "SavingFileFailed": "Guardando archivo fallido", "ScrollAndRotate": "Desplazar y rotar", diff --git a/src/PicView.Core/Config/Languages/fr.json b/src/PicView.Core/Config/Languages/fr.json index d9d710e0..d5cd50ed 100644 --- a/src/PicView.Core/Config/Languages/fr.json +++ b/src/PicView.Core/Config/Languages/fr.json @@ -282,7 +282,8 @@ "RotateRight": "Pivoter à droite", "Rotated": "Tourné", "Saturation": "Saturation", - "Save": "Sauvegarder", + "Save": "Enregistrer", + "SaveAs": "Enregistrer sous", "SaveImage": "Enregistrer l'image", "SavingFileFailed": "La sauvegarde du fichier a échoué", "ScrollAndRotate": "Faire défiler et tourner", diff --git a/src/PicView.Core/Config/Languages/it.json b/src/PicView.Core/Config/Languages/it.json index cd9822c9..30983104 100644 --- a/src/PicView.Core/Config/Languages/it.json +++ b/src/PicView.Core/Config/Languages/it.json @@ -283,6 +283,7 @@ "Rotated": "Ruotato", "Saturation": "Saturazione", "Save": "Salva", + "SaveAs": "Salva come", "SaveImage": "Salva immagine", "SavingFileFailed": "Salvataggio del file fallito", "ScrollAndRotate": "Scorrere e ruotare", diff --git a/src/PicView.Core/Config/Languages/ko.json b/src/PicView.Core/Config/Languages/ko.json index 70311073..95cbd73e 100644 --- a/src/PicView.Core/Config/Languages/ko.json +++ b/src/PicView.Core/Config/Languages/ko.json @@ -283,6 +283,7 @@ "Rotated": "회전됨", "Saturation": "채도", "Save": "저장", + "SaveAs": "다른 이름으로 저장", "SaveImage": "이미지 저장", "SavingFileFailed": "파일 저장 실패", "ScrollAndRotate": "스크롤 및 회전", diff --git a/src/PicView.Core/Config/Languages/pl.json b/src/PicView.Core/Config/Languages/pl.json index 4c69e54d..52410a07 100644 --- a/src/PicView.Core/Config/Languages/pl.json +++ b/src/PicView.Core/Config/Languages/pl.json @@ -283,6 +283,7 @@ "Rotated": "Obrócone", "Saturation": "Nasycenie", "Save": "Zapisz", + "SaveAs": "Zapisz jako", "SaveImage": "Zapisz obraz", "SavingFileFailed": "Zapis pliku nie powiódł się", "ScrollAndRotate": "Przewijaj i obracaj", diff --git a/src/PicView.Core/Config/Languages/ro.json b/src/PicView.Core/Config/Languages/ro.json index 9d5150ca..3c45071e 100644 --- a/src/PicView.Core/Config/Languages/ro.json +++ b/src/PicView.Core/Config/Languages/ro.json @@ -283,6 +283,7 @@ "Rotated": "Rotită", "Saturation": "Saturație", "Save": "Salvare", + "SaveAs": "Salvează ca", "SaveImage": "Salvare imagine", "SavingFileFailed": "Salvarea fișierului a eșuat", "ScrollAndRotate": "Derulați și rotiți", diff --git a/src/PicView.Core/Config/Languages/ru.json b/src/PicView.Core/Config/Languages/ru.json index 5aaff001..089213e5 100644 --- a/src/PicView.Core/Config/Languages/ru.json +++ b/src/PicView.Core/Config/Languages/ru.json @@ -283,6 +283,7 @@ "Rotated": "Повернуто", "Saturation": "Насыщенность", "Save": "Сохранить", + "SaveAs": "Сохранить как", "SaveImage": "Сохранить изображение", "SavingFileFailed": "Ошибка сохранения файла", "ScrollAndRotate": "Прокрутка и вращение", diff --git a/src/PicView.Core/Config/Languages/zh-CN.json b/src/PicView.Core/Config/Languages/zh-CN.json index 5391a389..601f2c50 100644 --- a/src/PicView.Core/Config/Languages/zh-CN.json +++ b/src/PicView.Core/Config/Languages/zh-CN.json @@ -283,6 +283,7 @@ "Rotated": "已旋转", "Saturation": "饱和度", "Save": "保存", + "SaveAs": "另存为", "SaveImage": "保存图片", "SavingFileFailed": "文件保存失败", "ScrollAndRotate": "滚动和旋转", diff --git a/src/PicView.Core/Config/Languages/zh-TW.json b/src/PicView.Core/Config/Languages/zh-TW.json index d86355af..b4ce8afe 100644 --- a/src/PicView.Core/Config/Languages/zh-TW.json +++ b/src/PicView.Core/Config/Languages/zh-TW.json @@ -283,6 +283,7 @@ "Rotated": "已旋轉", "Saturation": "飽和度", "Save": "儲存", + "SaveAs": "另存新檔", "SaveImage": "儲存圖片", "SavingFileFailed": "檔案儲存失敗", "ScrollAndRotate": "捲動和旋轉", diff --git a/src/PicView.Core/Extensions/StringExtensions.cs b/src/PicView.Core/Extensions/StringExtensions.cs index eef2b6ba..515e37d4 100644 --- a/src/PicView.Core/Extensions/StringExtensions.cs +++ b/src/PicView.Core/Extensions/StringExtensions.cs @@ -1,25 +1,67 @@ -namespace PicView.Core.Extensions; +using System.Text.RegularExpressions; -public static class StringExtensions +namespace PicView.Core.Extensions; + +/// +/// Provides extension methods for the class. +/// +public static partial class StringExtensions { - public static string FirstCharToUpper(this string input) => - input switch + /// + /// Converts the first character of the string to uppercase. + /// + /// The input string. + /// A string with the first character capitalized. If the string is null or empty, an empty string is returned. + public static string FirstCharToUpper(this string input) + { + return input switch { null => string.Empty, "" => string.Empty, _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1)) }; - + } + /// - /// Shortens the given string `name` to the given `amount` and appends "..." to it. + /// Shortens the given string to the specified and appends "..." to it. /// - /// The string to shorten - /// The length to shorten the string to - /// The shortened string + /// The string to shorten. + /// The length to shorten the string to. + /// The shortened string with "..." appended at the end. public static string Shorten(this string name, int amount) { name = name[..amount]; name += "..."; return name; } + + /// + /// Extracts the percentage value from the string, if present. + /// + /// The string containing a percentage value. + /// The percentage value found in the string, or 0 if no valid percentage is found. + public static double GetPercentage(this string text) + { + foreach (Match match in PercentageRegex().Matches(text)) // Find % sign + { + if (!match.Success) + { + continue; + } + + if (double.TryParse(match.Groups[1].Value, out var percentage)) + { + return percentage; + } + } + + return 0; + } + + /// + /// A regex pattern used to match percentage values (e.g., "50%"). + /// + /// A regex pattern that matches numbers followed by a percentage sign. + [GeneratedRegex("(\\d+)%")] + private static partial Regex PercentageRegex(); } \ No newline at end of file diff --git a/src/PicView.Core/ImageDecoding/SaveImageFileHelper.cs b/src/PicView.Core/ImageDecoding/SaveImageFileHelper.cs index c1c45372..a5e823cd 100644 --- a/src/PicView.Core/ImageDecoding/SaveImageFileHelper.cs +++ b/src/PicView.Core/ImageDecoding/SaveImageFileHelper.cs @@ -44,11 +44,31 @@ public static async Task SaveImageAsync(Stream? stream, string? path, stri if (width is not null) { - magickImage.Resize(width.Value, 0); + if (height is not null) + { + if (height >= 0) + { + magickImage.Resize(0, height.Value); + } + } + else + { + magickImage.Resize(width.Value, 0); + } } else if (height is not null) { - magickImage.Resize(0, height.Value); + if (width is not null) + { + if (width >= 0) + { + magickImage.Resize(width.Value, 0); + } + } + else + { + magickImage.Resize(0, height.Value); + } } if (rotationAngle is not null) diff --git a/src/PicView.Core/Localization/LanguageModel.cs b/src/PicView.Core/Localization/LanguageModel.cs index 4b557f56..d6ac2a1f 100644 --- a/src/PicView.Core/Localization/LanguageModel.cs +++ b/src/PicView.Core/Localization/LanguageModel.cs @@ -307,6 +307,8 @@ public record LanguageModel public string? Lossless { get; set; } public string? Lossy { get; set; } public string? Quality { get; set; } + + public string? SaveAs { get; set; } public string? Percentage { get; set; } public string? GenerateThumbnails { get; set; } public string? Thumbnail { get; set; }