Moved asynchronous image loading to Tile class

This commit is contained in:
ClemensFischer 2025-09-10 20:09:12 +02:00
parent 2f9c50fb47
commit f4d43eeb44
10 changed files with 114 additions and 184 deletions

View file

@ -1,33 +1,47 @@
using Avalonia; using Avalonia;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading;
using System; using System;
using System.Threading.Tasks;
namespace MapControl namespace MapControl
{ {
public partial class Tile public partial class Tile
{ {
private void FadeIn() public async Task LoadImageAsync(Func<Task<IImage>> loadImageFunc)
{ {
var fadeInAnimation = new Animation var image = await loadImageFunc().ConfigureAwait(false);
{
Duration = MapBase.ImageFadeDuration,
Children =
{
new KeyFrame
{
KeyTime = TimeSpan.Zero,
Setters = { new Setter(Visual.OpacityProperty, 0d) }
},
new KeyFrame
{
KeyTime = MapBase.ImageFadeDuration,
Setters = { new Setter(Visual.OpacityProperty, 1d) }
}
}
};
_ = fadeInAnimation.RunAsync(Image); await Dispatcher.UIThread.InvokeAsync(
() =>
{
Image.Source = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
var fadeInAnimation = new Animation
{
Duration = MapBase.ImageFadeDuration,
Children =
{
new KeyFrame
{
KeyTime = TimeSpan.Zero,
Setters = { new Setter(Visual.OpacityProperty, 0d) }
},
new KeyFrame
{
KeyTime = MapBase.ImageFadeDuration,
Setters = { new Setter(Visual.OpacityProperty, 1d) }
}
}
};
_ = fadeInAnimation.RunAsync(Image);
}
});
} }
} }
} }

View file

@ -1,18 +0,0 @@
using Avalonia.Media;
using Avalonia.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MapControl
{
public partial class TileImageLoader
{
private static async Task LoadTileImage(Tile tile, Func<Task<IImage>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);
await Dispatcher.UIThread.InvokeAsync(() => tile.SetImageSource(image));
}
}
}

View file

@ -1,5 +1,4 @@
using System; #if WPF
#if WPF
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
#elif UWP #elif UWP
@ -25,25 +24,12 @@ namespace MapControl
Column = ((x % columnCount) + columnCount) % columnCount; Column = ((x % columnCount) + columnCount) % columnCount;
} }
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public int ZoomLevel { get; } public int ZoomLevel { get; }
public int X { get; } public int X { get; }
public int Y { get; } public int Y { get; }
public int Column { get; } public int Column { get; }
public int Row => Y; public int Row => Y;
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public bool IsPending { get; set; } = true; public bool IsPending { get; set; } = true;
public void SetImageSource(ImageSource image)
{
IsPending = false;
Image.Source = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
FadeIn();
}
}
} }
} }

View file

@ -7,13 +7,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
#if WPF
using System.Windows.Media;
#elif UWP
using Windows.UI.Xaml.Media;
#elif WINUI
using Microsoft.UI.Xaml.Media;
#endif
namespace MapControl namespace MapControl
{ {
@ -27,7 +20,7 @@ namespace MapControl
void CancelLoadTiles(); void CancelLoadTiles();
} }
public partial class TileImageLoader : ITileImageLoader public class TileImageLoader : ITileImageLoader
{ {
private static ILogger logger; private static ILogger logger;
private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger<TileImageLoader>()); private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger<TileImageLoader>());
@ -161,9 +154,7 @@ namespace MapControl
if (string.IsNullOrEmpty(cacheName)) if (string.IsNullOrEmpty(cacheName))
{ {
Task<ImageSource> LoadImage() => tileSource.LoadImageAsync(tile.Column, tile.Row, tile.ZoomLevel); await tile.LoadImageAsync(() => tileSource.LoadImageAsync(tile.Column, tile.Row, tile.ZoomLevel)).ConfigureAwait(false);
await LoadTileImage(tile, LoadImage).ConfigureAwait(false);
} }
else else
{ {
@ -175,9 +166,7 @@ namespace MapControl
if (buffer?.Length > 0) if (buffer?.Length > 0)
{ {
Task<ImageSource> LoadImage() => tileSource.LoadImageAsync(buffer); await tile.LoadImageAsync(() => tileSource.LoadImageAsync(buffer)).ConfigureAwait(false);
await LoadTileImage(tile, LoadImage).ConfigureAwait(false);
} }
} }
} }

View file

@ -282,7 +282,6 @@
<Link>Tile.WinUI.cs</Link> <Link>Tile.WinUI.cs</Link>
</Compile> </Compile>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TileImageLoader.UWP.cs" />
<EmbeddedResource Include="Properties\MapControl.UWP.rd.xml" /> <EmbeddedResource Include="Properties\MapControl.UWP.rd.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,36 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml.Media;
namespace MapControl
{
public partial class TileImageLoader
{
private static async Task LoadTileImage(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{
var tcs = new TaskCompletionSource<object>();
async void LoadTileImage()
{
try
{
tile.SetImageSource(await loadImageFunc());
tcs.TrySetResult(null);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
if (!await tile.Image.Dispatcher.TryRunAsync(CoreDispatcherPriority.Low, LoadTileImage))
{
tcs.TrySetCanceled();
}
await tcs.Task;
}
}
}

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@ -9,6 +10,30 @@ namespace MapControl
{ {
public partial class Tile public partial class Tile
{ {
public async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);
await Image.Dispatcher.InvokeAsync(
() =>
{
Image.Source = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
if (image is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
}
});
}
private void BeginFadeInAnimation() private void BeginFadeInAnimation()
{ {
var fadeInAnimation = new DoubleAnimation var fadeInAnimation = new DoubleAnimation
@ -21,19 +46,6 @@ namespace MapControl
Image.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); Image.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
} }
private void FadeIn()
{
if (Image.Source is BitmapSource bitmap && !bitmap.IsFrozen && bitmap.IsDownloading)
{
bitmap.DownloadCompleted += BitmapDownloadCompleted;
bitmap.DownloadFailed += BitmapDownloadFailed;
}
else
{
BeginFadeInAnimation();
}
}
private void BitmapDownloadCompleted(object sender, EventArgs e) private void BitmapDownloadCompleted(object sender, EventArgs e)
{ {
var bitmap = (BitmapSource)sender; var bitmap = (BitmapSource)sender;

View file

@ -1,17 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
namespace MapControl
{
public partial class TileImageLoader
{
private static async Task LoadTileImage(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);
await tile.Image.Dispatcher.InvokeAsync(() => tile.SetImageSource(image));
}
}
}

View file

@ -1,9 +1,15 @@
#if UWP using System;
using System.Threading.Tasks;
#if UWP
using Windows.UI.Core;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Media.Imaging; using Windows.UI.Xaml.Media.Imaging;
#else #else
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
#endif #endif
@ -12,6 +18,50 @@ namespace MapControl
{ {
public partial class Tile public partial class Tile
{ {
public async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var tcs = new TaskCompletionSource<object>();
async void LoadImage()
{
try
{
var image = await loadImageFunc();
Image.Source = image;
if (image != null && MapBase.ImageFadeDuration > TimeSpan.Zero)
{
if (image is BitmapImage bitmap && bitmap.UriSource != null)
{
bitmap.ImageOpened += BitmapImageOpened;
bitmap.ImageFailed += BitmapImageFailed;
}
else
{
BeginFadeInAnimation();
}
}
tcs.TrySetResult(null);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
#if UWP
if (!await Image.Dispatcher.TryRunAsync(CoreDispatcherPriority.Low, LoadImage))
#else
if (!Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, LoadImage))
#endif
{
tcs.TrySetCanceled();
}
await tcs.Task;
}
private void BeginFadeInAnimation() private void BeginFadeInAnimation()
{ {
var fadeInAnimation = new DoubleAnimation var fadeInAnimation = new DoubleAnimation
@ -29,19 +79,6 @@ namespace MapControl
storyboard.Begin(); storyboard.Begin();
} }
private void FadeIn()
{
if (Image.Source is BitmapImage bitmap && bitmap.UriSource != null)
{
bitmap.ImageOpened += BitmapImageOpened;
bitmap.ImageFailed += BitmapImageFailed;
}
else
{
BeginFadeInAnimation();
}
}
private void BitmapImageOpened(object sender, RoutedEventArgs e) private void BitmapImageOpened(object sender, RoutedEventArgs e)
{ {
var bitmap = (BitmapImage)sender; var bitmap = (BitmapImage)sender;

View file

@ -1,36 +0,0 @@
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MapControl
{
public partial class TileImageLoader
{
private static Task LoadTileImage(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{
var tcs = new TaskCompletionSource();
async void LoadTileImage()
{
try
{
tile.SetImageSource(await loadImageFunc());
tcs.TrySetResult();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
if (!tile.Image.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, LoadTileImage))
{
tcs.TrySetCanceled();
}
return tcs.Task;
}
}
}