using Harmonia.Core.Caching; using Harmonia.Core.Imaging; using Harmonia.Core.Models; using Microsoft.Extensions.Caching.Memory; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml.Media.Imaging; using System; using System.Collections.Concurrent; using System.IO; using System.Threading; using System.Threading.Tasks; namespace Harmonia.WinUI.Caching; public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : MemoryCache, IAudioBitmapImageCache { protected override MemoryCacheOptions Options => new() { SizeLimit = 200_000_000, CompactionPercentage = 0.2, }; protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600); protected override int MaxConcurrentRequests => 8; protected virtual int MaxImageWidthOrHeight => 1000; protected override object? GetKey(Song key) { if (string.IsNullOrWhiteSpace(key.ImageHash) == false) { return key.ImageHash; } else if (string.IsNullOrWhiteSpace(key.ImageName) == false) { return key.ImageName; } return "Default"; } protected override async ValueTask FetchAsync(Song key, CancellationToken cancellationToken) { SongPictureInfo? songPictureInfo = await Task.Run( () => audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken), cancellationToken); if (songPictureInfo == null) return GetDefaultBitmapImage(); using MemoryStream stream = new(songPictureInfo.Data); BitmapImage bitmapImage = new(); await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); int decodePixelWidth = GetDecodePixelWidth(bitmapImage); int decodePixelHeight = GetDecodePixelHeight(bitmapImage); if (decodePixelWidth > 0 || decodePixelHeight > 0) { bitmapImage.DecodePixelWidth = decodePixelWidth; bitmapImage.DecodePixelHeight = decodePixelHeight; stream.Seek(0, SeekOrigin.Begin); await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); } return bitmapImage; } private BitmapImage GetDefaultBitmapImage() { Uri uri = new("ms-appx:///Assets/Default.png", UriKind.Absolute); BitmapImage bitmapImage = new(); bitmapImage.UriSource = uri; return bitmapImage; } private int GetDecodePixelWidth(BitmapImage bitmapImage) { int originalImageWidth = bitmapImage.PixelWidth; int orignalImageHeight = bitmapImage.PixelHeight; if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) return 0; if (orignalImageHeight > originalImageWidth) return 0; return MaxImageWidthOrHeight; } private int GetDecodePixelHeight(BitmapImage bitmapImage) { int originalImageWidth = bitmapImage.PixelWidth; int orignalImageHeight = bitmapImage.PixelHeight; if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) return 0; if (originalImageWidth > orignalImageHeight) return 0; return MaxImageWidthOrHeight; } protected override long GetEntrySize(BitmapImage entry) { int width = entry.DecodePixelWidth > 0 ? entry.DecodePixelWidth : entry.PixelWidth; int height = entry.DecodePixelHeight > 0 ? entry.DecodePixelHeight : entry.PixelHeight; return (long)width * height * 4; } } public class AudioBitmapImageCache2 : MemoryCache, IAudioBitmapImageCache { private readonly IAudioImageExtractor _audioImageExtractor; private readonly BitmapImage[] _bitmapPool; private int _nextIndex = 0; private ConcurrentDictionary _test = []; protected override MemoryCacheOptions Options => new() { SizeLimit = 200_000_000, CompactionPercentage = 0.2, }; protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600); protected override int MaxConcurrentRequests => 8; protected virtual int MaxImageWidthOrHeight => 1000; protected virtual int BitmapPoolSize => 64; public AudioBitmapImageCache2(IAudioImageExtractor audioImageExtractor) { _audioImageExtractor = audioImageExtractor; _bitmapPool = new BitmapImage[BitmapPoolSize]; InitializeBitmapPool(); } private void InitializeBitmapPool() { for (int i = 0; i < _bitmapPool.Length; i++) _bitmapPool[i] = new BitmapImage(); //DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); //TaskCompletionSource taskCompletionSource = new(); //dispatcherQueue.TryEnqueue(() => //{ // for (int i = 0; i < _bitmapPool.Length; i++) // _bitmapPool[i] = new BitmapImage(); // taskCompletionSource.SetResult(); //}); //taskCompletionSource.Task.GetAwaiter().GetResult(); } protected override object? GetKey(Song key) { if (string.IsNullOrWhiteSpace(key.ImageHash) == false) { return key.ImageHash; } else if (string.IsNullOrWhiteSpace(key.ImageName) == false) { return key.ImageName; } return "Default"; } protected override async ValueTask FetchAsync(Song key, CancellationToken cancellationToken) { int index = Interlocked.Increment(ref _nextIndex); BitmapImage bitmapImage = _bitmapPool[index % _bitmapPool.Length]; //_test.AddOrUpdate(index, bitmapImage); SongPictureInfo? songPictureInfo = await _audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken); if (songPictureInfo == null) { bitmapImage.UriSource = new("ms-appx:///Assets/Default.png", UriKind.Absolute); } else { using MemoryStream stream = new(songPictureInfo.Data); await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); } bitmapImage.DecodePixelWidth = GetDecodePixelWidth(bitmapImage); bitmapImage.DecodePixelHeight = GetDecodePixelHeight(bitmapImage); return bitmapImage; } private int GetDecodePixelWidth(BitmapImage bitmapImage) { int originalImageWidth = bitmapImage.PixelWidth; int orignalImageHeight = bitmapImage.PixelHeight; if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) return 0; if (orignalImageHeight > originalImageWidth) return 0; return MaxImageWidthOrHeight; } private int GetDecodePixelHeight(BitmapImage bitmapImage) { int originalImageWidth = bitmapImage.PixelWidth; int orignalImageHeight = bitmapImage.PixelHeight; if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) return 0; if (originalImageWidth > orignalImageHeight) return 0; return MaxImageWidthOrHeight; } protected override long GetEntrySize(BitmapImage entry) { return entry.DecodePixelWidth * entry.DecodePixelHeight; } protected override void PostEvictionCallback(object? cacheKey, object? cacheValue, EvictionReason evictionReason, object? state) { } }