using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using System.Globalization; using Foxel.Models; using Foxel.Models.Enums; namespace Foxel.Utils; /// /// 图片处理工具类 /// public static class ImageHelper { /// /// 创建缩略图 /// /// 原始图片路径 /// 缩略图保存路径 /// 缩略图最大宽度 /// 压缩质量(1-100) /// 生成的缩略图的文件大小(字节) public static async Task CreateThumbnailAsync(string originalPath, string thumbnailPath, int maxWidth, int quality = 75) { // 获取原始文件大小 var originalFileInfo = new FileInfo(originalPath); long originalSize = originalFileInfo.Length; using var image = await Image.LoadAsync(originalPath); image.Metadata.ExifProfile = null; image.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(maxWidth, 0), Mode = ResizeMode.Max })); string webpThumbnailPath = Path.ChangeExtension(thumbnailPath, ".webp"); int adjustedQuality = AdjustQualityByFileSize(originalSize, ".webp", quality); await image.SaveAsWebpAsync(webpThumbnailPath, new SixLabors.ImageSharp.Formats.Webp.WebpEncoder { Quality = adjustedQuality, Method = SixLabors.ImageSharp.Formats.Webp.WebpEncodingMethod.BestQuality }); var thumbnailFileInfo = new FileInfo(webpThumbnailPath); if (thumbnailFileInfo.Length < originalSize) return thumbnailFileInfo.Length; await image.SaveAsWebpAsync(webpThumbnailPath, new SixLabors.ImageSharp.Formats.Webp.WebpEncoder { Quality = Math.Max(adjustedQuality - 15, 50), Method = SixLabors.ImageSharp.Formats.Webp.WebpEncodingMethod.BestQuality }); thumbnailFileInfo = new FileInfo(webpThumbnailPath); return thumbnailFileInfo.Length; } /// /// 根据原始文件大小调整质量参数 /// private static int AdjustQualityByFileSize(long originalSize, string extension, int baseQuality) { if (extension == ".webp") { if (originalSize > 10 * 1024 * 1024) // 10MB return Math.Min(baseQuality, 70); else if (originalSize > 5 * 1024 * 1024) // 5MB return Math.Min(baseQuality, 75); else if (originalSize > 1 * 1024 * 1024) // 1MB return Math.Min(baseQuality, 80); } else if (extension == ".jpg" || extension == ".jpeg") { if (originalSize > 10 * 1024 * 1024) // 10MB return Math.Min(baseQuality, 65); else if (originalSize > 5 * 1024 * 1024) // 5MB return Math.Min(baseQuality, 70); else if (originalSize > 1 * 1024 * 1024) // 1MB return Math.Min(baseQuality, 75); } return baseQuality; } /// /// 将图片转换为Base64编码 /// /// 图片路径 /// Base64编码字符串 public static async Task ConvertImageToBase64(string imagePath) { byte[] imageBytes = await File.ReadAllBytesAsync(imagePath); return Convert.ToBase64String(imageBytes); } /// /// 提取图片的EXIF信息 /// /// 图片路径 /// EXIF信息对象 public static async Task ExtractExifInfoAsync(string imagePath) { var exifInfo = new ExifInfo(); try { // 确保文件存在 if (!File.Exists(imagePath)) { exifInfo.ErrorMessage = "找不到图片文件"; return exifInfo; } // 使用ImageSharp读取EXIF信息 using var image = await Image.LoadAsync(imagePath); var exifProfile = image.Metadata.ExifProfile; // 添加基本图像信息 exifInfo.Width = image.Width; exifInfo.Height = image.Height; if (exifProfile != null) { // 提取相机信息 if (exifProfile.TryGetValue(ExifTag.Make, out var make)) exifInfo.CameraMaker = make.Value; if (exifProfile.TryGetValue(ExifTag.Model, out var model)) exifInfo.CameraModel = model.Value; if (exifProfile.TryGetValue(ExifTag.Software, out var software)) exifInfo.Software = software.Value; // 提取拍摄参数 if (exifProfile.TryGetValue(ExifTag.ExposureTime, out var exposureTime)) exifInfo.ExposureTime = exposureTime.Value.ToString(); if (exifProfile.TryGetValue(ExifTag.FNumber, out var fNumber)) exifInfo.Aperture = $"f/{fNumber.Value}"; if (exifProfile.TryGetValue(ExifTag.ISOSpeedRatings, out var iso)) { if (iso.Value is { Length: > 0 } isoArray) { exifInfo.IsoSpeed = isoArray[0].ToString(); } else { exifInfo.IsoSpeed = iso.Value?.ToString(); } } if (exifProfile.TryGetValue(ExifTag.FocalLength, out var focalLength)) exifInfo.FocalLength = $"{focalLength.Value}mm"; if (exifProfile.TryGetValue(ExifTag.Flash, out var flash)) exifInfo.Flash = flash.Value.ToString(); if (exifProfile.TryGetValue(ExifTag.MeteringMode, out var meteringMode)) exifInfo.MeteringMode = meteringMode.Value.ToString(); if (exifProfile.TryGetValue(ExifTag.WhiteBalance, out var whiteBalance)) exifInfo.WhiteBalance = whiteBalance.Value.ToString(); // 提取时间信息并确保存储为字符串 if (exifProfile.TryGetValue(ExifTag.DateTimeOriginal, out var dateTime)) { exifInfo.DateTimeOriginal = dateTime.Value; // 解析日期时间 if (DateTime.TryParseExact(dateTime.Value, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) { // 只在ExifInfo中保留原始字符串格式 } } // 提取GPS信息 if (exifProfile.TryGetValue(ExifTag.GPSLatitude, out var latitude) && exifProfile.TryGetValue(ExifTag.GPSLatitudeRef, out var latitudeRef)) { string? latRef = latitudeRef.Value; exifInfo.GpsLatitude = ConvertGpsCoordinateToString(latitude.Value, latRef == "S"); } if (exifProfile.TryGetValue(ExifTag.GPSLongitude, out var longitude) && exifProfile.TryGetValue(ExifTag.GPSLongitudeRef, out var longitudeRef)) { string? longRef = longitudeRef.Value; exifInfo.GpsLongitude = ConvertGpsCoordinateToString(longitude.Value, longRef == "W"); } } } catch (Exception ex) { exifInfo.ErrorMessage = $"提取EXIF信息时出错: {ex.Message}"; } return exifInfo; } /// /// 将GPS坐标转换为字符串表示 /// /// GPS坐标的有理数数组(度、分、秒) /// 是否为负值(南纬/西经) /// 十进制格式的GPS坐标 private static string? ConvertGpsCoordinateToString(Rational[]? rationals, bool isNegative) { if (rationals == null || rationals.Length < 3) return null; try { // 度分秒转换为十进制度 double degrees = rationals[0].Numerator / (double)rationals[0].Denominator; double minutes = rationals[1].Numerator / (double)rationals[1].Denominator; double seconds = rationals[2].Numerator / (double)rationals[2].Denominator; double coordinate = degrees + (minutes / 60) + (seconds / 3600); // 如果是南纬或西经,则为负值 if (isNegative) coordinate = -coordinate; return coordinate.ToString(CultureInfo.InvariantCulture); } catch { return null; } } /// /// 从EXIF信息中解析拍摄时间 /// /// EXIF中的拍摄时间字符串 /// UTC格式的日期时间,如果解析失败则返回null public static DateTime? ParseExifDateTime(string? dateTimeOriginal) { if (string.IsNullOrEmpty(dateTimeOriginal)) return null; if (DateTime.TryParseExact(dateTimeOriginal, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate)) { return DateTime.SpecifyKind(parsedDate, DateTimeKind.Local).ToUniversalTime(); } return null; } /// /// 转换图片格式(无损转换并保留EXIF信息) /// /// 输入图片路径 /// 输出图片路径 /// 目标格式 /// 压缩质量(仅对JPEG和WebP有效,1-100) /// 转换后的文件路径 public static async Task ConvertImageFormatAsync(string inputPath, string outputPath, ImageFormat targetFormat, int quality = 95) { if (targetFormat == ImageFormat.Original) { // 如果是原格式,直接返回输入路径 return inputPath; } using var image = await Image.LoadAsync(inputPath); // 保留原始EXIF信息 var originalExifProfile = image.Metadata.ExifProfile; // 根据目标格式确定文件扩展名和输出路径 string extension = GetFileExtensionFromFormat(targetFormat); string finalOutputPath = Path.ChangeExtension(outputPath, extension); switch (targetFormat) { case ImageFormat.Jpeg: await image.SaveAsJpegAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder { Quality = quality }); break; case ImageFormat.Png: await image.SaveAsPngAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Png.PngEncoder { CompressionLevel = SixLabors.ImageSharp.Formats.Png.PngCompressionLevel.BestCompression, ColorType = SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha }); break; case ImageFormat.WebP: await image.SaveAsWebpAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Webp.WebpEncoder { Quality = quality, Method = SixLabors.ImageSharp.Formats.Webp.WebpEncodingMethod.BestQuality }); break; default: throw new NotSupportedException($"不支持的图片格式: {targetFormat}"); } // 如果原图有EXIF信息,保存到转换后的图片中 if (originalExifProfile != null) { using var convertedImage = await Image.LoadAsync(finalOutputPath); convertedImage.Metadata.ExifProfile = originalExifProfile; switch (targetFormat) { case ImageFormat.Jpeg: await convertedImage.SaveAsJpegAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder { Quality = quality }); break; case ImageFormat.Png: await convertedImage.SaveAsPngAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Png.PngEncoder { CompressionLevel = SixLabors.ImageSharp.Formats.Png.PngCompressionLevel.BestCompression, ColorType = SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha }); break; case ImageFormat.WebP: await convertedImage.SaveAsWebpAsync(finalOutputPath, new SixLabors.ImageSharp.Formats.Webp.WebpEncoder { Quality = quality, Method = SixLabors.ImageSharp.Formats.Webp.WebpEncodingMethod.BestQuality }); break; } } return finalOutputPath; } /// /// 根据图片格式获取文件扩展名 /// /// 图片格式 /// 文件扩展名 public static string GetFileExtensionFromFormat(ImageFormat format) { return format switch { ImageFormat.Jpeg => ".jpg", ImageFormat.Png => ".png", ImageFormat.WebP => ".webp", _ => throw new NotSupportedException($"不支持的图片格式: {format}") }; } /// /// 根据图片格式获取MIME类型 /// /// 图片格式 /// MIME类型 public static string GetMimeTypeFromFormat(ImageFormat format) { return format switch { ImageFormat.Jpeg => "image/jpeg", ImageFormat.Png => "image/png", ImageFormat.WebP => "image/webp", _ => throw new NotSupportedException($"不支持的图片格式: {format}") }; } }