react-native-fast-image 9.3.2 → 9.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -280,6 +280,11 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
280
280
|
.asFile()
|
|
281
281
|
.load(model)
|
|
282
282
|
.apply(options)
|
|
283
|
+
// Not kept in or taken from the memory cache (asFile() skips
|
|
284
|
+
// it, but the options turn it back on): the file is only there
|
|
285
|
+
// while the disk cache keeps it, and Glide gave the path of
|
|
286
|
+
// one that clearDiskCache had deleted.
|
|
287
|
+
.skipMemoryCache(true)
|
|
283
288
|
.listener(new RequestListener<File>() {
|
|
284
289
|
@Override
|
|
285
290
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
|
@@ -307,6 +312,8 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
307
312
|
.asFile()
|
|
308
313
|
.load(model)
|
|
309
314
|
.apply(options)
|
|
315
|
+
// On disk only, as in loadFile.
|
|
316
|
+
.skipMemoryCache(true)
|
|
310
317
|
.onlyRetrieveFromCache(true)
|
|
311
318
|
.submit()
|
|
312
319
|
.get();
|
|
@@ -322,8 +329,10 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
322
329
|
return result;
|
|
323
330
|
}
|
|
324
331
|
|
|
325
|
-
// Where getCachePath looks in the caches and downloads `web` images
|
|
326
|
-
//
|
|
332
|
+
// Where getCachePath looks in the caches and downloads `web` images,
|
|
333
|
+
// writeToCache stores files, configureCache measures the disk cache and
|
|
334
|
+
// clearDiskCache empties it (blocking calls, off the UI and modules
|
|
335
|
+
// threads).
|
|
327
336
|
private static final Executor cachePathExecutor = Executors.newSingleThreadExecutor();
|
|
328
337
|
|
|
329
338
|
private static WritableMap pathResult(File file) {
|
|
@@ -590,24 +599,33 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
590
599
|
});
|
|
591
600
|
}
|
|
592
601
|
|
|
593
|
-
// On
|
|
594
|
-
// thread.
|
|
602
|
+
// On cachePathExecutor: Glide clears its disk cache off the main thread,
|
|
603
|
+
// and the native modules' thread runs every module's async methods (e.g.
|
|
604
|
+
// a fetch()'s request), which would wait for it. It's also in order with
|
|
605
|
+
// the disk work of getCachePath, writeToCache and configureCache there.
|
|
595
606
|
@ReactMethod
|
|
596
|
-
public void clearDiskCache(Promise promise) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
607
|
+
public void clearDiskCache(final Promise promise) {
|
|
608
|
+
final ReactApplicationContext context = getReactApplicationContext();
|
|
609
|
+
cachePathExecutor.execute(new Runnable() {
|
|
610
|
+
@Override
|
|
611
|
+
public void run() {
|
|
612
|
+
try {
|
|
613
|
+
FastImageGlide.getRegistered(context).clearDiskCache();
|
|
614
|
+
} catch (RuntimeException e) {
|
|
615
|
+
promise.resolve(failure(e));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// And the HTTP cache of `cache: 'web'` images, which Glide
|
|
619
|
+
// doesn't cache (set up when FastImage's components are
|
|
620
|
+
// registered).
|
|
621
|
+
try {
|
|
622
|
+
FastImageOkHttpProgressGlideModule.clearWebCache();
|
|
623
|
+
} catch (IOException e) {
|
|
624
|
+
// Cleared as far as it could.
|
|
625
|
+
}
|
|
626
|
+
promise.resolve(cleared());
|
|
627
|
+
}
|
|
628
|
+
});
|
|
611
629
|
}
|
|
612
630
|
|
|
613
631
|
private static WritableMap cleared() {
|
|
@@ -11,13 +11,18 @@ import android.graphics.Bitmap;
|
|
|
11
11
|
import android.graphics.Canvas;
|
|
12
12
|
import android.graphics.Matrix;
|
|
13
13
|
import android.graphics.Paint;
|
|
14
|
+
import android.graphics.PixelFormat;
|
|
14
15
|
import android.graphics.PorterDuff;
|
|
15
16
|
import android.graphics.PorterDuffXfermode;
|
|
17
|
+
import android.graphics.RectF;
|
|
16
18
|
import android.graphics.Shader;
|
|
17
19
|
import android.graphics.drawable.AnimatedImageDrawable;
|
|
18
20
|
import android.graphics.drawable.BitmapDrawable;
|
|
19
21
|
import android.graphics.drawable.Drawable;
|
|
22
|
+
import android.graphics.drawable.GradientDrawable;
|
|
20
23
|
import android.os.Build;
|
|
24
|
+
import android.view.View;
|
|
25
|
+
import android.view.ViewParent;
|
|
21
26
|
|
|
22
27
|
import androidx.annotation.NonNull;
|
|
23
28
|
import androidx.annotation.Nullable;
|
|
@@ -342,6 +347,14 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
342
347
|
private ValueAnimator mFade;
|
|
343
348
|
@Nullable
|
|
344
349
|
private Bitmap mFadeFrom;
|
|
350
|
+
// Or, instead of a copy, the defaultSource the view showed, with the
|
|
351
|
+
// matrix it drew it with: FastImage's own drawable, which Glide doesn't
|
|
352
|
+
// recycle, so it's drawn again as it was, rather than copied into a
|
|
353
|
+
// bitmap the size of the view for every image that fades in over it.
|
|
354
|
+
@Nullable
|
|
355
|
+
private Drawable mFadeFromDrawable;
|
|
356
|
+
@Nullable
|
|
357
|
+
private Matrix mFadeFromMatrix;
|
|
345
358
|
private float mFadeProgress = 1f;
|
|
346
359
|
private final Paint mFadeFromPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
|
347
360
|
private final Paint mFadeInPaint = new Paint();
|
|
@@ -357,7 +370,14 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
357
370
|
if (width <= 0 || height <= 0) return;
|
|
358
371
|
// What the view shows now, including a fade in progress.
|
|
359
372
|
Bitmap from = null;
|
|
360
|
-
|
|
373
|
+
Drawable fromDrawable = null;
|
|
374
|
+
Matrix fromMatrix = null;
|
|
375
|
+
Drawable shown = getDrawable();
|
|
376
|
+
if (shown != null && mShowsDefaultSource && mFade == null) {
|
|
377
|
+
// Its own state, so its alpha (see drawFadeFrom) is only its own.
|
|
378
|
+
fromDrawable = shown.mutate();
|
|
379
|
+
fromMatrix = new Matrix(getImageMatrix());
|
|
380
|
+
} else if (shown != null) {
|
|
361
381
|
try {
|
|
362
382
|
from = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
363
383
|
onDraw(new Canvas(from));
|
|
@@ -371,6 +391,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
371
391
|
}
|
|
372
392
|
endFade();
|
|
373
393
|
mFadeFrom = from;
|
|
394
|
+
mFadeFromDrawable = fromDrawable;
|
|
395
|
+
mFadeFromMatrix = fromMatrix;
|
|
374
396
|
mFadeProgress = 0f;
|
|
375
397
|
final ValueAnimator fade = ValueAnimator.ofFloat(0f, 1f);
|
|
376
398
|
fade.setDuration(duration);
|
|
@@ -401,10 +423,45 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
401
423
|
mFadeFrom.recycle();
|
|
402
424
|
mFadeFrom = null;
|
|
403
425
|
}
|
|
426
|
+
mFadeFromDrawable = null;
|
|
427
|
+
mFadeFromMatrix = null;
|
|
404
428
|
mFadeProgress = 1f;
|
|
405
429
|
invalidate();
|
|
406
430
|
}
|
|
407
431
|
|
|
432
|
+
// Draws the defaultSource the view showed when the fade started (with
|
|
433
|
+
// `alpha`), as ImageView draws its drawable: inside the padding
|
|
434
|
+
// (cropToPadding), with the matrix it had then.
|
|
435
|
+
private void drawFadeFrom(Canvas canvas, Drawable drawable, int alpha) {
|
|
436
|
+
int count = canvas.save();
|
|
437
|
+
if (getCropToPadding()) {
|
|
438
|
+
int scrollX = getScrollX();
|
|
439
|
+
int scrollY = getScrollY();
|
|
440
|
+
canvas.clipRect(scrollX + getPaddingLeft(), scrollY + getPaddingTop(),
|
|
441
|
+
scrollX + getWidth() - getPaddingRight(), scrollY + getHeight() - getPaddingBottom());
|
|
442
|
+
}
|
|
443
|
+
canvas.translate(getPaddingLeft(), getPaddingTop());
|
|
444
|
+
if (mFadeFromMatrix != null) canvas.concat(mFadeFromMatrix);
|
|
445
|
+
if (alpha == 255) {
|
|
446
|
+
drawable.draw(canvas);
|
|
447
|
+
} else if (drawable instanceof BitmapDrawable || drawable instanceof GradientDrawable) {
|
|
448
|
+
// A bitmap or a shape applies its alpha as it's drawn.
|
|
449
|
+
int previous = drawable.getAlpha();
|
|
450
|
+
drawable.setAlpha(alpha);
|
|
451
|
+
drawable.draw(canvas);
|
|
452
|
+
drawable.setAlpha(previous);
|
|
453
|
+
} else {
|
|
454
|
+
// Others through a layer: a vector drawable, or an animated GIF or
|
|
455
|
+
// WebP resource (an AnimatedImageDrawable on Android 9+), keeps
|
|
456
|
+
// its alpha as a property the RenderThread reads when it syncs
|
|
457
|
+
// the frame, after it's set back here.
|
|
458
|
+
int layer = canvas.saveLayerAlpha(null, alpha);
|
|
459
|
+
drawable.draw(canvas);
|
|
460
|
+
canvas.restoreToCount(layer);
|
|
461
|
+
}
|
|
462
|
+
canvas.restoreToCount(count);
|
|
463
|
+
}
|
|
464
|
+
|
|
408
465
|
@SuppressWarnings("deprecation")
|
|
409
466
|
@Override
|
|
410
467
|
protected void onDraw(Canvas canvas) {
|
|
@@ -415,23 +472,118 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
415
472
|
int width = getWidth();
|
|
416
473
|
int height = getHeight();
|
|
417
474
|
int alpha = Math.round(255 * mFadeProgress);
|
|
418
|
-
if (
|
|
475
|
+
if (drawFadeWithoutLayers(canvas, alpha)) return;
|
|
476
|
+
if (mFadeFrom == null && mFadeFromDrawable == null) {
|
|
419
477
|
// Fading in over nothing: one layer.
|
|
420
478
|
int count = canvas.saveLayerAlpha(0, 0, width, height, alpha, Canvas.ALL_SAVE_FLAG);
|
|
421
479
|
super.onDraw(canvas);
|
|
422
480
|
canvas.restoreToCount(count);
|
|
423
481
|
return;
|
|
424
482
|
}
|
|
483
|
+
// Two layers, which add up to the cross-dissolve whatever the image's
|
|
484
|
+
// transparency.
|
|
425
485
|
// Canvas.ALL_SAVE_FLAG: the saveLayer without flags is API 21+.
|
|
426
486
|
int count = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
|
|
427
|
-
|
|
428
|
-
|
|
487
|
+
if (mFadeFromDrawable != null) {
|
|
488
|
+
drawFadeFrom(canvas, mFadeFromDrawable, 255 - alpha);
|
|
489
|
+
} else {
|
|
490
|
+
mFadeFromPaint.setAlpha(255 - alpha);
|
|
491
|
+
canvas.drawBitmap(mFadeFrom, 0, 0, mFadeFromPaint);
|
|
492
|
+
}
|
|
429
493
|
mFadeInPaint.setAlpha(alpha);
|
|
430
494
|
canvas.saveLayer(0, 0, width, height, mFadeInPaint, Canvas.ALL_SAVE_FLAG);
|
|
431
495
|
super.onDraw(canvas);
|
|
432
496
|
canvas.restoreToCount(count);
|
|
433
497
|
}
|
|
434
498
|
|
|
499
|
+
// Draws the fade without layers, each of which is a buffer drawn again
|
|
500
|
+
// every frame, when the image takes an alpha in one drawing (a bitmap or
|
|
501
|
+
// GIF): over nothing, the image with the fade's alpha; over
|
|
502
|
+
// what the view showed, if the image is opaque and covers it, that and
|
|
503
|
+
// the image over it with the alpha, which is the same cross-dissolve,
|
|
504
|
+
// from * (1 - t) + image * t: photos without transparency, most images.
|
|
505
|
+
// Over what the view showed, only when nothing draws the view with an
|
|
506
|
+
// alpha (see drawnWithAlpha).
|
|
507
|
+
// Not an animated WebP or AVIF (an AnimatedImageDrawable): its alpha is a
|
|
508
|
+
// property the RenderThread reads when it syncs the frame, after it's set
|
|
509
|
+
// back here, so it would show without the fade.
|
|
510
|
+
// Returns false, having drawn nothing, otherwise.
|
|
511
|
+
private boolean drawFadeWithoutLayers(Canvas canvas, int alpha) {
|
|
512
|
+
Drawable image = getDrawable();
|
|
513
|
+
boolean drawsOnce = image instanceof BitmapDrawable || image instanceof GifDrawable;
|
|
514
|
+
if (!drawsOnce) return false;
|
|
515
|
+
boolean over = mFadeFrom != null || mFadeFromDrawable != null;
|
|
516
|
+
if (over && (!coversOpaquely(image) || drawnWithAlpha())) return false;
|
|
517
|
+
if (mFadeFromDrawable != null) {
|
|
518
|
+
drawFadeFrom(canvas, mFadeFromDrawable, 255);
|
|
519
|
+
} else if (mFadeFrom != null) {
|
|
520
|
+
mFadeFromPaint.setAlpha(255);
|
|
521
|
+
canvas.drawBitmap(mFadeFrom, 0, 0, mFadeFromPaint);
|
|
522
|
+
}
|
|
523
|
+
// Without the drawable asking the view to draw again.
|
|
524
|
+
Drawable.Callback callback = image.getCallback();
|
|
525
|
+
image.setCallback(null);
|
|
526
|
+
int previous = image.getAlpha();
|
|
527
|
+
image.setAlpha(alpha);
|
|
528
|
+
super.onDraw(canvas);
|
|
529
|
+
image.setAlpha(previous);
|
|
530
|
+
image.setCallback(callback);
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Whether the view, or a view it's in, has an alpha (an opacity), or a
|
|
535
|
+
// view animation (android.view.animation, e.g. an AlphaAnimation), which
|
|
536
|
+
// can apply one without changing the view's.
|
|
537
|
+
// React Native's View (unless it has needsOffscreenAlphaCompositing) and
|
|
538
|
+
// an ImageView without a background have no layer of their own for it,
|
|
539
|
+
// so it applies to each drawing in them: what the view showed, drawn at
|
|
540
|
+
// full strength, would show through the image over it until the fade
|
|
541
|
+
// ends. The layers are right with any alpha: it applies to the
|
|
542
|
+
// cross-dissolve they add up to. Checked on every frame of the fade, as
|
|
543
|
+
// an opacity can change during it.
|
|
544
|
+
// Not the transition alpha, which only Android's Transition fades
|
|
545
|
+
// (android.transition.Fade) set: getTransitionAlpha is public from API 29
|
|
546
|
+
// only (hidden on Android 5 to 9, and the library compiles against 28
|
|
547
|
+
// when an app doesn't set compileSdkVersion). React Native's opacity,
|
|
548
|
+
// Animated, LayoutAnimation and Reanimated set the view's alpha, and
|
|
549
|
+
// react-native-screens' fragment animations run on a container with a
|
|
550
|
+
// layer of its own.
|
|
551
|
+
private boolean drawnWithAlpha() {
|
|
552
|
+
View view = this;
|
|
553
|
+
while (true) {
|
|
554
|
+
if (view.getAlpha() < 1 || view.getAnimation() != null) {
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
ViewParent parent = view.getParent();
|
|
558
|
+
if (!(parent instanceof View)) return false;
|
|
559
|
+
view = (View) parent;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private final RectF mFadeImageRect = new RectF();
|
|
564
|
+
|
|
565
|
+
// Whether the image, as the view draws it, is opaque and covers all of
|
|
566
|
+
// what the view showed: inside the padding (cropToPadding), or the whole
|
|
567
|
+
// view.
|
|
568
|
+
private boolean coversOpaquely(Drawable image) {
|
|
569
|
+
if (image.getOpacity() != PixelFormat.OPAQUE || getColorFilter() != null) return false;
|
|
570
|
+
mFadeImageRect.set(image.getBounds());
|
|
571
|
+
getImageMatrix().mapRect(mFadeImageRect);
|
|
572
|
+
mFadeImageRect.offset(getPaddingLeft(), getPaddingTop());
|
|
573
|
+
boolean crop = getCropToPadding();
|
|
574
|
+
float left = crop ? getPaddingLeft() : 0;
|
|
575
|
+
float top = crop ? getPaddingTop() : 0;
|
|
576
|
+
float right = getWidth() - (crop ? getPaddingRight() : 0);
|
|
577
|
+
float bottom = getHeight() - (crop ? getPaddingBottom() : 0);
|
|
578
|
+
// Within float error only: an edge half a pixel in leaves a row or
|
|
579
|
+
// column of pixels uncovered (which one depends on how it's
|
|
580
|
+
// rasterized; e.g. contain, with the image a pixel shorter than the
|
|
581
|
+
// view), where what the view showed would stay until the fade ends.
|
|
582
|
+
float error = 0.01f;
|
|
583
|
+
return mFadeImageRect.left <= left + error && mFadeImageRect.top <= top + error
|
|
584
|
+
&& mFadeImageRect.right >= right - error && mFadeImageRect.bottom >= bottom - error;
|
|
585
|
+
}
|
|
586
|
+
|
|
435
587
|
@Override
|
|
436
588
|
protected void onDetachedFromWindow() {
|
|
437
589
|
endFade();
|
|
@@ -651,9 +803,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
651
803
|
updateImageMatrix();
|
|
652
804
|
}
|
|
653
805
|
|
|
806
|
+
// Whether the view shows its defaultSource (or its tiled copy, which has
|
|
807
|
+
// its bitmap): FastImage's own drawable, which Glide doesn't recycle (see
|
|
808
|
+
// startFade).
|
|
809
|
+
private boolean mShowsDefaultSource = false;
|
|
810
|
+
|
|
654
811
|
// Repeats what the view shows (the loaded image, and defaultSource).
|
|
655
812
|
@Override
|
|
656
813
|
public void setImageDrawable(@Nullable Drawable drawable) {
|
|
814
|
+
mShowsDefaultSource = drawable != null && drawable == mDefaultSource;
|
|
657
815
|
super.setImageDrawable(mRepeat ? tiled(drawable) : drawable);
|
|
658
816
|
updateImageMatrix();
|
|
659
817
|
}
|
|
@@ -905,6 +1063,11 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
905
1063
|
mFadeFrom = null;
|
|
906
1064
|
invalidate();
|
|
907
1065
|
}
|
|
1066
|
+
if (mFadeFromDrawable != null && (w != oldw || h != oldh)) {
|
|
1067
|
+
mFadeFromDrawable = null;
|
|
1068
|
+
mFadeFromMatrix = null;
|
|
1069
|
+
invalidate();
|
|
1070
|
+
}
|
|
908
1071
|
if (!switchSourceIfResized()) reloadIfResized();
|
|
909
1072
|
}
|
|
910
1073
|
|