Skip to content

Commit

Permalink
Batch resize - Resize and Compression #165
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruben2776 committed Dec 5, 2024
1 parent 5e422b3 commit 5fee57b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 73 deletions.
4 changes: 3 additions & 1 deletion src/PicView.Avalonia/Views/BatchResizeView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@
Margin="10,0,0,3"
Text="{Binding Path=Value, ElementName=QualitySlider}" />


</StackPanel>

<StackPanel Margin="0,10" Orientation="Horizontal">
Expand Down Expand Up @@ -894,6 +893,9 @@
</StackPanel>





<Border
Background="{DynamicResource TertiaryBackgroundColor}"
BorderBrush="{DynamicResource MainBorderColor}"
Expand Down
170 changes: 113 additions & 57 deletions src/PicView.Avalonia/Views/BatchResizeView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using Avalonia.Controls;
using Avalonia.Threading;
using ImageMagick;
using PicView.Avalonia.FileSystem;
using PicView.Avalonia.Navigation;
using PicView.Avalonia.ViewModels;
using PicView.Core.FileHandling;
using PicView.Core.ImageDecoding;
using PicView.Core.Localization;

namespace PicView.Avalonia.Views;

public partial class BatchResizeView : UserControl
{

private bool _isKeepingAspectRatio;

public BatchResizeView()
{
InitializeComponent();
Expand All @@ -23,7 +27,8 @@ public BatchResizeView()
SourceFolderTextBox.TextChanged += delegate { CheckIfValidDirectory(SourceFolderTextBox.Text); };
SourceFolderTextBox.TextChanged += delegate
{
OutputFolderTextBox.Text = Path.Combine(SourceFolderTextBox.Text ?? string.Empty, TranslationHelper.Translation.BatchResize);
OutputFolderTextBox.Text = Path.Combine(SourceFolderTextBox.Text ?? string.Empty,
TranslationHelper.Translation.BatchResize);
};

SourceFolderButton.Click += async delegate
Expand All @@ -36,7 +41,7 @@ public BatchResizeView()

SourceFolderTextBox.Text = directory;
};

OutputFolderButton.Click += async delegate
{
var directory = await FilePicker.SelectDirectory();
Expand All @@ -63,17 +68,15 @@ public BatchResizeView()
UnlinkChainImage.IsVisible = true;
}
};

StartButton.Click += async (_, _) => await StartBatchResize();

if (!NavigationHelper.CanNavigate(vm))
{
return;
}

SourceFolderTextBox.Text = vm.FileInfo?.DirectoryName ?? string.Empty;


SourceFolderTextBox.Text = vm.FileInfo?.DirectoryName ?? string.Empty;
};
}

Expand All @@ -83,63 +86,117 @@ private async Task StartBatchResize()
{
return;
}
var file = vm.FileInfo.FullName;
var ext = vm.FileInfo.Extension;
var destination = string.IsNullOrWhiteSpace(OutputFolderTextBox.Text) ? SourceFolderTextBox.Text : OutputFolderTextBox.Text;
uint width = 0, height = 0;
if (!NoConversion.IsSelected)

var files = await Task.FromResult(FileListHelper.RetrieveFiles(new FileInfo(SourceFolderTextBox.Text)));

if (!Directory.Exists(OutputFolderTextBox.Text))
{
if (PngItem.IsSelected)
{
ext = ".png";
destination = Path.ChangeExtension(destination, ".png");
}
else if (JpgItem.IsSelected)
{
ext = ".jpg";
destination = Path.ChangeExtension(destination, ".jpg");
}
else if (WebpItem.IsSelected)
{
ext = ".webp";
destination = Path.ChangeExtension(destination, ".webp");
}
else if (AvifItem.IsSelected)
Directory.CreateDirectory(OutputFolderTextBox.Text);
}

var outputFolder = string.IsNullOrWhiteSpace(OutputFolderTextBox.Text)
? SourceFolderTextBox.Text
: OutputFolderTextBox.Text;

var toConvert = !NoConversion.IsSelected;
var pngSelected = PngItem.IsSelected;
var jpgSelected = JpgItem.IsSelected;
var webpSelected = WebpItem.IsSelected;
var avifSelected = AvifItem.IsSelected;
var heicSelected = HeicItem.IsSelected;
var jxlSelected = JxlItem.IsSelected;

var qualityEnabled = IsQualityEnabledBox.IsChecked.HasValue && IsQualityEnabledBox.IsChecked.Value;
var qualityValue = (uint)QualitySlider.Value;

var losslessCompress = Lossless.IsSelected;
var lossyCompress = Lossy.IsSelected;

Percentage? percentage = null;
if (PercentageResizeBox.IsSelected)
{
if (int.TryParse(PercentageValueBox.Text, out var percentageValue))
{
ext = ".avif";
destination = Path.ChangeExtension(destination, ".avif");
percentage = new Percentage(percentageValue);
}
else if (HeicItem.IsSelected)
}

ProgressBar.Maximum = files.Count();

await Parallel.ForEachAsync(files, async (file, token) =>
{
var ext = Path.GetExtension(file);

var destination = Path.Combine(outputFolder, Path.GetFileName(file));
uint width = 0, height = 0;
if (toConvert)
{
ext = ".heic";
destination = Path.ChangeExtension(destination, ".heic");
if (pngSelected)
{
ext = ".png";
destination = Path.ChangeExtension(destination, ".png");
}
else if (jpgSelected)
{
ext = ".jpg";
destination = Path.ChangeExtension(destination, ".jpg");
}
else if (webpSelected)
{
ext = ".webp";
destination = Path.ChangeExtension(destination, ".webp");
}
else if (avifSelected)
{
ext = ".avif";
destination = Path.ChangeExtension(destination, ".avif");
}
else if (heicSelected)
{
ext = ".heic";
destination = Path.ChangeExtension(destination, ".heic");
}
else if (jxlSelected)
{
ext = ".jxl";
destination = Path.ChangeExtension(destination, ".jxl");
}
}
else if (JxlItem.IsSelected)

uint? quality = null;
if (qualityEnabled)
{
ext = ".jxl";
destination = Path.ChangeExtension(destination, ".jxl");
if (ext.Equals(".jpg", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(destination).Equals(".jpg", StringComparison.OrdinalIgnoreCase) ||
Path.GetExtension(destination).Equals(".jpeg", StringComparison.OrdinalIgnoreCase) ||
ext.Equals(".png", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(destination).Equals(".png", StringComparison.OrdinalIgnoreCase))
{
quality = qualityValue;
}
}
}
var success = await SaveImageFileHelper.SaveImageAsync(null,
file,
destination,
width,
height,
quality,
ext,
null,
percentage,
losslessCompress,
lossyCompress,
_isKeepingAspectRatio).ConfigureAwait(false);

uint? quality = null;
if (QualitySlider.IsEnabled)
{
if (ext == ".jpg" || Path.GetExtension(destination) == ".jpg" || Path.GetExtension(destination) == ".jpeg")
if (success)
{
quality = (uint)QualitySlider.Value;
#if DEBUG
Console.WriteLine($"Saved {file} to {destination}");
#endif
await Dispatcher.UIThread.InvokeAsync(() =>
{
ProgressBar.Value++;
});
}
}

return;
var success = await SaveImageFileHelper.SaveImageAsync(null,
file,
destination,
width,
height,
quality,
ext,
null,
_isKeepingAspectRatio).ConfigureAwait(false);
});
}

private void CheckIfValidDirectory(string path)
Expand All @@ -151,6 +208,5 @@ private void CheckIfValidDirectory(string path)
}

StartButton.IsEnabled = true;

}
}
}
1 change: 1 addition & 0 deletions src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ private async Task DoSaveImage(MainViewModel vm, string destination)
quality,
ext,
rotationAngle,
null,
_isKeepingAspectRatio).ConfigureAwait(false);
await Dispatcher.UIThread.InvokeAsync(() =>
{
Expand Down
40 changes: 25 additions & 15 deletions src/PicView.Core/ImageDecoding/SaveImageFileHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ public static class SaveImageFileHelper
public static async Task<bool> SaveImageAsync(Stream? stream, string? path, string? destination = null,
uint? width = null,
uint? height = null, uint? quality = null, string? ext = null, double? rotationAngle = null,
bool respectAspectRatio = true)
Percentage? percentage = null, bool losslessCompress = false, bool lossyCompress = false, bool respectAspectRatio = true)
{
try
{
using MagickImage magickImage = new();
string writtenFile;

if (stream is not null)
{
Expand All @@ -45,7 +46,11 @@ public static async Task<bool> SaveImageAsync(Stream? stream, string? path, stri
magickImage.Quality = quality.Value;
}

if (width is not null)
if (percentage.HasValue)
{
magickImage.Resize(percentage.Value);
}
else if (width is not null)
{
if (height is not null)
{
Expand All @@ -62,10 +67,6 @@ public static async Task<bool> SaveImageAsync(Stream? stream, string? path, stri
magickImage.Resize(width > 0 ? width.Value : 0, height.Value);
}
}
else
{
magickImage.Resize(width.Value, 0);
}
}
else
{
Expand All @@ -89,10 +90,6 @@ public static async Task<bool> SaveImageAsync(Stream? stream, string? path, stri
magickImage.Resize(width.Value, height > 0 ? height.Value : 0);
}
}
else
{
magickImage.Resize(0, height.Value);
}
}
else
{
Expand Down Expand Up @@ -120,22 +117,35 @@ public static async Task<bool> SaveImageAsync(Stream? stream, string? path, stri
_ => magickImage.Format
};
}



if (destination is not null)
{
await magickImage.WriteAsync(!keepExt ? Path.ChangeExtension(destination, ext) : destination)
.ConfigureAwait(false);
writtenFile = destination;
}
else if (path is not null)
{
await magickImage.WriteAsync(!keepExt ? Path.ChangeExtension(path, ext) : path)
.ConfigureAwait(false);
writtenFile = path;
}
else
{
return false;
}

if (lossyCompress || losslessCompress)
{
ImageOptimizer imageOptimizer = new()
{
OptimalCompression = losslessCompress
};
if (imageOptimizer.IsSupported(writtenFile))
{
imageOptimizer.Compress(writtenFile);
}
}
}
catch (Exception exception)
{
Expand All @@ -161,7 +171,7 @@ await magickImage.WriteAsync(!keepExt ? Path.ChangeExtension(path, ext) : path)
/// <param name="compress">Indicates whether to apply optimal compression to the image after resizing. If null, no compression is applied.</param>
/// <param name="ext">The file extension of the output image (e.g., ".jpg", ".png"). If null, the original extension is kept.</param>
/// <returns>True if the image is resized and saved successfully; otherwise, false.</returns>
public static async Task<bool> ResizeImageAsync(FileInfo fileInfo, uint width, uint height, uint quality = 100,
public static async Task<bool> ResizeImageAsync(FileInfo fileInfo, uint width, uint height, uint? quality = 100,
Percentage? percentage = null, string? destination = null, bool? compress = null, string? ext = null)
{
if (fileInfo.Exists == false)
Expand All @@ -174,9 +184,9 @@ public static async Task<bool> ResizeImageAsync(FileInfo fileInfo, uint width, u
ColorSpace = ColorSpace.Transparent
};

if (quality > 0) // not inputting quality results in lower file size
if (quality.HasValue) // not inputting quality results in lower file size
{
magick.Quality = quality;
magick.Quality = quality.Value;
}

try
Expand Down

0 comments on commit 5fee57b

Please sign in to comment.