react-native-fast-image 9.3.3 → 9.3.5
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.
|
@@ -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
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -255,14 +255,15 @@ FastImage.resizeMode = resizeMode;
|
|
|
255
255
|
FastImage.cacheControl = cacheControl;
|
|
256
256
|
FastImage.priority = priority;
|
|
257
257
|
const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use";
|
|
258
|
-
|
|
258
|
+
const isSourceObject = (source) => typeof source === "object" && source !== null && !Array.isArray(source);
|
|
259
|
+
FastImage.preload = (sources) => Promise.resolve(react_native.NativeModules.FastImageView.preload(sources.map((s) => isSourceObject(s) ? s : {}))).then((results) => sources.map((source, i) => {
|
|
259
260
|
var _results$i;
|
|
260
261
|
if (Array.isArray(source)) return {
|
|
261
262
|
ok: false,
|
|
262
263
|
error: ONE_SOURCE,
|
|
263
264
|
uri: void 0
|
|
264
265
|
};
|
|
265
|
-
const uri = source ? source.uri : void 0;
|
|
266
|
+
const uri = isSourceObject(source) ? source.uri : void 0;
|
|
266
267
|
const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
|
|
267
268
|
if (!result.ok) return {
|
|
268
269
|
...result,
|
|
@@ -283,11 +284,11 @@ FastImage.configureCache = (limits = {}) => Promise.resolve(react_native.NativeM
|
|
|
283
284
|
FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({
|
|
284
285
|
ok: false,
|
|
285
286
|
error: ONE_SOURCE
|
|
286
|
-
}) : Promise.resolve(react_native.NativeModules.FastImageView.writeToCache(source
|
|
287
|
+
}) : Promise.resolve(react_native.NativeModules.FastImageView.writeToCache(isSourceObject(source) ? source : {}, file));
|
|
287
288
|
FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({
|
|
288
289
|
ok: false,
|
|
289
290
|
error: ONE_SOURCE
|
|
290
|
-
}) : Promise.resolve(react_native.NativeModules.FastImageView.getCachePath(source
|
|
291
|
+
}) : Promise.resolve(react_native.NativeModules.FastImageView.getCachePath(isSourceObject(source) ? source : {}));
|
|
291
292
|
const styles = react_native.StyleSheet.create({
|
|
292
293
|
fallbackImage: {
|
|
293
294
|
position: "absolute",
|
package/dist/index.js
CHANGED
|
@@ -228,14 +228,15 @@ FastImage.resizeMode = resizeMode;
|
|
|
228
228
|
FastImage.cacheControl = cacheControl;
|
|
229
229
|
FastImage.priority = priority;
|
|
230
230
|
const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use";
|
|
231
|
-
|
|
231
|
+
const isSourceObject = (source) => typeof source === "object" && source !== null && !Array.isArray(source);
|
|
232
|
+
FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.preload(sources.map((s) => isSourceObject(s) ? s : {}))).then((results) => sources.map((source, i) => {
|
|
232
233
|
var _results$i;
|
|
233
234
|
if (Array.isArray(source)) return {
|
|
234
235
|
ok: false,
|
|
235
236
|
error: ONE_SOURCE,
|
|
236
237
|
uri: void 0
|
|
237
238
|
};
|
|
238
|
-
const uri = source ? source.uri : void 0;
|
|
239
|
+
const uri = isSourceObject(source) ? source.uri : void 0;
|
|
239
240
|
const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
|
|
240
241
|
if (!result.ok) return {
|
|
241
242
|
...result,
|
|
@@ -256,11 +257,11 @@ FastImage.configureCache = (limits = {}) => Promise.resolve(NativeModules.FastIm
|
|
|
256
257
|
FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({
|
|
257
258
|
ok: false,
|
|
258
259
|
error: ONE_SOURCE
|
|
259
|
-
}) : Promise.resolve(NativeModules.FastImageView.writeToCache(source
|
|
260
|
+
}) : Promise.resolve(NativeModules.FastImageView.writeToCache(isSourceObject(source) ? source : {}, file));
|
|
260
261
|
FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({
|
|
261
262
|
ok: false,
|
|
262
263
|
error: ONE_SOURCE
|
|
263
|
-
}) : Promise.resolve(NativeModules.FastImageView.getCachePath(source
|
|
264
|
+
}) : Promise.resolve(NativeModules.FastImageView.getCachePath(isSourceObject(source) ? source : {}));
|
|
264
265
|
const styles = StyleSheet.create({
|
|
265
266
|
fallbackImage: {
|
|
266
267
|
position: "absolute",
|