2025-03-31 21:33:52 +02:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using System;
|
2023-08-20 14:00:30 +02:00
|
|
|
|
using System.IO;
|
2019-07-13 00:08:56 +02:00
|
|
|
|
using System.Net.Http;
|
2025-08-21 21:48:08 +02:00
|
|
|
|
using System.Threading.Tasks;
|
2024-05-22 11:25:32 +02:00
|
|
|
|
#if WPF
|
|
|
|
|
|
using System.Windows.Media;
|
2021-11-17 23:17:11 +01:00
|
|
|
|
#elif UWP
|
2019-06-13 21:38:01 +02:00
|
|
|
|
using Windows.UI.Xaml.Media;
|
2024-05-22 11:25:32 +02:00
|
|
|
|
#elif WINUI
|
|
|
|
|
|
using Microsoft.UI.Xaml.Media;
|
2018-08-08 23:31:52 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
|
|
|
|
|
public static partial class ImageLoader
|
|
|
|
|
|
{
|
2025-03-31 21:33:52 +02:00
|
|
|
|
private static ILogger logger;
|
|
|
|
|
|
private static ILogger Logger => logger ?? (logger = LoggerFactory?.CreateLogger(typeof(ImageLoader)));
|
|
|
|
|
|
|
|
|
|
|
|
public static ILoggerFactory LoggerFactory { get; set; }
|
|
|
|
|
|
|
2018-08-08 23:31:52 +02:00
|
|
|
|
/// <summary>
|
2019-07-13 19:53:03 +02:00
|
|
|
|
/// The System.Net.Http.HttpClient instance used to download images via a http or https Uri.
|
2018-08-08 23:31:52 +02:00
|
|
|
|
/// </summary>
|
2025-03-31 21:33:52 +02:00
|
|
|
|
public static HttpClient HttpClient { get; set; }
|
2018-08-08 23:31:52 +02:00
|
|
|
|
|
2023-08-25 18:43:30 +02:00
|
|
|
|
static ImageLoader()
|
|
|
|
|
|
{
|
2025-08-22 11:06:37 +02:00
|
|
|
|
HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
2023-08-25 18:43:30 +02:00
|
|
|
|
HttpClient.DefaultRequestHeaders.Add("User-Agent", $"XAML-Map-Control/{typeof(ImageLoader).Assembly.GetName().Version}");
|
|
|
|
|
|
}
|
2019-07-13 08:49:49 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
public static async Task<ImageSource> LoadImageAsync(Uri uri, IProgress<double> progress = null)
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2019-06-13 21:38:01 +02:00
|
|
|
|
ImageSource image = null;
|
2018-08-08 23:31:52 +02:00
|
|
|
|
|
2022-11-30 17:59:38 +01:00
|
|
|
|
progress?.Report(0d);
|
|
|
|
|
|
|
2018-08-09 18:58:47 +02:00
|
|
|
|
try
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2024-05-19 17:24:18 +02:00
|
|
|
|
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
2018-08-09 18:58:47 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
var response = await GetHttpResponseAsync(uri, progress);
|
2019-07-13 00:08:56 +02:00
|
|
|
|
|
2025-01-02 13:44:46 +01:00
|
|
|
|
if (response?.Buffer != null)
|
2019-07-13 08:49:49 +02:00
|
|
|
|
{
|
2019-07-13 19:53:03 +02:00
|
|
|
|
image = await LoadImageAsync(response.Buffer);
|
2019-07-13 00:08:56 +02:00
|
|
|
|
}
|
2018-08-09 18:58:47 +02:00
|
|
|
|
}
|
2024-05-19 17:24:18 +02:00
|
|
|
|
else if (uri.IsFile || !uri.IsAbsoluteUri)
|
|
|
|
|
|
{
|
|
|
|
|
|
image = await LoadImageAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString);
|
|
|
|
|
|
}
|
2018-08-09 18:58:47 +02:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-05-19 17:24:18 +02:00
|
|
|
|
image = LoadImage(uri);
|
2018-08-09 18:58:47 +02:00
|
|
|
|
}
|
2018-08-08 23:31:52 +02:00
|
|
|
|
}
|
2018-08-09 18:58:47 +02:00
|
|
|
|
catch (Exception ex)
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2025-08-22 11:06:37 +02:00
|
|
|
|
Logger?.LogError(ex, "Failed loading {uri}", uri);
|
2018-08-08 23:31:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-30 17:59:38 +01:00
|
|
|
|
progress?.Report(1d);
|
|
|
|
|
|
|
2018-12-09 17:14:32 +01:00
|
|
|
|
return image;
|
2018-08-08 23:31:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-20 14:00:30 +02:00
|
|
|
|
public static async Task<ImageSource> LoadImageAsync(byte[] buffer)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var stream = new MemoryStream(buffer))
|
|
|
|
|
|
{
|
|
|
|
|
|
return await LoadImageAsync(stream);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-22 19:47:57 +02:00
|
|
|
|
internal class HttpResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
public byte[] Buffer { get; }
|
|
|
|
|
|
public TimeSpan? MaxAge { get; }
|
|
|
|
|
|
|
|
|
|
|
|
public HttpResponse(byte[] buffer, TimeSpan? maxAge)
|
|
|
|
|
|
{
|
|
|
|
|
|
Buffer = buffer;
|
|
|
|
|
|
MaxAge = maxAge;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
internal static async Task<HttpResponse> GetHttpResponseAsync(Uri uri, IProgress<double> progress = null)
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2019-07-13 08:49:49 +02:00
|
|
|
|
HttpResponse response = null;
|
2019-07-13 00:08:56 +02:00
|
|
|
|
|
|
|
|
|
|
try
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2025-08-22 11:06:37 +02:00
|
|
|
|
var completionOptions = progress != null ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead;
|
|
|
|
|
|
|
|
|
|
|
|
using (var responseMessage = await HttpClient.GetAsync(uri, completionOptions).ConfigureAwait(false))
|
2018-08-08 23:31:52 +02:00
|
|
|
|
{
|
2019-07-13 08:49:49 +02:00
|
|
|
|
if (responseMessage.IsSuccessStatusCode)
|
2019-07-13 00:08:56 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
byte[] buffer;
|
|
|
|
|
|
|
|
|
|
|
|
if (progress != null && responseMessage.Content.Headers.ContentLength.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
buffer = await ReadAsByteArray(responseMessage.Content, progress).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
buffer = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
|
|
|
|
|
}
|
2019-07-22 19:47:57 +02:00
|
|
|
|
|
|
|
|
|
|
response = new HttpResponse(buffer, responseMessage.Headers.CacheControl?.MaxAge);
|
2019-07-13 00:08:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-08-22 11:06:37 +02:00
|
|
|
|
Logger?.LogWarning("{status} ({reason}) from {uri}", (int)responseMessage.StatusCode, responseMessage.ReasonPhrase, uri);
|
2019-07-13 00:08:56 +02:00
|
|
|
|
}
|
2018-08-08 23:31:52 +02:00
|
|
|
|
}
|
2018-08-09 18:58:47 +02:00
|
|
|
|
}
|
2025-08-22 11:06:37 +02:00
|
|
|
|
catch (TaskCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
Logger?.LogWarning("Timeout while loading {uri}", uri);
|
|
|
|
|
|
}
|
2019-07-13 00:08:56 +02:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-08-22 11:06:37 +02:00
|
|
|
|
Logger?.LogError(ex, "Failed loading {uri}", uri);
|
2019-07-13 00:08:56 +02:00
|
|
|
|
}
|
2018-08-09 18:58:47 +02:00
|
|
|
|
|
2019-07-13 08:49:49 +02:00
|
|
|
|
return response;
|
2019-07-13 00:08:56 +02:00
|
|
|
|
}
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
private static async Task<byte[]> ReadAsByteArray(HttpContent content, IProgress<double> progress)
|
2022-08-05 18:54:19 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
var length = (int)content.Headers.ContentLength.Value;
|
|
|
|
|
|
var buffer = new byte[length];
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
using (var stream = await content.ReadAsStreamAsync().ConfigureAwait(false))
|
2022-08-05 18:54:19 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
int offset = 0;
|
|
|
|
|
|
int read;
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
while (offset < length &&
|
|
|
|
|
|
(read = await stream.ReadAsync(buffer, offset, length - offset).ConfigureAwait(false)) > 0)
|
2022-08-05 18:54:19 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
offset += read;
|
2025-08-20 16:44:48 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
if (offset < length) // 1.0 reported by caller
|
2022-08-05 18:54:19 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
progress.Report((double)offset / length);
|
2022-08-05 18:54:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
|
|
}
|
2018-08-08 23:31:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|