-
Notifications
You must be signed in to change notification settings - Fork 6
/
ImagesCache.bas
456 lines (428 loc) · 14.3 KB
/
ImagesCache.bas
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
B4J=true
Group=Network
ModulesStructureVersion=1
Type=Class
Version=8.3
@EndOfDesignText@
Sub Class_Globals
Private Cache As B4XOrderedMap
Private xui As XUI
Private CurrentlyDownloadingURLs As Map
Private CurrentlyDownloadingIds As Map
Private CACHE_MAX_SIZE As Int = 20
Public const NSFW_URL = "nsfw", MISSING_URL = "missing", PLAY = "play", EMPTY = "empty" As String
Private PermCache As B4XOrderedMap
Type CachedBitmap (Bmp As B4XBitmap, Url As String, ReferenceCount As Int, IsPermanent As Boolean, IsGif As Boolean, GifFile As String)
Type ImageConsumer (CBitmaps As List, WaitingId As Int, Target As B4XView, _
GifTarget As B4XGifView, IsVisible As Boolean, PanelColor As Int, NoAnimation As Boolean)
Public NoImage As CachedBitmap
Private WaitingId As Int
Private const MAX_IMAGE_SIZE As Int = 1000
Private ImagesLoader As BitmapsAsync
Private GifViews As List
Private B4XGifView1 As B4XGifView
Private GifCounter As Int = 1
Private WebPLoader As WebP
Private const REMOVED_ID As Int = -1
Private RequestsManager1 As RequestsManager
Public RESIZE_FILLWIDTH = 1, RESIZE_FIT = 2, RESIZE_NONE = 0 , RESIZE_FILL_NO_DISTORTIONS = 3 As Int
Private NoGifView As B4XGifView
Private GifsViewsCreated As Int
End Sub
Public Sub Initialize
Cache.Initialize
CurrentlyDownloadingIds.Initialize
CurrentlyDownloadingURLs.Initialize
ImagesLoader.Initialize
ImagesLoader.MaxFileSize = 8 * 1024 * 1024
NoImage.Initialize
PermCache.Initialize
PermCache.Put(NSFW_URL, CreateImageCacheBmp(xui.LoadBitmap(File.DirAssets, "nsfw.74818f9.png"), NSFW_URL))
PermCache.Put(MISSING_URL, CreateImageCacheBmp(xui.LoadBitmap(File.DirAssets, Constants.MissingBitmapFileName), MISSING_URL))
PermCache.Put(PLAY, CreateImageCacheBmp(xui.LoadBitmap(File.DirAssets, "play.png"), PLAY))
PermCache.Put("null", PermCache.Get(MISSING_URL))
PermCache.Put("", PermCache.Get(MISSING_URL))
PermCache.Put(EMPTY, CreateImageCacheBmp(xui.LoadBitmap(File.DirAssets, "empty.png"), EMPTY))
GifViews.Initialize
WebPLoader.Initialize
For Each cb As CachedBitmap In PermCache.Values
cb.IsPermanent = True
Next
RequestsManager1.Initialize
End Sub
'should be called after a call to SetImage
Public Sub HoldAnotherImage(URL As String, Consumer As ImageConsumer, SetWhenReady As Boolean, ResizeMode As Int)
Dim id As Int = Consumer.WaitingId
Wait For (GetImage(URL, Consumer)) Complete (Result As CachedBitmap)
Result.ReferenceCount = Result.ReferenceCount - 1
If Consumer.WaitingId = id Then
Result.ReferenceCount = Result.ReferenceCount + 1
If SetWhenReady Then
ReleaseImage(Consumer)
Result.ReferenceCount = Result.ReferenceCount - 1
SetImageAfterReady(Result, Consumer, ResizeMode)
Else
Consumer.CBitmaps.Add(Result)
End If
End If
End Sub
Public Sub IsImageReady(URL As String) As Boolean
Return PermCache.ContainsKey(URL) Or Cache.ContainsKey(URL)
End Sub
Public Sub SetPermImageImmediately (URL As String, Consumer As ImageConsumer, ResizeMode As Int)
SetImageAfterReady(PermCache.Get(URL), Consumer, ResizeMode)
End Sub
Public Sub SetImage (Url As String, Consumer As ImageConsumer, ResizeMode As Int)
For Each cb As CachedBitmap In Consumer.CBitmaps
If cb.IsPermanent = False Then
If cb.Url = Url Then
cb.ReferenceCount = cb.ReferenceCount - 1
Else
Log("Previous image not released: " & Url & ", " & cb.Url)
End If
End If
Next
Consumer.CBitmaps.Clear
WaitingId = WaitingId + 1
Dim id As Int = WaitingId
Consumer.WaitingId = id
If PermCache.ContainsKey(Url) Then
SetImageAfterReady(PermCache.Get(Url), Consumer, ResizeMode)
Else If Cache.ContainsKey(Url) Then
SetImageAfterReady(Cache.Get(Url), Consumer, ResizeMode)
Else
Wait For (GetImage(Url, Consumer)) Complete (Result As CachedBitmap)
Result.ReferenceCount = Result.ReferenceCount - 1
If Consumer.WaitingId = id Then
SetImageAfterReady(Result, Consumer, ResizeMode)
End If
End If
End Sub
'increases the reference count
Private Sub SetImageAfterReady(Result As CachedBitmap, Consumer As ImageConsumer, ResizeMode As Int)
If ResizeMode = RESIZE_FILLWIDTH Then
FillImageViewWidth(Result, Consumer)
Else If ResizeMode = RESIZE_FIT Then
FitImageView(Result, Consumer)
Else If ResizeMode = RESIZE_FILL_NO_DISTORTIONS Then
FillNoDistortions(Result, Consumer)
Else
SetImageAndFillImageView(Result, Consumer)
End If
End Sub
Public Sub RemovePanelChildImageViews(pnl As B4XView)
For Each x As B4XView In pnl.GetAllViewsRecursive
If x.Tag Is ImageConsumer Then
B4XPages.MainPage.ImagesCache1.ReleaseImage(x.Tag)
End If
Next
End Sub
Private Sub FillNoDistortions(cb As CachedBitmap, Consumer As ImageConsumer)
Dim iv As B4XView = Consumer.Target
If iv.Parent.IsInitialized = False Or iv.Parent.Parent.IsInitialized = False Then Return
Dim wr As Float = iv.Parent.Width / cb.Bmp.Width
Dim hr As Float = iv.Parent.Height / cb.Bmp.Height
Dim r As Float = Max(wr, hr)
Dim width As Int = cb.Bmp.Width * r
Dim height As Int = cb.Bmp.Height * r
iv.SetLayoutAnimated(0, iv.Parent.Width / 2 - width / 2, iv.Parent.Height / 2 - height / 2, width, height)
SetImageAndFillImageView(cb, Consumer)
End Sub
Private Sub FillImageViewWidth(cb As CachedBitmap, Consumer As ImageConsumer)
Dim iv As B4XView = Consumer.Target
If iv.Parent.IsInitialized = False Or iv.Parent.Parent.IsInitialized = False Then Return
Dim bmpRatio As Float = cb.bmp.Height / cb.bmp.Width
Dim height As Int = iv.Parent.Width * bmpRatio
iv.SetLayoutAnimated(0, 0, iv.Parent.Height / 2 - height / 2, iv.Parent.Width, height)
SetImageAndFillImageView(cb, Consumer)
End Sub
Private Sub FitImageView(cb As CachedBitmap, Consumer As ImageConsumer)
Dim iv As B4XView = Consumer.Target
If iv.Parent.IsInitialized = False Then Return
Dim bmpRatio As Float = cb.bmp.Width / cb.bmp.Height
Dim viewRatio As Float = iv.Parent.Width / iv.Parent.Height
Dim Height, Width As Int
If viewRatio > bmpRatio Then
Height = iv.Parent.Height
Width = iv.Parent.Height * bmpRatio
Else
Width = iv.Parent.Width
Height = iv.Parent.Width / bmpRatio
End If
iv.SetLayoutAnimated(0, iv.Parent.Width / 2 - Width / 2, iv.Parent.Height / 2 - Height / 2, Width, Height)
SetImageAndFillImageView(cb, Consumer)
End Sub
Private Sub SetImageAndFillImageView(cb As CachedBitmap, Consumer As ImageConsumer)
Consumer.CBitmaps.Add(cb)
cb.ReferenceCount = cb.ReferenceCount + 1
If Consumer.IsVisible Then CallSetBitmap(Consumer)
End Sub
Public Sub SetConsumerVisibility(Consumer As ImageConsumer, NewState As Boolean)
If Consumer.IsVisible = NewState Then Return
Consumer.IsVisible = NewState
If Consumer.IsVisible And Consumer.CBitmaps.Size > 0 Then
CallSetBitmap(Consumer)
End If
End Sub
Private Sub CallSetBitmap(Consumer As ImageConsumer)
Dim Target As B4XView = Consumer.Target
Dim cb As CachedBitmap = Consumer.CBitmaps.Get(0)
If cb.IsGif Then
If Target.Parent.IsInitialized = False Then Return
If Consumer.GifTarget.IsInitialized = False Then
Consumer.GifTarget = GetGifView
' Log("add gif view. created = " & GifsViewsCreated & ", cached: " & GifViews.Size)
Consumer.GifTarget.mBase.RemoveViewFromParent
Target.Parent.AddView(Consumer.GifTarget.mBase, Target.Left, Target.Top, Target.Width, Target.Height)
Consumer.GifTarget.Base_Resize(Target.Width, Target.Height)
End If
Try
Consumer.GifTarget.SetGif(xui.DefaultFolder, cb.GifFile)
Catch
Log(LastException)
End Try
Else
ImageViewSetBitmap(Target, cb.Bmp)
End If
If Target.Parent.IsInitialized And Consumer.PanelColor <> 0 Then
Target.Parent.Color = Consumer.PanelColor
End If
If Consumer.NoAnimation = False And Target.Parent.IsInitialized Then
Target.Parent.Visible = False
Target.Parent.SetVisibleAnimated(100, True)
End If
End Sub
Private Sub GetImage (Url As String, Consumer As ImageConsumer) As ResumableSub
If PermCache.ContainsKey(Url) Then
Return PermCache.Get(Url)
End If
Dim MyWaitingId As Int = Consumer.WaitingId
Sleep(Rnd(100, 300))
Do While Consumer.IsVisible = False Or Consumer.WaitingId <> MyWaitingId
' Log("waiting: " & Url)
Sleep(Rnd(100, 300))
If Consumer.WaitingId <> MyWaitingId Then
' Log("removed before download: " & MyWaitingId)
Return PermCache.Get(MISSING_URL)
End If
If CurrentlyDownloadingURLs.Size <= 2 Then Exit
Loop
Do While CurrentlyDownloadingURLs.ContainsKey(Url)
Sleep(200)
Loop
If Cache.ContainsKey(Url) Then
Dim res As CachedBitmap = Cache.Get(Url)
res.ReferenceCount = res.ReferenceCount + 1
Return res
End If
Dim res As CachedBitmap
Try
Dim j As HttpJob
j.Initialize("", Me)
j.Download(Url)
CurrentlyDownloadingURLs.Put(Url, j)
CurrentlyDownloadingIds.Put(MyWaitingId, Url)
Wait For (j) JobDone(j As HttpJob)
If Consumer.WaitingId = MyWaitingId And j.Success Then
Dim ContentType As String = j.Response.ContentType
Dim IsGif As Boolean = ContentType = "image/gif"
Dim ShouldLoadRegularImage As Boolean = True
If IsGif Then
'In B4i the downloaded data cannot be accessed asynchronously. The input stream must be opened before the sub is paused.
#if B4i
Dim GifFile As String = LoadGif(j)
#else
Wait For (LoadGif(j)) Complete (GifFile As String)
#End If
If GifFile <> "" Then
Log("Gif: " & Url)
res.GifFile = GifFile
End If
Else If ContentType = "image/webp" Then
Log("loading webp: " & Url)
Dim bmp As B4XBitmap = WebPLoader.LoadWebP(Bit.InputStreamToBytes(j.GetInputStream))
ShouldLoadRegularImage = False
End If
If ShouldLoadRegularImage Then
Wait For (ImagesLoader.LoadFromHttpJob(j, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)) Complete (bmp As B4XBitmap)
End If
If bmp.IsInitialized Then
#if B4A
If ContentType = "image/jpeg" Then
bmp = RotateJpegIfNeeded(bmp, j)
End If
#end if
res = CreateImageCacheBmp(bmp, Url)
res.ReferenceCount = res.ReferenceCount + 1
res.IsGif = IsGif
res.GifFile = GifFile
Else
Log("error loading bitmap")
End If
Else
Log("ERROR: " & MyWaitingId & ": " & j.ErrorMessage)
End If
Catch
Log("Caught error: " & LastException)
End Try
j.Release
If res.IsInitialized Then
If Cache.Size > CACHE_MAX_SIZE Then
ClearCache
End If
Cache.Put(Url, res)
Else
res = PermCache.Get(MISSING_URL)
End If
CurrentlyDownloadingURLs.Remove(Url)
CurrentlyDownloadingIds.Remove(MyWaitingId)
Return res
End Sub
#if B4i
Private Sub LoadGif (job As HttpJob) As String
#Else
Private Sub LoadGif (job As HttpJob) As ResumableSub
#End If
GifCounter = GifCounter + 1
Dim FileName As String = GifCounter & ".gif"
File.Delete(xui.DefaultFolder, FileName)
Dim out As OutputStream = File.OpenOutput(xui.DefaultFolder, FileName, False)
#if B4i
'in B4i the downloaded data is not available when JobDone ends. This means that it must be done synchronously.
File.Copy2(job.GetInputStream, out)
out.Close
Return FileName
#else
Wait For (File.Copy2Async(job.GetInputStream, out)) Complete (Success As Boolean)
out.Close
If Success Then
Return FileName
Else
Return ""
End If
#End If
End Sub
#if B4A
Private Sub RotateJpegIfNeeded (bmp As B4XBitmap, job As HttpJob) As B4XBitmap
Dim p As Phone
If p.SdkVersion >= 24 Then
Dim ExifInterface As JavaObject
Dim in As InputStream = job.GetInputStream
ExifInterface.InitializeNewInstance("android.media.ExifInterface", Array(in))
Dim orientation As Int = ExifInterface.RunMethod("getAttribute", Array("Orientation"))
Select orientation
Case 3 '180
bmp = bmp.Rotate(180)
Case 6 '90
bmp = bmp.Rotate(90)
Case 8 '270
bmp = bmp.Rotate(270)
End Select
in.Close
End If
Return bmp
End Sub
#End If
Private Sub ClearCache
For Each ic As CachedBitmap In Cache.Values
If ic.ReferenceCount < 0 Then
Log("error: " & ic.ReferenceCount)
End If
If ic.ReferenceCount <= 0 Then
Cache.Remove(ic.Url)
' Log("removing: " & ic.Url & ", " & ic.ReferenceCount)
#if B4A
If ic.Bmp.IsInitialized Then
Dim jo As JavaObject = ic.Bmp
jo.RunMethod("recycle", Null)
End If
#End If
ic.Bmp = Null
'Log("release: " & ic.Url)
End If
Next
End Sub
Public Sub LogCacheState
Dim c As Int
For Each ic As CachedBitmap In Cache.Values
If ic.ReferenceCount > 0 Then
Log(ic.Url & " , " & ic.ReferenceCount)
c = c + ic.ReferenceCount
End If
Next
Log("Total references: " & c)
Log("Gifs created: " & GifsViewsCreated & ", gifs in cache: " & GifViews.Size)
End Sub
Private Sub CreateImageCacheBmp (Bmp As B4XBitmap, Url As String) As CachedBitmap
Dim t1 As CachedBitmap
t1.Initialize
t1.Bmp = Bmp
t1.Url = Url
t1.ReferenceCount = 0
Return t1
End Sub
Public Sub ReleaseImage(Consumer As ImageConsumer)
For Each cb As CachedBitmap In Consumer.CBitmaps
If cb.IsPermanent = False Then
cb.ReferenceCount = cb.ReferenceCount - 1
If cb.IsGif Then
If Consumer.GifTarget.IsInitialized Then
Consumer.GifTarget.mBase.GetView(0).SetBitmap(Null)
#if B4A
If Consumer.GifTarget.GifDrawable.IsInitialized Then
Consumer.GifTarget.GifDrawable.RunMethod("recycle", Null)
End If
#End If
' Log("release gif view: " & GifViews.Size)
Consumer.GifTarget.mBase.Tag = "removed"
GifViews.Add(Consumer.GifTarget)
Consumer.GifTarget.mBase.RemoveViewFromParent
Consumer.GifTarget = NoGifView
End If
End If
End If
Next
Consumer.CBitmaps.Clear
ImageViewSetBitmap(Consumer.Target, Null)
If CurrentlyDownloadingIds.ContainsKey(Consumer.WaitingId) Then
Dim url As String = CurrentlyDownloadingIds.Get(Consumer.WaitingId)
RequestsManager1.CancelRequest(url, CurrentlyDownloadingURLs.Get(url))
End If
Consumer.WaitingId = REMOVED_ID
End Sub
Private Sub ImageViewSetBitmap(Target As B4XView, bmp As B4XBitmap)
If Target Is ImageView Then
If bmp.IsInitialized = False Then
Target.SetBitmap(Null)
Else
Target.SetBitmap(bmp)
#if B4A
Dim iiv As ImageView = Target
iiv.Gravity = Gravity.FILL
#End If
End If
Else
CallSub2(Target.Tag, "SetBitmap", bmp)
End If
End Sub
Private Sub GetGifView As B4XGifView
If GifViews.Size > 0 Then
Dim gif As B4XGifView = GifViews.Get(0)
GifViews.RemoveAt(0)
Return gif
End If
Dim pnl As B4XView = xui.CreatePanel("")
pnl.SetLayoutAnimated(0, 0, 0, 102dip, 102dip)
pnl.LoadLayout("GifView")
GifsViewsCreated = GifsViewsCreated + 1
B4XGifView1.mBase.RemoveViewFromParent
'don't capture the touch events
#if B4J
Private jo = B4XGifView1.mBase As JavaObject
jo.RunMethod("setMouseTransparent", Array(True))
#else if B4i
Dim v As View = B4XGifView1.mBase
v.UserInteractionEnabled = False
#end if
Return B4XGifView1
End Sub