Skip to content

Instantly share code, notes, and snippets.

@AldeRoberge
Created March 19, 2026 12:54
Show Gist options
  • Select an option

  • Save AldeRoberge/da5033e349802fc2c1bc95cb5f66667b to your computer and use it in GitHub Desktop.

Select an option

Save AldeRoberge/da5033e349802fc2c1bc95cb5f66667b to your computer and use it in GitHub Desktop.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp;
namespace ImageSharp.Converter;
/// <summary>
/// Converts PNG images to WebP format using ImageSharp library.
/// Scans a target directory (defaults to current working directory) recursively.
/// </summary>
abstract class ConvertToWebP
{
private static int _quality = 85;
private static bool _force = false;
private static string? _targetPath = null;
private static readonly List<string> _skipFolders = [];
private static readonly List<string> _skipPrefixes = [];
static async Task<int> Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("PNG to WebP Converter using ImageSharp");
Console.WriteLine("======================================\n");
Console.ResetColor();
ParseArguments(args);
// Resolve target directory: explicit arg > cwd
var searchPath = _targetPath ?? Directory.GetCurrentDirectory();
if (!Directory.Exists(searchPath))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: Target directory not found: {searchPath}");
Console.ResetColor();
return 1;
}
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Searching for PNG files in: {searchPath}");
Console.WriteLine($"Quality: {_quality}");
if (_skipFolders.Count > 0)
Console.WriteLine($"Skipping folders: {string.Join(", ", _skipFolders)}");
if (_skipPrefixes.Count > 0)
Console.WriteLine($"Skipping file prefixes: {string.Join(", ", _skipPrefixes)}");
Console.WriteLine();
Console.ResetColor();
var pngFiles = Directory.GetFiles(searchPath, "*.png", SearchOption.AllDirectories);
var filesToConvert = FilterFiles(pngFiles, searchPath);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Found {filesToConvert.Count} PNG file(s) to convert\n");
Console.ResetColor();
int converted = 0;
int skipped = 0;
int failed = 0;
foreach (var pngFile in filesToConvert)
{
var webpFile = Path.ChangeExtension(pngFile, ".webp");
var relativePath = pngFile[searchPath.Length..];
if (File.Exists(webpFile) && !_force)
{
if (File.GetLastWriteTime(webpFile) > File.GetLastWriteTime(pngFile))
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Skipping (up-to-date): {relativePath}");
Console.ResetColor();
skipped++;
continue;
}
}
Console.WriteLine($"Converting: {relativePath}");
try
{
var originalSize = new FileInfo(pngFile).Length;
using (var image = await Image.LoadAsync(pngFile))
{
var encoder = new WebpEncoder
{
Quality = _quality,
Method = WebpEncodingMethod.BestQuality,
FileFormat = WebpFileFormatType.Lossy
};
await image.SaveAsWebpAsync(webpFile, encoder);
}
var webpSize = new FileInfo(webpFile).Length;
var savings = Math.Round((1 - (webpSize / (double)originalSize)) * 100, 1);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($" ✓ {savings}% saved ({FormatFileSize(originalSize)}{FormatFileSize(webpSize)})");
Console.ResetColor();
converted++;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($" ✗ Error: {ex.Message}");
Console.ResetColor();
failed++;
}
}
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("\n========================================");
Console.WriteLine("Conversion Summary:");
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($" Converted: {converted}");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" Skipped (up-to-date): {skipped}");
Console.ForegroundColor = failed > 0 ? ConsoleColor.Red : ConsoleColor.Gray;
Console.WriteLine($" Failed: {failed}");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("========================================\n");
Console.ResetColor();
return failed > 0 ? 1 : 0;
}
private static List<string> FilterFiles(string[] pngFiles, string basePath)
{
var result = new List<string>();
foreach (var pngFile in pngFiles)
{
var relativePath = pngFile[basePath.Length..];
var fileName = Path.GetFileName(pngFile);
// Skip folders passed via --skip-folder
var skipFolder = _skipFolders.Any(folder =>
relativePath.Contains(Path.DirectorySeparatorChar + folder + Path.DirectorySeparatorChar,
StringComparison.OrdinalIgnoreCase));
if (skipFolder)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"Skipping (excluded folder): {relativePath}");
Console.ResetColor();
continue;
}
// Skip filenames matching --skip-prefix
var skipPrefix = _skipPrefixes.Any(prefix =>
fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
if (skipPrefix)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"Skipping (excluded prefix): {relativePath}");
Console.ResetColor();
continue;
}
result.Add(pngFile);
}
return result;
}
private static void ParseArguments(string[] args)
{
foreach (var arg in args)
{
if (arg.StartsWith("--quality=") || arg.StartsWith("-q="))
{
var value = arg.Split('=')[1];
if (int.TryParse(value, out var quality))
_quality = Math.Clamp(quality, 0, 100);
}
else if (arg.StartsWith("--path=") || arg.StartsWith("-p="))
{
_targetPath = arg.Split('=', 2)[1];
}
else if (arg.StartsWith("--skip-folder="))
{
_skipFolders.Add(arg.Split('=', 2)[1]);
}
else if (arg.StartsWith("--skip-prefix="))
{
_skipPrefixes.Add(arg.Split('=', 2)[1]);
}
else if (arg == "--force" || arg == "-f")
{
_force = true;
}
else if (arg == "--help" || arg == "-h")
{
ShowHelp();
Environment.Exit(0);
}
}
}
private static void ShowHelp()
{
Console.WriteLine("Usage: dotnet run [options]");
Console.WriteLine("\nOptions:");
Console.WriteLine(" --path=<dir>, -p=<dir> Directory to scan (default: current working directory)");
Console.WriteLine(" --quality=<0-100>, -q=<0-100> WebP quality (default: 85)");
Console.WriteLine(" --force, -f Force reconversion of up-to-date files");
Console.WriteLine(" --skip-folder=<name> Skip a folder by name (repeatable)");
Console.WriteLine(" --skip-prefix=<prefix> Skip files whose name starts with prefix (repeatable)");
Console.WriteLine(" --help, -h Show this help message");
Console.WriteLine("\nExamples:");
Console.WriteLine(" dotnet run");
Console.WriteLine(" dotnet run --path=C:\\MyProject\\wwwroot");
Console.WriteLine(" dotnet run --quality=90 --force");
Console.WriteLine(" dotnet run --skip-folder=unity --skip-folder=vendor");
Console.WriteLine(" dotnet run --skip-prefix=favicon --skip-prefix=apple-touch-icon");
}
private static string FormatFileSize(long size)
{
if (size > 1024 * 1024)
return $"{size / (1024.0 * 1024.0):N2} MB";
if (size > 1024)
return $"{size / 1024.0:N2} KB";
return $"{size} bytes";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment