Multi-Platform Package Manager for Stable Diffusion
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.

249 lines
7.0 KiB

using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Labs.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;
namespace StabilityMatrix.Avalonia.Controls.VendorLabs;
/// <summary>
/// An image control that asynchronously retrieves an image using a <see cref="Uri"/>.
/// </summary>
[TemplatePart("PART_Image", typeof(Image))]
[TemplatePart("PART_PlaceholderImage", typeof(Image))]
public partial class BetterAsyncImage : TemplatedControl
{
protected Image? ImagePart { get; private set; }
protected Image? PlaceholderPart { get; private set; }
private bool _isInitialized;
private CancellationTokenSource? _tokenSource;
private AsyncImageState _state;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
ImagePart = e.NameScope.Get<Image>("PART_Image");
PlaceholderPart = e.NameScope.Get<Image>("PART_PlaceholderImage");
_tokenSource = new CancellationTokenSource();
_isInitialized = true;
if (Source != null)
{
SetSource(Source);
}
}
private async void SetSource(object? source)
{
if (!_isInitialized)
{
return;
}
_tokenSource?.Cancel();
_tokenSource = new CancellationTokenSource();
AttachSource(null);
if (source == null)
{
return;
}
State = AsyncImageState.Loading;
if (Source is IImage image)
{
AttachSource(image);
return;
}
if (Source == null)
{
return;
}
var uri = Source;
if (uri != null && uri.IsAbsoluteUri)
{
if (uri.Scheme == "http" || uri.Scheme == "https")
{
Bitmap? bitmap = null;
// Android doesn't allow network requests on the main thread, even though we are using async apis.
#if NET6_0_OR_GREATER
if (OperatingSystem.IsAndroid())
{
await Task.Run(async () =>
{
try
{
bitmap = await LoadImageAsync(uri, _tokenSource.Token);
}
catch (Exception ex)
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
State = AsyncImageState.Failed;
RaiseEvent(new AsyncImageFailedEventArgs(ex));
});
}
});
}
else
#endif
{
try
{
bitmap = await LoadImageAsync(uri, _tokenSource.Token);
}
catch (Exception ex)
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
State = AsyncImageState.Failed;
RaiseEvent(new AsyncImageFailedEventArgs(ex));
});
}
}
AttachSource(bitmap);
}
else if (uri.Scheme == "avares")
{
try
{
AttachSource(new Bitmap(AssetLoader.Open(uri)));
}
catch (Exception ex)
{
State = AsyncImageState.Failed;
RaiseEvent(new AsyncImageFailedEventArgs(ex));
}
}
else if (uri.Scheme == "file" && File.Exists(uri.LocalPath))
{
// Added error handling here for local files
try
{
AttachSource(new Bitmap(uri.LocalPath));
}
catch (Exception ex)
{
State = AsyncImageState.Failed;
RaiseEvent(new AsyncImageFailedEventArgs(ex));
}
}
else
{
RaiseEvent(
new AsyncImageFailedEventArgs(
new UriFormatException($"Uri has unsupported scheme. Uri:{source}")
)
);
}
}
else
{
RaiseEvent(
new AsyncImageFailedEventArgs(
new UriFormatException($"Relative paths aren't supported. Uri:{source}")
)
);
}
}
private void AttachSource(IImage? image)
{
if (ImagePart != null)
{
ImagePart.Source = image;
}
_tokenSource?.Cancel();
_tokenSource = new CancellationTokenSource();
if (image == null)
{
State = AsyncImageState.Unloaded;
ImageTransition?.Start(ImagePart, PlaceholderPart, true, _tokenSource.Token);
}
else if (image.Size != default)
{
State = AsyncImageState.Loaded;
ImageTransition?.Start(PlaceholderPart, ImagePart, true, _tokenSource.Token);
RaiseEvent(new RoutedEventArgs(OpenedEvent));
}
}
private async Task<Bitmap> LoadImageAsync(Uri? url, CancellationToken token)
{
if (await ProvideCachedResourceAsync(url, token) is { } bitmap)
{
return bitmap;
}
#if NET6_0_OR_GREATER
using var client = new HttpClient();
var stream = await client.GetStreamAsync(url, token).ConfigureAwait(false);
await using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, token).ConfigureAwait(false);
#elif NETSTANDARD2_0
using var client = new HttpClient();
var response = await client.GetAsync(url, token).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
#endif
memoryStream.Position = 0;
return new Bitmap(memoryStream);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SourceProperty)
{
SetSource(Source);
}
}
protected virtual async Task<Bitmap?> ProvideCachedResourceAsync(Uri? imageUri, CancellationToken token)
{
if (IsCacheEnabled && imageUri != null)
{
return await ImageCache
.Instance.GetFromCacheAsync(imageUri, cancellationToken: token)
.ConfigureAwait(false);
}
return null;
}
}