This commit is contained in:
Chenx221 2024-08-27 14:22:23 +08:00
parent 3860b1bd54
commit 3c645dc893
9 changed files with 502 additions and 16 deletions

View File

@ -1,8 +1,9 @@
using System; using LibHeifSharp;
using System.Collections.Generic; using SixLabors.ImageSharp;
using System.Linq; using SixLabors.ImageSharp.Processing;
using System.Text; using ShellProgressBar;
using System.Threading.Tasks; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace Comic_Compressor namespace Comic_Compressor
{ {
@ -10,8 +11,319 @@ namespace Comic_Compressor
{ {
internal static void CompressImages(string sourceImagePath, string targetStoragePath) internal static void CompressImages(string sourceImagePath, string targetStoragePath)
{ {
//尚未实现 LibHeifSharpDllImportResolver.Register();
throw new NotImplementedException(); // Step 1: Get all subdirectories and store them in a list
List<string> subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories));
int totalFiles = 0;
foreach (string subdirectory in subdirectories)
{
totalFiles += GetImageFiles(subdirectory).Length;
}
using var progressBar = new ShellProgressBar.ProgressBar(totalFiles, "Compressing images", new ProgressBarOptions
{
ProgressCharacter = '─',
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);
}
Console.WriteLine("All directories processed successfully.");
} }
private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar)
{
// 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 = 2 // 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"];
List<string> 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<Rgb24>();
//// 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<Rgb24> 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<Rgb24> 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<Rgb24> 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<Rgb24> 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<Rgb24> 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;
}
} }
} }

View File

@ -7,11 +7,46 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LibHeifSharp" Version="3.2.0" /> <PackageReference Include="LibHeifSharp" Version="3.2.0" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="aom.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="dav1d.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="libde265.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="libheif.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="libs\aom.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="libs\dav1d.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="libs\libde265.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="libs\libheif.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="libs\libx265.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="libx265.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,105 @@
/*
* 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;
/// <summary>
/// Registers the <see cref="DllImportResolver"/> for the LibHeifSharp assembly.
/// </summary>
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);
}
}
}
}

View File

@ -1,6 +1,7 @@
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using ShellProgressBar;
namespace Comic_Compressor namespace Comic_Compressor
{ {
@ -11,15 +12,29 @@ namespace Comic_Compressor
// Step 1: Get all subdirectories and store them in a list // Step 1: Get all subdirectories and store them in a list
List<string> subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories)); List<string> subdirectories = new(Directory.GetDirectories(sourceImagePath, "*", SearchOption.AllDirectories));
int totalFiles = 0;
foreach (string subdirectory in subdirectories)
{
totalFiles += GetImageFiles(subdirectory).Length;
}
using var progressBar = new ShellProgressBar.ProgressBar(totalFiles, "Compressing images", new ProgressBarOptions
{
ProgressCharacter = '─',
ProgressBarOnBottom = true
});
// Step 2: Iterate through each subdirectory in order // Step 2: Iterate through each subdirectory in order
foreach (string subdirectory in subdirectories) foreach (string subdirectory in subdirectories)
{ {
// Step 3: Process each directory // Step 3: Process each directory
ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath); ProcessDirectory(subdirectory, sourceImagePath, targetStoragePath, progressBar);
} }
Console.WriteLine("All directories processed successfully.");
} }
private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath) private static void ProcessDirectory(string subdirectory, string sourceImagePath, string targetStoragePath, ShellProgressBar.ProgressBar progressBar)
{ {
// Get the relative path of the subdirectory // Get the relative path of the subdirectory
string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory); string relativePath = Path.GetRelativePath(sourceImagePath, subdirectory);
@ -31,15 +46,34 @@ namespace Comic_Compressor
// Get all image files in the subdirectory (jpg and png) // Get all image files in the subdirectory (jpg and png)
string[] imageFiles = GetImageFiles(subdirectory); string[] imageFiles = GetImageFiles(subdirectory);
// Iterate through each image file // Set up ParallelOptions to limit the number of concurrent threads
foreach (string imageFile in imageFiles) ParallelOptions options = new()
{
MaxDegreeOfParallelism = 2 // 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 // Set the target file path with the .webp extension
string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp"); string targetFilePath = Path.Combine(targetSubdirectory, Path.GetFileNameWithoutExtension(imageFile) + ".webp");
CompressImage(imageFile, targetFilePath);
}
Console.WriteLine($"{Path.GetFileName(subdirectory)} processed successfully."); // 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) private static string[] GetImageFiles(string directoryPath)
@ -69,10 +103,10 @@ namespace Comic_Compressor
image.Mutate(x => x.Resize(newWidth, newHeight)); image.Mutate(x => x.Resize(newWidth, newHeight));
} }
// Save the image as WebP with a quality level of 70 (for lossy compression) // Save the image as WebP with a quality level of 85 (for lossy compression)
var encoder = new WebpEncoder var encoder = new WebpEncoder
{ {
Quality = 70, Quality = 90,
FileFormat = WebpFileFormatType.Lossy FileFormat = WebpFileFormatType.Lossy
}; };

BIN
Comic_Compressor/aom.dll Normal file

Binary file not shown.

BIN
Comic_Compressor/dav1d.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.