Skip to content

Commit 293724c

Browse files
committed
Merge remote-tracking branch 'origin/develop'
2 parents d3aa382 + 8a19a7b commit 293724c

File tree

1 file changed

+102
-39
lines changed

1 file changed

+102
-39
lines changed

AuroraControlsMaui/Platforms/Android/NoCacheFileImageSourceService.cs

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Bumptech.Glide.Request;
1111
using Bumptech.Glide.Request.Target;
1212
using Microsoft.Extensions.Logging;
13+
using Microsoft.Extensions.Options;
1314
using Microsoft.Maui.Controls.PlatformConfiguration;
1415
using Microsoft.Maui.Platform;
1516
using Path = System.IO.Path;
@@ -18,78 +19,140 @@ namespace AuroraControls;
1819

1920
internal partial class NoCacheFileImageSourceService
2021
{
22+
private static readonly BitmapFactory.Options _bitmapFactoryOptions =
23+
new()
24+
{
25+
InSampleSize = 1,
26+
InPreferredConfig = Bitmap.Config.Argb8888, // Uses less memory than ARGB_8888
27+
InDither = false,
28+
InTempStorage = new byte[32 * 1024], // 32KB buffer for decoding
29+
};
30+
2131
public override async Task<IImageSourceServiceResult?> LoadDrawableAsync(IImageSource imageSource, ImageView imageView,
2232
CancellationToken cancellationToken = default)
2333
{
2434
var fileImageSource = (INoCacheFileImageSource)imageSource;
2535

26-
if (!fileImageSource.IsEmpty)
36+
if (fileImageSource.IsEmpty)
2737
{
28-
var file = fileImageSource.File;
38+
return null;
39+
}
2940

30-
try
41+
var file = fileImageSource.File;
42+
43+
try
44+
{
45+
if (!Path.IsPathRooted(file) || !File.Exists(file))
3146
{
32-
if (!Path.IsPathRooted(file) || !File.Exists(file))
47+
var id = imageView.Context?.GetDrawableId(file) ?? -1;
48+
if (id > 0)
3349
{
34-
var id = imageView.Context?.GetDrawableId(file) ?? -1;
35-
if (id > 0)
36-
{
37-
imageView.SetImageResource(id);
38-
return new ImageSourceServiceLoadResult();
39-
}
50+
imageView.SetImageResource(id);
51+
return new ImageSourceServiceLoadResult();
4052
}
41-
42-
using var pathBitmap = await BitmapFactory.DecodeFileAsync(file);
43-
using var pathDrawable = new BitmapDrawable(Platform.AppContext.Resources, pathBitmap);
44-
imageView.SetImageDrawable(pathDrawable);
45-
46-
return new ImageSourceServiceLoadResult();
4753
}
48-
catch (Exception ex)
54+
55+
var pathDrawable = CreateDrawableModern(file, imageView.Context!);
56+
if (pathDrawable != null)
4957
{
50-
Logger?.LogWarning(ex, "Unable to load image file '{File}'.", file);
51-
throw;
58+
imageView.SetImageDrawable(pathDrawable);
59+
return new ImageSourceServiceLoadResult(() => pathDrawable.Dispose());
5260
}
53-
}
5461

55-
return null;
62+
return null;
63+
}
64+
catch (Exception ex)
65+
{
66+
this.Logger?.LogWarning(ex, "Unable to load image file '{File}'.", file);
67+
throw;
68+
}
5669
}
5770

5871
public override async Task<IImageSourceServiceResult<Drawable>?> GetDrawableAsync(IImageSource imageSource, Context context,
5972
CancellationToken cancellationToken = default)
6073
{
6174
var fileImageSource = (INoCacheFileImageSource)imageSource;
62-
if (!fileImageSource.IsEmpty)
75+
if (fileImageSource.IsEmpty)
6376
{
64-
var file = fileImageSource.File;
77+
return null;
78+
}
79+
80+
var file = fileImageSource.File;
6581

66-
try
82+
try
83+
{
84+
if (!Path.IsPathRooted(file) || !File.Exists(file))
6785
{
68-
if (!Path.IsPathRooted(file) || !File.Exists(file))
86+
var id = context?.GetDrawableId(file) ?? -1;
87+
if (id > 0)
6988
{
70-
var id = context?.GetDrawableId(file) ?? -1;
71-
if (id > 0)
89+
var d = context?.GetDrawable(id);
90+
if (d is not null)
7291
{
73-
var d = context?.GetDrawable(id);
74-
if (d is not null)
75-
{
76-
return new ImageSourceServiceResult(d);
77-
}
92+
return new ImageSourceServiceResult(d);
7893
}
7994
}
95+
}
8096

81-
var pathBitmap = BitmapFactory.DecodeFile(file);
82-
var pathDrawable = new BitmapDrawable(Platform.AppContext.Resources, pathBitmap);
83-
return new ImageSourceServiceResult(pathDrawable);
97+
var pathDrawable = CreateDrawableModern(file, context!);
98+
if (pathDrawable != null)
99+
{
100+
return new ImageSourceServiceResult(pathDrawable, () => pathDrawable.Dispose());
101+
}
102+
103+
return null;
104+
}
105+
catch (Exception ex)
106+
{
107+
this.Logger?.LogWarning(ex, "Unable to load image file '{File}'.", file);
108+
throw;
109+
}
110+
}
111+
112+
private static Drawable? CreateDrawableModern(string file, Context context)
113+
{
114+
try
115+
{
116+
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.P)
117+
{
118+
// Use modern ImageDecoder for Android API 28+
119+
var source = ImageDecoder.CreateSource(new Java.IO.File(file));
120+
var bitmap = ImageDecoder.DecodeBitmap(
121+
source,
122+
new ImageDecoderOnHeaderDecodedListener(
123+
decoder =>
124+
{
125+
decoder.SetTargetColorSpace(ColorSpace.Get(ColorSpace.Named.Srgb)!);
126+
decoder.MemorySizePolicy = ImageDecoderMemoryPolicy.Default;
127+
}));
128+
return new BitmapDrawable(context.Resources, bitmap);
84129
}
85-
catch (Exception ex)
130+
else
86131
{
87-
Logger?.LogWarning(ex, "Unable to load image file '{File}'.", file);
88-
throw;
132+
// Fallback to optimized BitmapFactory for older devices
133+
var bitmap = BitmapFactory.DecodeFile(file, _bitmapFactoryOptions);
134+
return new BitmapDrawable(context.Resources, bitmap);
89135
}
90136
}
137+
catch
138+
{
139+
return null;
140+
}
141+
}
142+
143+
private class ImageDecoderOnHeaderDecodedListener : Java.Lang.Object, ImageDecoder.IOnHeaderDecodedListener
144+
{
145+
private readonly Action<ImageDecoder> _onHeaderDecoded;
146+
147+
public ImageDecoderOnHeaderDecodedListener(Action<ImageDecoder> onHeaderDecoded)
148+
{
149+
_onHeaderDecoded = onHeaderDecoded;
150+
}
91151

92-
return null;
152+
public void OnHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source)
153+
{
154+
_onHeaderDecoded(decoder);
155+
}
93156
}
94157
}
95158

0 commit comments

Comments
 (0)