Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: AvaloniaUtils/AsyncImageLoader.Avalonia
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.2.1
Choose a base ref
...
head repository: AvaloniaUtils/AsyncImageLoader.Avalonia
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.3.0
Choose a head ref
  • 4 commits
  • 9 files changed
  • 2 contributors

Commits on Aug 17, 2024

  1. Copy the full SHA
    f02aaab View commit details
  2. Add errors logging to loaders

    Fix #23
    SKProCH committed Aug 17, 2024
    Copy the full SHA
    274706f View commit details
  3. Merge pull request #24 from AvaloniaUtils/addLogging

    Add errors logging to loaders
    SKProCH authored Aug 17, 2024
    Copy the full SHA
    00edb42 View commit details
  4. Copy the full SHA
    5836120 View commit details
21 changes: 21 additions & 0 deletions .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Build and publish

on:
push:
branches:
- master
- main
- release/**
paths-ignore:
- Material.Avalonia.Demo*/**
tags:
- v**

jobs:
build-and-test:
uses: SKProCH/CommonWorkflows/.github/workflows/build-publish.yml@main
secrets:
NUGET_KEY: ${{ secrets.NUGET_KEY }}
with:
publish-nightly: false
dotnet-version: 8
21 changes: 0 additions & 21 deletions .github/workflows/build.yml

This file was deleted.

24 changes: 0 additions & 24 deletions .github/workflows/publish.yml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets/avalonia-logo.ico" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0" />
<PackageReference Include="Avalonia" Version="11.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AsyncImageLoader.Avalonia\AsyncImageLoader.Avalonia.csproj" />
126 changes: 71 additions & 55 deletions AsyncImageLoader.Avalonia/AdvancedImage.axaml.cs
Original file line number Diff line number Diff line change
@@ -3,12 +3,13 @@
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;

namespace AsyncImageLoader;
namespace AsyncImageLoader;

public class AdvancedImage : ContentControl
{
@@ -74,6 +75,7 @@ public class AdvancedImage : ContentControl
private bool _shouldLoaderChangeTriggerUpdate;

private CancellationTokenSource? _updateCancellationToken;
private readonly ParametrizedLogger? _logger;

static AdvancedImage()
{
@@ -89,6 +91,7 @@ static AdvancedImage()
public AdvancedImage(Uri? baseUri)
{
_baseUri = baseUri;
_logger = Logger.TryGet(LogEventLevel.Error, ImageLoader.AsyncImageLoaderLogArea);
}

/// <summary>
@@ -177,16 +180,18 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
base.OnPropertyChanged(change);
}

private void ClearSourceIfUserProvideImage() {
if (CurrentImage is not null and not ImageWrapper) {
private void ClearSourceIfUserProvideImage()
{
if (CurrentImage is not null and not ImageWrapper)
{
// User provided image himself
Source = null;
}
}

private async void UpdateImage(string? source, IAsyncImageLoader? loader)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationTokenSource = new CancellationTokenSource();

var oldCancellationToken = Interlocked.Exchange(ref _updateCancellationToken, cancellationTokenSource);

@@ -197,59 +202,65 @@ private async void UpdateImage(string? source, IAsyncImageLoader? loader)
catch (ObjectDisposedException)
{
}

if (source is null && CurrentImage is not ImageWrapper) {

if (source is null && CurrentImage is not ImageWrapper)
{
// User provided image himself
return;
}
IsLoading = true;
CurrentImage = null;

var bitmap = await Task.Run(async () =>
{
try
{
if (source == null)
return null;

// A small delay allows to cancel early if the image goes out of screen too fast (eg. scrolling)
// The Bitmap constructor is expensive and cannot be cancelled
await Task.Delay(10, cancellationTokenSource.Token);

// Hack to support relative URI
// TODO: Refactor IAsyncImageLoader to support BaseUri
try
{
var uri = new Uri(source, UriKind.RelativeOrAbsolute);
if (AssetLoader.Exists(uri, _baseUri))

IsLoading = true;
CurrentImage = null;


var bitmap = await Task.Run(async () =>
{
try
{
if (source == null)
return null;

// A small delay allows to cancel early if the image goes out of screen too fast (eg. scrolling)
// The Bitmap constructor is expensive and cannot be cancelled
await Task.Delay(10, cancellationTokenSource.Token);

// Hack to support relative URI
// TODO: Refactor IAsyncImageLoader to support BaseUri
try
{
var uri = new Uri(source, UriKind.RelativeOrAbsolute);
if (AssetLoader.Exists(uri, _baseUri))
return new Bitmap(AssetLoader.Open(uri, _baseUri));
}
catch (Exception)
{
// ignored
}

loader ??= ImageLoader.AsyncImageLoader;
return await loader.ProvideImageAsync(source);
}
catch (TaskCanceledException)
{
return null;
}
finally
{
cancellationTokenSource.Dispose();
}

}, CancellationToken.None);

if (cancellationTokenSource.IsCancellationRequested)
}
catch (Exception)
{
// ignored
}

loader ??= ImageLoader.AsyncImageLoader;
return await loader.ProvideImageAsync(source);
}
catch (TaskCanceledException)
{
return null;
}
catch (Exception e)
{
_logger?.Log(this, "AdvancedImage image resolution failed: {0}", e);

return null;
}
finally
{
cancellationTokenSource.Dispose();
}
}, CancellationToken.None);

if (cancellationTokenSource.IsCancellationRequested)
return;
CurrentImage = bitmap is null ? null : new ImageWrapper(bitmap);
IsLoading = false;
}
}

private void UpdateCornerRadius(CornerRadius radius)
{
@@ -308,18 +319,23 @@ protected override Size ArrangeOverride(Size finalSize)
? Stretch.CalculateSize(finalSize, CurrentImage.Size)
: base.ArrangeOverride(finalSize);
}

public sealed class ImageWrapper : IImage {

public sealed class ImageWrapper : IImage
{
public IImage ImageImplementation { get; }
internal ImageWrapper(IImage imageImplementation) {

internal ImageWrapper(IImage imageImplementation)
{
ImageImplementation = imageImplementation;
}

/// <inheritdoc />
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect) {
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)
{
ImageImplementation.Draw(context, sourceRect, destRect);
}

/// <inheritdoc />
public Size Size => ImageImplementation.Size;
}
}
}
6 changes: 1 addition & 5 deletions AsyncImageLoader.Avalonia/AsyncImageLoader.Avalonia.csproj
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<RootNamespace>AsyncImageLoader</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

<Title>AsyncImageLoader.Avalonia</Title>
<Authors>SKProCH</Authors>
<Description>Provides way to asynchronous bitmap loading from web for Avalonia Image control and more</Description>
@@ -15,10 +15,6 @@
<RepositoryType>git</RepositoryType>
<PackageTags>image cross-platform avalonia avaloniaui c-sharp-library</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>3.2.1</Version>
<PackageReleaseNotes>
- Fix ObjectDisposedException in Cancellation token access (#19)
</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
23 changes: 19 additions & 4 deletions AsyncImageLoader.Avalonia/ImageBrushLoader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using AsyncImageLoader.Loaders;
using System;
using AsyncImageLoader.Loaders;
using Avalonia;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Imaging;

namespace AsyncImageLoader {
public static class ImageBrushLoader {
private static readonly ParametrizedLogger? Logger;
public static IAsyncImageLoader AsyncImageLoader { get; set; } = new RamCachedWebImageLoader();
static ImageBrushLoader() {
SourceProperty.Changed.AddClassHandler<ImageBrush>(OnSourceChanged);
Logger = Avalonia.Logging.Logger.TryGet(LogEventLevel.Error, ImageLoader.AsyncImageLoaderLogArea);
}

private static async void OnSourceChanged(ImageBrush imageBrush, AvaloniaPropertyChangedEventArgs args) {
@@ -16,9 +21,19 @@ private static async void OnSourceChanged(ImageBrush imageBrush, AvaloniaPropert

SetIsLoading(imageBrush, true);

var bitmap = newValue == null
? null
: await AsyncImageLoader.ProvideImageAsync(newValue);
Bitmap? bitmap = null;
try
{
if (newValue is not null)
{
bitmap = await AsyncImageLoader.ProvideImageAsync(newValue);
}
}
catch (Exception e)
{
Logger?.Log("ImageBrushLoader", "ImageBrushLoader image resolution failed: {0}", e);
}

if (GetSource(imageBrush) != newValue) return;
imageBrush.Source = bitmap;

Loading