You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
7.7 KiB

using Avalonia.Data.Converters;
using Avalonia.Media.Imaging;
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
namespace AuroraDesk.Presentation.Converters;
/// <summary>
/// 图片相关转换器
/// </summary>
public static class ImageConverters
{
/// <summary>
/// 文件路径到图片源的转换器
/// </summary>
public static readonly IValueConverter FilePathToImageSourceConverter = new FilePathToImageSourceConverter();
/// <summary>
/// 大于零的转换器(用于数字到布尔值)
/// </summary>
public static readonly IValueConverter IsGreaterThanZeroConverter = new IsGreaterThanZeroConverter();
/// <summary>
/// 反转布尔值转换器
/// </summary>
public static readonly IValueConverter InvertedBoolConverter = new InvertedBoolConverter();
/// <summary>
/// 是否为零的转换器
/// </summary>
public static readonly IValueConverter IsZeroConverter = new IsZeroConverter();
}
/// <summary>
/// 文件路径到图片源转换器(异步加载缩略图,类似 Windows 11)
/// 特点:
/// 1. 异步加载,不阻塞UI线程
/// 2. 只加载缩略图(184x184),不加载完整图片
/// 3. 缓存已加载的缩略图,避免重复加载
/// 4. 延迟加载,只在需要时才加载
/// </summary>
public class FilePathToImageSourceConverter : IValueConverter
{
// 缩略图缓存(线程安全)
private static readonly ConcurrentDictionary<string, Bitmap?> _thumbnailCache = new();
// 正在加载的任务字典,避免重复加载
private static readonly ConcurrentDictionary<string, Task<Bitmap?>> _loadingTasks = new();
// 缩略图大小(与UI中的184x184一致)
private const int ThumbnailSize = 184;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string filePath && !string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
// 先返回 null,让 UI 显示占位符
// 然后异步加载缩略图
_ = LoadThumbnailAsync(filePath);
return null; // 初始返回 null,异步加载完成后通过绑定更新
}
return null;
}
/// <summary>
/// 异步加载缩略图
/// </summary>
private static async Task<Bitmap?> LoadThumbnailAsync(string filePath)
{
// 检查缓存
if (_thumbnailCache.TryGetValue(filePath, out var cached))
{
return cached;
}
// 检查是否正在加载
if (_loadingTasks.TryGetValue(filePath, out var loadingTask))
{
return await loadingTask;
}
// 创建新的加载任务
var task = Task.Run(async () =>
{
try
{
// 在后台线程加载图片
using var imageStream = File.OpenRead(filePath);
var bitmap = new Bitmap(imageStream);
// 如果图片尺寸小于缩略图尺寸,直接返回原图(克隆)
if (bitmap.PixelSize.Width <= ThumbnailSize && bitmap.PixelSize.Height <= ThumbnailSize)
{
// 克隆位图以避免释放原始资源
using var memoryStream = new MemoryStream();
bitmap.Save(memoryStream);
memoryStream.Position = 0;
var result = new Bitmap(memoryStream);
_thumbnailCache.TryAdd(filePath, result);
return result;
}
// 创建缩略图(保持宽高比)
var sourceSize = bitmap.PixelSize;
var scale = Math.Min(
(double)ThumbnailSize / sourceSize.Width,
(double)ThumbnailSize / sourceSize.Height);
var thumbnailWidth = (int)(sourceSize.Width * scale);
var thumbnailHeight = (int)(sourceSize.Height * scale);
// 创建缩略图
var thumbnail = bitmap.CreateScaledBitmap(
new Avalonia.PixelSize(thumbnailWidth, thumbnailHeight),
Avalonia.Media.Imaging.BitmapInterpolationMode.HighQuality);
// 释放原图资源
bitmap.Dispose();
// 缓存缩略图
_thumbnailCache.TryAdd(filePath, thumbnail);
return thumbnail;
}
catch
{
// 加载失败,缓存 null 避免重复尝试
_thumbnailCache.TryAdd(filePath, null);
return null;
}
finally
{
// 移除加载任务
_loadingTasks.TryRemove(filePath, out _);
}
});
_loadingTasks.TryAdd(filePath, task);
return await task;
}
/// <summary>
/// 获取缩略图(同步方法,用于异步绑定)
/// </summary>
public static Bitmap? GetThumbnail(string filePath)
{
if (_thumbnailCache.TryGetValue(filePath, out var cached))
{
return cached;
}
// 触发异步加载
_ = LoadThumbnailAsync(filePath);
return null;
}
/// <summary>
/// 清除缓存
/// </summary>
public static void ClearCache()
{
foreach (var bitmap in _thumbnailCache.Values)
{
bitmap?.Dispose();
}
_thumbnailCache.Clear();
_loadingTasks.Clear();
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 大于零转换器
/// </summary>
public class IsGreaterThanZeroConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int intValue)
{
return intValue > 0;
}
if (value is long longValue)
{
return longValue > 0;
}
if (value is double doubleValue)
{
return doubleValue > 0;
}
return false;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 反转布尔值转换器
/// </summary>
public class InvertedBoolConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return !boolValue;
}
return true;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return !boolValue;
}
return false;
}
}
/// <summary>
/// 是否为零转换器
/// </summary>
public class IsZeroConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int intValue)
{
return intValue == 0;
}
if (value is long longValue)
{
return longValue == 0;
}
if (value is double doubleValue)
{
return doubleValue == 0;
}
return true;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}