forked from BeyondDimension/SteamTools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImageLoader.cs
More file actions
358 lines (318 loc) · 13.2 KB
/
ImageLoader.cs
File metadata and controls
358 lines (318 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Widget;
using Android.OS;
using Square.OkHttp3;
using Square.Picasso;
using System.Application.Services;
using System.IO;
using JFile = Java.IO.File;
using JObject = Java.Lang.Object;
using JException = Java.Lang.Exception;
using AndroidApplication = Android.App.Application;
using Size = System.Drawing.Size;
using _ThisAssembly = System.Properties.ThisAssembly;
using System.Net.Http;
using System.Threading.Tasks;
namespace System.Application.UI
{
public static class ImageLoader
{
const string TAG = nameof(ImageLoader);
static readonly Lazy<Picasso> _Picasso = new(GetPicasso);
static Picasso GetPicasso()
{
Picasso.Builder picassoBuilder = new(AndroidApplication.Context);
picassoBuilder.IndicatorsEnabled(_ThisAssembly.Debuggable);
var cacheDir = CreateDefaultCacheDir();
var maxSize = CalculateDiskCacheSize(cacheDir);
var client = CreateOkHttpClient(cacheDir, maxSize);
OkHttp3Downloader downloader = new(client);
picassoBuilder.Downloader(downloader);
return picassoBuilder.Build();
}
public static Picasso Picasso => _Picasso.Value;
#region 高效加载大型位图 https://developer.android.google.cn/topic/performance/graphics/load-bitmap?hl=zh-cn#java
public static void SetImageSource(this ImageView imageView,
Stream? stream,
int targetResIdW = 0,
int targetResIdH = 0,
int targetW = 0,
int targetH = 0,
Bitmap.Config? inPreferredConfig = null)
{
if (stream == null || !stream.CanRead)
{
imageView.SetImageDrawable(null);
return;
}
try
{
Bitmap? bitmap = null;
if (stream.CanSeek)
{
if (targetResIdW > 0)
{
if (targetResIdH <= 0) targetResIdH = targetResIdW;
var resources = imageView.Resources!;
var reqWidth = resources.GetDimensionPixelSize(targetResIdW);
var reqHeight = resources.GetDimensionPixelSize(targetResIdH);
bitmap = DecodeSampledBitmapFromStream(stream, reqWidth, reqHeight, inPreferredConfig);
}
else if (targetW > 0)
{
if (targetH <= 0) targetH = targetW;
bitmap = DecodeSampledBitmapFromStream(stream, targetW, targetH, inPreferredConfig);
}
}
bitmap ??= BitmapFactory.DecodeStream(stream)!;
#if DEBUG
Log.Info(TAG,
$"Context: {imageView.Context!.GetType().Name}, " +
$"Bitmap.Width: {bitmap.Width}, " +
$"Bitmap.Height: {bitmap.Height}, " +
$"Bitmap.Config: {bitmap.GetConfig()}, " +
$"Bitmap.Size1: {IOPath.GetDisplayFileSizeString(bitmap.ByteCount)}, " +
$"Bitmap.Size2: {IOPath.GetDisplayFileSizeString(bitmap.AllocationByteCount)}.");
#endif
imageView.SetImageBitmap(bitmap);
}
catch (Exception e)
{
Log.Error(TAG, e, "SetImageSource(Stream) catch.");
}
}
static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth)
{
inSampleSize *= 2;
}
}
return inSampleSize;
}
static Bitmap DecodeSampledBitmapFromStream(Stream stream, int reqWidth, int reqHeight, Bitmap.Config? inPreferredConfig = null)
{
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new();
if (inPreferredConfig != null)
{
options.InPreferredConfig = inPreferredConfig;
}
options.InJustDecodeBounds = true;
BitmapFactory.DecodeStream(stream, null, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
stream.Position = 0;
return BitmapFactory.DecodeStream(stream, null, options)!;
}
#endregion
static Drawable ErrorDrawable => new ColorDrawable(Color.DarkRed);
static Drawable Placeholder => new ColorDrawable(new(AndroidApplication.Context.GetColorCompat(Resource.Color.grey_background)));
static RequestCreator? GetRequestCreator(string? requestUri,
Size targetSize = default,
Size targetSizeResId = default,
ScaleType scaleType = default,
bool useErrorDrawable = true,
bool usePlaceholder = true)
{
try
{
if (Browser2.IsHttpUrl(requestUri))
{
var requestCreator = Picasso.Load(requestUri);
if (usePlaceholder) requestCreator = requestCreator.Placeholder(Placeholder);
if (useErrorDrawable) requestCreator = requestCreator.Error(ErrorDrawable);
var useCenterCropDefault = false;
if (targetSize != default)
{
if (targetSize.Width > 0)
{
if (targetSize.Height <= 0) targetSize.Height = targetSize.Width;
requestCreator = requestCreator.Resize(targetSize.Width, targetSize.Height);
useCenterCropDefault = true;
}
}
else if (targetSizeResId != default)
{
if (targetSizeResId.Width > 0)
{
if (targetSizeResId.Height <= 0) targetSizeResId.Height = targetSizeResId.Width;
requestCreator = requestCreator.ResizeDimen(targetSizeResId.Width, targetSizeResId.Height);
useCenterCropDefault = true;
}
}
if (scaleType == ScaleType.CenterCrop || (useCenterCropDefault && scaleType == default))
{
requestCreator = requestCreator.CenterCrop();
}
else if (scaleType == ScaleType.CenterInside)
{
requestCreator = requestCreator.CenterInside();
}
return requestCreator;
}
}
catch (Exception e)
{
Log.Error(TAG, e, "GetRequestCreator catch, requestUri: {0}", requestUri);
}
return null;
}
public static void SetImageSource(this ImageView imageView,
string? requestUri,
int targetResIdW,
int targetResIdH = 0,
ScaleType scaleType = default)
{
try
{
var requestCreator = GetRequestCreator(requestUri, default, new(targetResIdW, targetResIdH), scaleType);
if (requestCreator == null)
{
imageView.SetImageDrawable(null);
}
else
{
requestCreator.Into(imageView, null, e =>
{
Log.Error(TAG, e, "SetImageSource.Callback catch, requestUri: {0}", requestUri);
});
}
}
catch (Exception e)
{
Log.Error(TAG, e, "SetImageSource catch, requestUri: {0}", requestUri);
}
}
public enum ScaleType
{
Default,
CenterCrop,
CenterInside,
}
// https://github.com/JakeWharton/picasso2-okhttp3-downloader/blob/master/src/main/java/com/jakewharton/picasso/OkHttp3Downloader.java
static JFile CreateDefaultCacheDir()
{
var cachePath = IHttpService.GetImagesCacheDirectory(null);
JFile cache = new(cachePath);
if (!cache.Exists())
{
//noinspection ResultOfMethodCallIgnored
cache.Mkdirs();
}
return cache;
}
const int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
const int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
static long CalculateDiskCacheSize(JFile dir)
{
long size = MIN_DISK_CACHE_SIZE;
try
{
var statFs = new StatFs(dir.AbsolutePath);
long available = statFs.BlockCountLong * statFs.BlockSizeLong;
// Target 2% of the total space.
size = available / 50;
}
catch (Java.Lang.IllegalArgumentException)
{
}
// Bound inside min/max size for disk cache.
return Math.Max(Math.Min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
}
static OkHttpClient CreateOkHttpClient(JFile cacheDir, long maxSize)
{
var s = IHttpPlatformHelperService.Instance;
var client = new OkHttpClient.Builder()
.Cache(new(cacheDir, maxSize))
.FollowRedirects(true)
.FollowSslRedirects(true)
.CallTimeout(GeneralHttpClientFactory.DefaultTimeoutMilliseconds, Java.Util.Concurrent.TimeUnit.Milliseconds)
.AddInterceptor(chain =>
{
var newRequest = chain.Request().NewBuilder()
.AddHeader("User-Agent", s.UserAgent)
.Build();
return chain.Proceed(newRequest);
})
.Build();
return client;
}
static Task<Bitmap?> GetBitmapCoreAsync(string? requestUri,
Size targetSize = default,
Size targetSizeResId = default,
ScaleType scaleType = default)
{
try
{
var requestCreator = GetRequestCreator(requestUri, targetSize, targetSizeResId, scaleType, useErrorDrawable: false, usePlaceholder: false);
if (requestCreator != null)
{
var tcs = new TaskCompletionSource<Bitmap?>();
requestCreator.Into(new TaskCompletionSourceTarget(tcs));
return tcs.Task;
}
}
catch (Exception e)
{
Log.Error(TAG, e, "GetBitmapCoreAsync catch, requestUri: {0}", requestUri);
}
return Task.FromResult<Bitmap?>(null);
}
/// <summary>
/// 从 HttpUrl 中加载图片并返回 <see cref="Bitmap"/> 实例,如果 Url 不合法或出现 <see cref="Exception"/> 将返回 <see langword="null"/>
/// </summary>
/// <param name="requestUri"></param>
/// <param name="targetSize">目标图片大小宽高</param>
/// <param name="targetSizeResId">目标图片大小宽高(R.dimen)</param>
/// <param name="scaleType">图片缩放类型</param>
/// <returns></returns>
public static async Task<Bitmap?> GetBitmapAsync(string? requestUri,
Size targetSize = default,
Size targetSizeResId = default,
ScaleType scaleType = default)
{
try
{
var bitmap = await GetBitmapCoreAsync(requestUri, targetSize, targetSizeResId, scaleType);
return bitmap;
}
catch (Exception e)
{
Log.Error(TAG, e, "GetBitmapAsync catch, requestUri: {0}", requestUri);
}
return null;
}
sealed class TaskCompletionSourceTarget : JObject, ITarget
{
readonly TaskCompletionSource<Bitmap?> tcs;
public TaskCompletionSourceTarget(TaskCompletionSource<Bitmap?> tcs) => this.tcs = tcs;
void ITarget.OnBitmapFailed(JException exception, Drawable _)
{
tcs.TrySetException(exception);
}
void ITarget.OnBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom _)
{
tcs.TrySetResult(bitmap);
}
void ITarget.OnPrepareLoad(Drawable _)
{
}
}
}
}