diff --git a/Comic_Compressor/AvifCompressor.cs b/Comic_Compressor/AvifCompressor.cs index d75a88c..32b4f70 100644 --- a/Comic_Compressor/AvifCompressor.cs +++ b/Comic_Compressor/AvifCompressor.cs @@ -1,17 +1,17 @@ -using LibHeifSharp; -using SixLabors.ImageSharp; +using ImageMagick; using ShellProgressBar; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; namespace Comic_Compressor { - internal class AvifCompressor + internal class AvifCompressor : Utils { internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount) { - LibHeifSharpDllImportResolver.Register(); - // Step 1: Get all subdirectories and store them in a list + //config + MagickFormat targetFormat = MagickFormat.Avif; + string targetExtension = ".avif"; + int targetQuality = 80; + List subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); int totalFiles = 0; @@ -26,303 +26,12 @@ namespace Comic_Compressor ProgressBarOnBottom = true }); - // Step 2: Iterate through each subdirectory in order foreach (string subdirectory in subdirectories) { - // Step 3: Process each directory - ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount); + ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount, targetExtension, targetFormat, targetQuality); } Console.WriteLine("All directories processed successfully."); } - - private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount) - { - // Get the relative path of the subdirectory - string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); - - // Create the corresponding subdirectory in the target storage path - string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); - Directory.CreateDirectory(targetSubdirectory); - - // Get all image files in the subdirectory (jpg and png) - string[] imageFiles = GetImageFiles(subdirectory); - - // Set up ParallelOptions to limit the number of concurrent threads - ParallelOptions options = new() - { - MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads - }; - - // Process each image file in parallel - Parallel.ForEach(imageFiles, options, imageFile => - { - // Set the target file path with the .avif extension - string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".avif"); - - // Check if the target file already exists - if (!File.Exists(targetFilePath)) - { - CompressImage(imageFile, targetFilePath); - - // Update progress bar safely - lock (progressBar) - { - progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); - } - } - else - { - lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } - } - }); - } - - private static string[] GetImageFiles(string directoryPath) - { - // Get all image files supported by ImageSharp - string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.gif"]; - List allFiles = []; - - foreach (string extension in supportedExtensions) - { - allFiles.AddRange(Directory.GetFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)); - } - - return [.. allFiles]; - } - - private static void CompressImage(string sourceFilePath, string targetFilePath) - { - int quality = 80; - //int quality = 70; - var format = HeifCompressionFormat.Av1; - bool saveAlphaChannel = false; - bool writeTwoProfiles = false; - - try - { - // Load the image and ensure it's in Rgb24 format - using var image = SixLabors.ImageSharp.Image.Load(sourceFilePath); - var rgbImage = image.CloneAs(); - - //// Check the longest side of the image and resize if necessary - //int maxDimension = Math.Max(rgbImage.Width, rgbImage.Height); - //if (maxDimension > 1200) - //{ - // double scaleFactor = 1200.0 / maxDimension; - // int newWidth = (int)(rgbImage.Width * scaleFactor); - // int newHeight = (int)(rgbImage.Height * scaleFactor); - // rgbImage.Mutate(x => x.Resize(newWidth, newHeight)); - //} - - // Save as AVIF format - using var context = new HeifContext(); - HeifEncoderDescriptor? encoderDescriptor = null; - - if (LibHeifInfo.HaveEncoder(format)) - { - var encoderDescriptors = context.GetEncoderDescriptors(format); - encoderDescriptor = encoderDescriptors[0]; - } - else - { - Console.WriteLine("No AV1 encoder available."); - return; - } - - using HeifEncoder encoder = context.GetEncoder(encoderDescriptor); - if (writeTwoProfiles && !LibHeifInfo.CanWriteTwoColorProfiles) - { - writeTwoProfiles = false; - Console.WriteLine($"Warning: LibHeif version {LibHeifInfo.Version} cannot write two color profiles."); - } - - using var heifImage = CreateHeifImage(rgbImage, writeTwoProfiles, out var metadata); - encoder.SetLossyQuality(quality); - - var encodingOptions = new HeifEncodingOptions - { - SaveAlphaChannel = saveAlphaChannel, - WriteTwoColorProfiles = writeTwoProfiles - }; - context.EncodeImage(heifImage, encoder, encodingOptions); - context.WriteToFile(targetFilePath); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - - private static HeifImage CreateHeifImage(Image image, - bool writeTwoColorProfiles, - out ImageMetadata metadata) - { - HeifImage? heifImage = null; - HeifImage? temp = null; - - try - { - metadata = image.Metadata; - temp = ConvertToHeifImage(image); - - if (writeTwoColorProfiles && metadata.IccProfile != null) - { - temp.IccColorProfile = new HeifIccColorProfile(metadata.IccProfile.ToByteArray()); - - temp.NclxColorProfile = new HeifNclxColorProfile(ColorPrimaries.BT709, - TransferCharacteristics.Srgb, - MatrixCoefficients.BT601, - fullRange: true); - } - else - { - if (metadata.IccProfile != null) - { - temp.IccColorProfile = new HeifIccColorProfile(metadata.IccProfile.ToByteArray()); - } - else - { - temp.NclxColorProfile = new HeifNclxColorProfile(ColorPrimaries.BT709, - TransferCharacteristics.Srgb, - MatrixCoefficients.BT601, - fullRange: true); - } - } - - heifImage = temp; - temp = null; - } - finally - { - temp?.Dispose(); - } - - return heifImage; - } - - private static HeifImage ConvertToHeifImage(Image image) - { - bool isGrayscale = IsGrayscale(image); - - var colorspace = isGrayscale ? HeifColorspace.Monochrome : HeifColorspace.Rgb; - var chroma = colorspace == HeifColorspace.Monochrome ? HeifChroma.Monochrome : HeifChroma.InterleavedRgb24; - - HeifImage? heifImage = null; - HeifImage? temp = null; - - try - { - temp = new HeifImage(image.Width, image.Height, colorspace, chroma); - - if (colorspace == HeifColorspace.Monochrome) - { - temp.AddPlane(HeifChannel.Y, image.Width, image.Height, 8); - CopyGrayscale(image, temp); - } - else - { - temp.AddPlane(HeifChannel.Interleaved, image.Width, image.Height, 8); - CopyRgb(image, temp); - } - - heifImage = temp; - temp = null; - } - finally - { - temp?.Dispose(); - } - - return heifImage; - } - - private static unsafe void CopyGrayscale(Image image, HeifImage heifImage) - { - var grayPlane = heifImage.GetPlane(HeifChannel.Y); - - byte* grayPlaneScan0 = (byte*)grayPlane.Scan0; - int grayPlaneStride = grayPlane.Stride; - - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - var src = accessor.GetRowSpan(y); - byte* dst = grayPlaneScan0 + (y * grayPlaneStride); - - for (int x = 0; x < accessor.Width; x++) - { - ref var pixel = ref src[x]; - - dst[0] = pixel.R; - - dst++; - } - } - }); - } - - private static unsafe void CopyRgb(Image image, HeifImage heifImage) - { - var interleavedData = heifImage.GetPlane(HeifChannel.Interleaved); - - byte* srcScan0 = (byte*)interleavedData.Scan0; - int srcStride = interleavedData.Stride; - - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - var src = accessor.GetRowSpan(y); - byte* dst = srcScan0 + (y * srcStride); - - for (int x = 0; x < accessor.Width; x++) - { - ref var pixel = ref src[x]; - - dst[0] = pixel.R; - dst[1] = pixel.G; - dst[2] = pixel.B; - - dst += 3; - } - } - }); - } - - private static bool IsGrayscale(Image image) - { - bool isGrayscale = true; - - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - var src = accessor.GetRowSpan(y); - - for (int x = 0; x < accessor.Width; x++) - { - ref var pixel = ref src[x]; - - if (!(pixel.R == pixel.G && pixel.G == pixel.B)) - { - isGrayscale = false; - break; - } - } - - if (!isGrayscale) - { - break; - } - } - }); - - return isGrayscale; - } - } } diff --git a/Comic_Compressor/JxlCompressor.cs b/Comic_Compressor/JxlCompressor.cs index 1d275ac..a0c7f70 100644 --- a/Comic_Compressor/JxlCompressor.cs +++ b/Comic_Compressor/JxlCompressor.cs @@ -1,15 +1,17 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Processing; +using ImageMagick; using ShellProgressBar; namespace Comic_Compressor { - internal class JxlCompressor + internal class JxlCompressor : Utils { internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount) { - // Step 1: Get all subdirectories and store them in a list + //config + MagickFormat targetFormat = MagickFormat.Jxl; + string targetExtension = ".jxl"; + int targetQuality = 90; + List subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); int totalFiles = 0; @@ -24,93 +26,12 @@ namespace Comic_Compressor ProgressBarOnBottom = true }); - // Step 2: Iterate through each subdirectory in order foreach (string subdirectory in subdirectories) { - // Step 3: Process each directory - ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount); + ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount, targetExtension, targetFormat, targetQuality); } Console.WriteLine("All directories processed successfully."); } - - private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount) - { - // Get the relative path of the subdirectory - string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); - - // Create the corresponding subdirectory in the target storage path - string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); - Directory.CreateDirectory(targetSubdirectory); - - // Get all image files in the subdirectory (jpg and png) - string[] imageFiles = GetImageFiles(subdirectory); - - // Set up ParallelOptions to limit the number of concurrent threads - ParallelOptions options = new() - { - MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads - }; - - // Process each image file in parallel - Parallel.ForEach(imageFiles, options, imageFile => - { - // Set the target file path with the .webp extension - string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp"); - - // Check if the target file already exists - if (!File.Exists(targetFilePath)) - { - CompressImage(imageFile, targetFilePath); - - // Update progress bar safely - lock (progressBar) - { - progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); - } - } - else - { - lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } - } - }); - } - - private static string[] GetImageFiles(string directoryPath) - { - // Get all image files supported by ImageSharp - string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.gif"]; - List allFiles = []; - - foreach (string extension in supportedExtensions) - { - allFiles.AddRange(Directory.GetFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)); - } - - return [.. allFiles]; - } - - private static void CompressImage(string sourceFilePath, string targetFilePath) - { - using SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(sourceFilePath); - // Check the longest side of the image and resize if necessary - int maxDimension = Math.Max(image.Width, image.Height); - if (maxDimension > 1200) - { - double scaleFactor = 1200.0 / maxDimension; - int newWidth = (int)(image.Width * scaleFactor); - int newHeight = (int)(image.Height * scaleFactor); - image.Mutate(x => x.Resize(newWidth, newHeight)); - } - - // Save the image as WebP with a quality level of 85 (for lossy compression) - var encoder = new WebpEncoder - { - Quality = 90, - FileFormat = WebpFileFormatType.Lossy - }; - - image.Save(targetFilePath, encoder); - } } } diff --git a/Comic_Compressor/LegacyFormatCompressor.cs b/Comic_Compressor/LegacyFormatCompressor.cs index e8e3e52..216154f 100644 --- a/Comic_Compressor/LegacyFormatCompressor.cs +++ b/Comic_Compressor/LegacyFormatCompressor.cs @@ -1,15 +1,19 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Processing; +using ImageMagick; using ShellProgressBar; - namespace Comic_Compressor { - internal class LegacyFormatCompressor + //Process images in legacy format(JPG,PNG) + internal class LegacyFormatCompressor : Utils { - internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount, int targetFormat) + internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount, int format) { - // Step 1: Get all subdirectories and store them in a list + //check format + //throw new NotImplementedException(); + //config + MagickFormat targetFormat = MagickFormat.Jxl; + string targetExtension = ".jxl"; + int targetQuality = 90; + List subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); int totalFiles = 0; @@ -24,93 +28,13 @@ namespace Comic_Compressor ProgressBarOnBottom = true }); - // Step 2: Iterate through each subdirectory in order foreach (string subdirectory in subdirectories) { - // Step 3: Process each directory - ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount); + ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount, targetExtension, targetFormat, targetQuality); } Console.WriteLine("All directories processed successfully."); } - private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount) - { - // Get the relative path of the subdirectory - string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); - - // Create the corresponding subdirectory in the target storage path - string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); - Directory.CreateDirectory(targetSubdirectory); - - // Get all image files in the subdirectory (jpg and png) - string[] imageFiles = GetImageFiles(subdirectory); - - // Set up ParallelOptions to limit the number of concurrent threads - ParallelOptions options = new() - { - MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads - }; - - // Process each image file in parallel - Parallel.ForEach(imageFiles, options, imageFile => - { - // Set the target file path with the .webp extension - string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp"); - - // Check if the target file already exists - if (!File.Exists(targetFilePath)) - { - CompressImage(imageFile, targetFilePath); - - // Update progress bar safely - lock (progressBar) - { - progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); - } - } - else - { - lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } - } - }); - } - - private static string[] GetImageFiles(string directoryPath) - { - // Get all image files supported by ImageSharp - string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.gif"]; - List allFiles = []; - - foreach (string extension in supportedExtensions) - { - allFiles.AddRange(Directory.GetFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)); - } - - return [.. allFiles]; - } - - private static void CompressImage(string sourceFilePath, string targetFilePath) - { - using SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(sourceFilePath); - // Check the longest side of the image and resize if necessary - int maxDimension = Math.Max(image.Width, image.Height); - if (maxDimension > 1200) - { - double scaleFactor = 1200.0 / maxDimension; - int newWidth = (int)(image.Width * scaleFactor); - int newHeight = (int)(image.Height * scaleFactor); - image.Mutate(x => x.Resize(newWidth, newHeight)); - } - - // Save the image as WebP with a quality level of 85 (for lossy compression) - var encoder = new WebpEncoder - { - Quality = 90, - FileFormat = WebpFileFormatType.Lossy - }; - - image.Save(targetFilePath, encoder); - } } } diff --git a/Comic_Compressor/LibHeifSharpDllImportResolver.cs b/Comic_Compressor/LibHeifSharpDllImportResolver.cs deleted file mode 100644 index e1960ff..0000000 --- a/Comic_Compressor/LibHeifSharpDllImportResolver.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This file is part of libheif-sharp-samples, a collection of example applications - * for libheif-sharp - * - * The MIT License (MIT) - * - * Copyright (c) 2020, 2021, 2022, 2023 Nicholas Hayes - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -using System.Reflection; -using System; -using System.Runtime.InteropServices; - -namespace Comic_Compressor -{ - internal static class LibHeifSharpDllImportResolver - { - private static IntPtr cachedLibHeifModule = IntPtr.Zero; - private static bool firstRequestForLibHeif = true; - - /// - /// Registers the for the LibHeifSharp assembly. - /// - public static void Register() - { - // The runtime will execute the specified callback when it needs to resolve a native library - // import for the LibHeifSharp assembly. - NativeLibrary.SetDllImportResolver(typeof(LibHeifSharp.LibHeifInfo).Assembly, Resolver); - } - - private static IntPtr Resolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - // We only care about a native library named libheif, the runtime will use - // its default behavior for any other native library. - if (string.Equals(libraryName, "libheif", StringComparison.Ordinal)) - { - // Because the DllImportResolver will be called multiple times we load libheif once - // and cache the module handle for future requests. - if (firstRequestForLibHeif) - { - firstRequestForLibHeif = false; - cachedLibHeifModule = LoadNativeLibrary(libraryName, assembly, searchPath); - } - - return cachedLibHeifModule; - } - - // Fall back to default import resolver. - return IntPtr.Zero; - } - - private static nint LoadNativeLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - if (OperatingSystem.IsWindows()) - { - // On Windows the libheif DLL name defaults to heif.dll, so we try to load that if - // libheif.dll was not found. - try - { - return NativeLibrary.Load(libraryName, assembly, searchPath); - } - catch (DllNotFoundException) - { - if (NativeLibrary.TryLoad("heif.dll", assembly, searchPath, out IntPtr handle)) - { - return handle; - } - else - { - throw; - } - } - } - else if (OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS()) - { - // The Apple mobile/embedded platforms statically link libheif into the AOT compiled main program binary. - return NativeLibrary.GetMainProgramHandle(); - } - else - { - // Use the default runtime behavior for all other platforms. - return NativeLibrary.Load(libraryName, assembly, searchPath); - } - } - } -} diff --git a/Comic_Compressor/MixProcessor.cs b/Comic_Compressor/MixProcessor.cs index fe148fe..734822b 100644 --- a/Comic_Compressor/MixProcessor.cs +++ b/Comic_Compressor/MixProcessor.cs @@ -1,10 +1,17 @@ -namespace Comic_Compressor +using ImageMagick; +using ShellProgressBar; +namespace Comic_Compressor { - internal class MixProcessor + internal class MixProcessor : Utils { - internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount, int targetFormat) + internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount, int format) { - // Step 1: Get all subdirectories and store them in a list + //detect mu-config + //config + MagickFormat targetFormat = MagickFormat.Jxl; + string targetExtension = ".jxl"; + int targetQuality = 90; + List subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); int totalFiles = 0; @@ -19,93 +26,12 @@ ProgressBarOnBottom = true }); - // Step 2: Iterate through each subdirectory in order foreach (string subdirectory in subdirectories) { - // Step 3: Process each directory - ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount); + ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount, targetExtension, targetFormat, targetQuality); } Console.WriteLine("All directories processed successfully."); } - - private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount) - { - // Get the relative path of the subdirectory - string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); - - // Create the corresponding subdirectory in the target storage path - string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); - Directory.CreateDirectory(targetSubdirectory); - - // Get all image files in the subdirectory (jpg and png) - string[] imageFiles = GetImageFiles(subdirectory); - - // Set up ParallelOptions to limit the number of concurrent threads - ParallelOptions options = new() - { - MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads - }; - - // Process each image file in parallel - Parallel.ForEach(imageFiles, options, imageFile => - { - // Set the target file path with the .webp extension - string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp"); - - // Check if the target file already exists - if (!File.Exists(targetFilePath)) - { - CompressImage(imageFile, targetFilePath); - - // Update progress bar safely - lock (progressBar) - { - progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); - } - } - else - { - lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } - } - }); - } - - private static string[] GetImageFiles(string directoryPath) - { - // Get all image files supported by ImageSharp - string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.gif"]; - List allFiles = []; - - foreach (string extension in supportedExtensions) - { - allFiles.AddRange(Directory.GetFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)); - } - - return [.. allFiles]; - } - - private static void CompressImage(string sourceFilePath, string targetFilePath) - { - using SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(sourceFilePath); - // Check the longest side of the image and resize if necessary - int maxDimension = Math.Max(image.Width, image.Height); - if (maxDimension > 1200) - { - double scaleFactor = 1200.0 / maxDimension; - int newWidth = (int)(image.Width * scaleFactor); - int newHeight = (int)(image.Height * scaleFactor); - image.Mutate(x => x.Resize(newWidth, newHeight)); - } - - // Save the image as WebP with a quality level of 85 (for lossy compression) - var encoder = new WebpEncoder - { - Quality = 90, - FileFormat = WebpFileFormatType.Lossy - }; - - image.Save(targetFilePath, encoder); - } } } diff --git a/Comic_Compressor/Program.cs b/Comic_Compressor/Program.cs index 5f454e6..b4d6a31 100644 --- a/Comic_Compressor/Program.cs +++ b/Comic_Compressor/Program.cs @@ -10,7 +10,7 @@ namespace Comic_Compressor Console.OutputEncoding = Encoding.UTF8; Console.WriteLine("请选择源图像所在位置:"); - string? sourceImagePath = GetFolderPath(); + string? sourceImagePath = Utils.GetFolderPath(); if (string.IsNullOrEmpty(sourceImagePath)) { Console.WriteLine("未选择文件夹,程序将退出。"); @@ -18,7 +18,7 @@ namespace Comic_Compressor } Console.WriteLine("请选择保存位置:"); - string? targetStoragePath = GetFolderPath(); + string? targetStoragePath = Utils.GetFolderPath(); if (string.IsNullOrEmpty(targetStoragePath)) { Console.WriteLine("未选择文件夹,程序将退出。"); @@ -29,11 +29,11 @@ namespace Comic_Compressor int threadCount = int.Parse(Console.ReadLine() ?? "2"); Console.WriteLine($"处理线程数设定:{threadCount}"); - Console.WriteLine("目标格式:0 - webp, 1 - avif, 2 - JXL(JPEG-XL), 3 - JPG, 4 - PNG, 5 - 保留原格式(best effort)"); + Console.WriteLine("目标格式:0 - webp, 1 - avif, 2 - JXL(JPEG-XL), 3 - JPG, 4 - PNG, 5 - BMP, 6 - 保留原格式(best effort)"); string? modeInput = Console.ReadLine(); if (modeInput == null) { - Console.WriteLine("无效输入"); + Console.WriteLine("无效格式"); return; } @@ -41,85 +41,28 @@ namespace Comic_Compressor { case "0": WebpCompressor.CompressImages(sourceImagePath, targetStoragePath, threadCount); - GetCompressorResult(sourceImagePath, targetStoragePath); + Utils.GetCompressorResult(sourceImagePath, targetStoragePath); break; case "1": AvifCompressor.CompressImages(sourceImagePath, targetStoragePath, threadCount); - GetCompressorResult(sourceImagePath, targetStoragePath); + Utils.GetCompressorResult(sourceImagePath, targetStoragePath); break; case "2": JxlCompressor.CompressImages(sourceImagePath, targetStoragePath, threadCount); - GetCompressorResult(sourceImagePath, targetStoragePath); + Utils.GetCompressorResult(sourceImagePath, targetStoragePath); break; case "3": case "4": - LegacyFormatCompressor.CompressImages(sourceImagePath, targetStoragePath, threadCount,int.Parse(modeInput)); - GetCompressorResult(sourceImagePath, targetStoragePath); - break; case "5": + LegacyFormatCompressor.CompressImages(sourceImagePath, targetStoragePath, threadCount,int.Parse(modeInput)); + Utils.GetCompressorResult(sourceImagePath, targetStoragePath); + break; + case "6": throw new NotImplementedException(); default: Console.WriteLine("不支持的格式"); break; } } - - - private static string? GetFolderPath() - { - using var dialog = new FolderBrowserDialog(); - dialog.ShowNewFolderButton = false; - - DialogResult result = dialog.ShowDialog(); - - if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.SelectedPath)) - { - return dialog.SelectedPath; - } - else - { - return null; - } - } - - private static long GetDirectorySize(string path) - { - long size = 0; - - foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) - { - FileInfo fileInfo = new(file); - size += fileInfo.Length; - } - - return size; - } - - private static string GetHumanReadableSize(long size) - { - string[] sizes = { "B", "KB", "MB", "GB", "TB" }; - double len = size; - int order = 0; - while (len >= 1024 && order < sizes.Length - 1) - { - order++; - len = len / 1024; - } - - return $"{len:0.##} {sizes[order]}"; - } - - - private static void GetCompressorResult(string source, string target) - { - long sourceSize = GetDirectorySize(source); - long targetSize = GetDirectorySize(target); - double reduced = (sourceSize - targetSize) * 1.0 / sourceSize; - - Console.WriteLine($"压缩前大小:{GetHumanReadableSize(sourceSize)}"); - Console.WriteLine($"压缩后大小:{GetHumanReadableSize(targetSize)}"); - Console.WriteLine($"体积已减少{reduced:P}"); - } - } } diff --git a/Comic_Compressor/Utils.cs b/Comic_Compressor/Utils.cs new file mode 100644 index 0000000..331ef26 --- /dev/null +++ b/Comic_Compressor/Utils.cs @@ -0,0 +1,138 @@ +using ImageMagick; +using System.Collections.Concurrent; + +namespace Comic_Compressor +{ + internal class Utils + { + // Compress an image file + // Resize the image if its max dimension is larger than 1200 + // Set the quality of the compressed image + public static void CompressImage(string sourceFilePath, string targetFilePath, MagickFormat mFormat, int quality) + { + using MagickImage image = new(sourceFilePath); + + uint maxDimension = image.Width > image.Height ? image.Width : image.Height; + if (maxDimension > 1200) + { + double scaleFactor = 1200.0 / maxDimension; + uint newWidth = (uint)(image.Width * scaleFactor); + uint newHeight = (uint)(image.Height * scaleFactor); + image.Resize(newWidth, newHeight); + } + image.Quality = (uint)quality; + + image.Write(targetFilePath, mFormat); + } + + // Get all image files in a directory with supported extensions + public static string[] GetImageFiles(string directoryPath) + { + string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.jxl", "*.avif", "*.webp"]; + ConcurrentBag allFiles = []; + + foreach (string extension in supportedExtensions) + { + foreach (var file in Directory.EnumerateFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)) + { + allFiles.Add(file); + } + } + return [.. allFiles]; + } + + // Process all image files in a directory + public static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount, string format, MagickFormat mFormat, int quality) + { + string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); + + string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); + Directory.CreateDirectory(targetSubdirectory); + + string[] imageFiles = GetImageFiles(subdirectory); + + ParallelOptions options = new() + { + MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads + }; + + Parallel.ForEach(imageFiles, options, imageFile => + { + string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + format); + + if (!File.Exists(targetFilePath)) + { + CompressImage(imageFile, targetFilePath, mFormat, quality); + + lock (progressBar) + { + progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); + } + } + else + { + lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } + } + }); + } + + // Display the compression result + public static void GetCompressorResult(string source, string target) + { + long sourceSize = GetDirectorySize(source); + long targetSize = GetDirectorySize(target); + double reduced = (sourceSize - targetSize) * 1.0 / sourceSize; + + Console.WriteLine($"压缩前大小:{GetHumanReadableSize(sourceSize)}"); + Console.WriteLine($"压缩后大小:{GetHumanReadableSize(targetSize)}"); + Console.WriteLine($"体积已减少{reduced:P}"); + } + + // Get the size of a directory + public static long GetDirectorySize(string path) + { + long size = 0; + + foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) + { + FileInfo fileInfo = new(file); + size += fileInfo.Length; + } + + return size; + } + + // Get the path of a folder via a dialog + public static string? GetFolderPath() + { + using var dialog = new FolderBrowserDialog(); + dialog.ShowNewFolderButton = false; + + DialogResult result = dialog.ShowDialog(); + + if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.SelectedPath)) + { + return dialog.SelectedPath; + } + else + { + return null; + } + } + + // Get the human-readable size of a file + public static string GetHumanReadableSize(long size) + { + string[] sizes = ["B", "KB", "MB", "GB", "TB"]; + double len = size; + int order = 0; + while (len >= 1024 && order < sizes.Length - 1) + { + order++; + len /= 1024; + } + + return $"{len:0.##} {sizes[order]}"; + } + } +} \ No newline at end of file diff --git a/Comic_Compressor/WebpCompressor.cs b/Comic_Compressor/WebpCompressor.cs index 57a0e1e..ecbdd5f 100644 --- a/Comic_Compressor/WebpCompressor.cs +++ b/Comic_Compressor/WebpCompressor.cs @@ -1,15 +1,17 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Processing; +using ImageMagick; using ShellProgressBar; namespace Comic_Compressor { - internal class WebpCompressor + internal class WebpCompressor : Utils { internal static void CompressImages(string sourceImagePath, string targetStoragePath, int threadCount) { - // Step 1: Get all subdirectories and store them in a list + //config + MagickFormat targetFormat = MagickFormat.WebP; + string targetExtension = ".webp"; + int targetQuality = 90; + List subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); int totalFiles = 0; @@ -24,93 +26,12 @@ namespace Comic_Compressor ProgressBarOnBottom = true }); - // Step 2: Iterate through each subdirectory in order foreach (string subdirectory in subdirectories) { - // Step 3: Process each directory - ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount); + ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar, threadCount, targetExtension, targetFormat, targetQuality); } Console.WriteLine("All directories processed successfully."); } - - private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar, int threadCount) - { - // Get the relative path of the subdirectory - string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); - - // Create the corresponding subdirectory in the target storage path - string targetSubdirectory = Path.Combine(targetStoragePath, relativePath); - Directory.CreateDirectory(targetSubdirectory); - - // Get all image files in the subdirectory (jpg and png) - string[] imageFiles = GetImageFiles(subdirectory); - - // Set up ParallelOptions to limit the number of concurrent threads - ParallelOptions options = new() - { - MaxDegreeOfParallelism = threadCount // Adjust this value to set the number of concurrent threads - }; - - // Process each image file in parallel - Parallel.ForEach(imageFiles, options, imageFile => - { - // Set the target file path with the .webp extension - string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp"); - - // Check if the target file already exists - if (!File.Exists(targetFilePath)) - { - CompressImage(imageFile, targetFilePath); - - // Update progress bar safely - lock (progressBar) - { - progressBar.Tick($"Processed {Path.GetFileName(imageFile)}"); - } - } - else - { - lock (progressBar) { progressBar.Tick($"Skipped {Path.GetFileName(imageFile)}"); } - } - }); - } - - private static string[] GetImageFiles(string directoryPath) - { - // Get all image files supported by ImageSharp - string[] supportedExtensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.tiff", "*.gif"]; - List allFiles = []; - - foreach (string extension in supportedExtensions) - { - allFiles.AddRange(Directory.GetFiles(directoryPath, extension, SearchOption.TopDirectoryOnly)); - } - - return [.. allFiles]; - } - - private static void CompressImage(string sourceFilePath, string targetFilePath) - { - using SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(sourceFilePath); - // Check the longest side of the image and resize if necessary - int maxDimension = Math.Max(image.Width, image.Height); - if (maxDimension > 1200) - { - double scaleFactor = 1200.0 / maxDimension; - int newWidth = (int)(image.Width * scaleFactor); - int newHeight = (int)(image.Height * scaleFactor); - image.Mutate(x => x.Resize(newWidth, newHeight)); - } - - // Save the image as WebP with a quality level of 85 (for lossy compression) - var encoder = new WebpEncoder - { - Quality = 90, - FileFormat = WebpFileFormatType.Lossy - }; - - image.Save(targetFilePath, encoder); - } } }