using System; using System.IO; using System.Threading.Tasks; using AsyncImageLoader; using Avalonia.Media.Imaging; using Blake3; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.FileInterfaces; namespace StabilityMatrix.Avalonia.Models; public record ImageSource : IDisposable { private Hash? contentHashBlake3; /// /// Local file path /// public FilePath? LocalFile { get; init; } /// /// Remote URL /// public Uri? RemoteUrl { get; init; } /// /// Bitmap /// public Bitmap? Bitmap { get; set; } /// /// Optional label for the image /// public string? Label { get; set; } public ImageSource(FilePath localFile) { LocalFile = localFile; } public ImageSource(Uri remoteUrl) { RemoteUrl = remoteUrl; } public ImageSource(Bitmap bitmap) { Bitmap = bitmap; } public Task BitmapAsync => GetBitmapAsync(); /// /// Get the bitmap /// public async Task GetBitmapAsync() { if (Bitmap is not null) return Bitmap; var loader = ImageLoader.AsyncImageLoader; // Use local file path if available, otherwise remote URL var path = LocalFile?.FullPath ?? RemoteUrl?.ToString(); if (path is null) return null; // Load the image Bitmap = await loader.ProvideImageAsync(path).ConfigureAwait(false); return Bitmap; } public async Task GetBlake3HashAsync() { // Use cached value if available if (contentHashBlake3 is not null) { return contentHashBlake3.Value; } // Only available for local files if (LocalFile is null) { throw new InvalidOperationException("ImageSource is not a local file"); } var data = await LocalFile.ReadAllBytesAsync(); contentHashBlake3 = await FileHash.GetBlake3ParallelAsync(data); return contentHashBlake3.Value; } /// /// Return a file name with Guid from Blake3 hash /// public async Task GetHashGuidFileNameAsync() { if (LocalFile is null) { throw new InvalidOperationException("ImageSource is not a local file"); } var extension = LocalFile.Info.Extension; var hash = await GetBlake3HashAsync(); var guid = hash.ToGuid(); return guid + extension; } /// /// Return a file name with Guid from Blake3 hash /// This will throw if the Blake3 hash has not been calculated yet /// public string GetHashGuidFileNameCached() { if (LocalFile is null) { throw new InvalidOperationException("ImageSource is not a local file"); } if (contentHashBlake3 is null) { throw new InvalidOperationException("Blake3 hash has not been calculated yet"); } var extension = LocalFile.Info.Extension; var guid = contentHashBlake3.Value.ToGuid(); return guid + extension; } public string GetHashGuidFileNameCached(string pathPrefix) { return Path.Combine(pathPrefix, GetHashGuidFileNameCached()); } /// /// Clears the cached bitmap /// protected virtual void Dispose(bool disposing) { if (!disposing) return; Bitmap?.Dispose(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// public override string ToString() { return LocalFile?.FullPath ?? RemoteUrl?.ToString() ?? ""; } /// /// Implicit conversion to string for async image loader. /// Resolves with the local file path if available, otherwise the remote URL. /// Otherwise returns null. /// public static implicit operator string(ImageSource imageSource) => imageSource.ToString(); }