react-native-fast-image 8.28.1 → 8.30.0
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.
- package/README.md +12 -19
- package/RNFastImage.podspec +3 -0
- package/android/build.gradle +23 -3
- package/android/consumer-rules.pro +9 -5
- package/android/src/glide-4.15/java/com/dylanvann/fastimage/FastImageAnimated.java +244 -0
- package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +36 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +13 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +141 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +141 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +6 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSharedDownloads.java +292 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +27 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +93 -0
- package/dist/index.d.ts +9 -6
- package/ios/FastImage/FFFAnimatedWebPCoder.h +10 -0
- package/ios/FastImage/FFFAnimatedWebPCoder.m +41 -0
- package/ios/FastImage/FFFastImageSource.m +24 -7
- package/package.json +3 -1
|
@@ -13,11 +13,14 @@ import android.graphics.Paint;
|
|
|
13
13
|
import android.graphics.PorterDuff;
|
|
14
14
|
import android.graphics.PorterDuffXfermode;
|
|
15
15
|
import android.graphics.Shader;
|
|
16
|
+
import android.graphics.drawable.AnimatedImageDrawable;
|
|
16
17
|
import android.graphics.drawable.BitmapDrawable;
|
|
17
18
|
import android.graphics.drawable.Drawable;
|
|
19
|
+
import android.os.Build;
|
|
18
20
|
|
|
19
21
|
import androidx.annotation.NonNull;
|
|
20
22
|
import androidx.annotation.Nullable;
|
|
23
|
+
import androidx.annotation.RequiresApi;
|
|
21
24
|
import androidx.appcompat.widget.AppCompatImageView;
|
|
22
25
|
import androidx.core.view.ViewCompat;
|
|
23
26
|
|
|
@@ -29,6 +32,7 @@ import com.bumptech.glide.load.model.GlideUrl;
|
|
|
29
32
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
|
|
30
33
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
31
34
|
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
|
35
|
+
import com.bumptech.glide.request.Request;
|
|
32
36
|
import com.bumptech.glide.request.RequestOptions;
|
|
33
37
|
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
|
34
38
|
import com.bumptech.glide.request.target.SizeReadyCallback;
|
|
@@ -179,6 +183,17 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
179
183
|
mOwnGif.startFromFirstFrame();
|
|
180
184
|
}
|
|
181
185
|
}
|
|
186
|
+
if (getDrawable() instanceof FastImageAnimatable) {
|
|
187
|
+
((FastImageAnimatable) getDrawable()).setLoopCount(mLoopCount, !mPaused);
|
|
188
|
+
}
|
|
189
|
+
if (mAnimated != null && getDrawable() == mAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
190
|
+
AnimatedImageDrawable animated = (AnimatedImageDrawable) mAnimated;
|
|
191
|
+
applyRepeatCount(animated);
|
|
192
|
+
if (!mPaused) {
|
|
193
|
+
animated.stop();
|
|
194
|
+
animated.start();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
182
197
|
}
|
|
183
198
|
|
|
184
199
|
// Pauses GIFs on the frame they're showing (the view's own animation).
|
|
@@ -359,6 +374,18 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
359
374
|
FastImageGif.resume(mOwnGif);
|
|
360
375
|
}
|
|
361
376
|
}
|
|
377
|
+
if (getDrawable() instanceof FastImageAnimatable) {
|
|
378
|
+
((FastImageAnimatable) getDrawable()).setPaused(paused);
|
|
379
|
+
}
|
|
380
|
+
if (mAnimated != null && getDrawable() == mAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
381
|
+
if (paused) {
|
|
382
|
+
((AnimatedImageDrawable) mAnimated).stop();
|
|
383
|
+
} else {
|
|
384
|
+
// Plays from its first frame: an AnimatedImageDrawable has no
|
|
385
|
+
// way to resume.
|
|
386
|
+
((AnimatedImageDrawable) mAnimated).start();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
362
389
|
}
|
|
363
390
|
|
|
364
391
|
// The GIF this view shows as its own animation (FastImageGif), recycled
|
|
@@ -366,12 +393,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
366
393
|
@Nullable
|
|
367
394
|
private GifDrawable mOwnGif;
|
|
368
395
|
|
|
396
|
+
// An animated WebP or AVIF this view shows: an AnimatedImageDrawable, which
|
|
397
|
+
// Glide 4.15+ decodes with Android's ImageDecoder (API 28+; AVIF 31+).
|
|
398
|
+
@Nullable
|
|
399
|
+
private Drawable mAnimated;
|
|
400
|
+
|
|
369
401
|
// Shows each GIF as this view's own animation. Glide's target also starts
|
|
370
402
|
// and stops it with the Activity, as it does Glide's own GifDrawable.
|
|
371
403
|
private final class OwnGifTarget extends DrawableImageViewTarget {
|
|
372
404
|
// Shown instead of the placeholder while loading (see shownCopy).
|
|
373
405
|
@Nullable
|
|
374
406
|
private Drawable mMeanwhile;
|
|
407
|
+
// Counts images ready (a thumbnail, then the image), so a copy
|
|
408
|
+
// decoded for one isn't shown over a later one.
|
|
409
|
+
private int mReady = 0;
|
|
375
410
|
|
|
376
411
|
OwnGifTarget(@Nullable Drawable meanwhile) {
|
|
377
412
|
super(FastImageViewWithUrl.this);
|
|
@@ -387,6 +422,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
387
422
|
|
|
388
423
|
@Override
|
|
389
424
|
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
|
425
|
+
int ready = ++mReady;
|
|
390
426
|
if (resource instanceof BitmapDrawable) {
|
|
391
427
|
if (mPixelated) {
|
|
392
428
|
// Scaled by the view without filtering: sharp pixels.
|
|
@@ -406,9 +442,48 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
406
442
|
return;
|
|
407
443
|
}
|
|
408
444
|
}
|
|
445
|
+
if (resource instanceof FastImageAnimatable) {
|
|
446
|
+
// An APNG: each view's is its own (FastImageApngRenderer).
|
|
447
|
+
FastImageAnimatable animatable = (FastImageAnimatable) resource;
|
|
448
|
+
animatable.setLoopCount(mLoopCount, false);
|
|
449
|
+
// The target starts it; paused, it stays on its first frame.
|
|
450
|
+
super.onResourceReady(resource, transition);
|
|
451
|
+
if (mPaused) animatable.setPaused(true);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && resource instanceof AnimatedImageDrawable) {
|
|
455
|
+
if (!FastImageAnimated.shownElsewhere(resource, FastImageViewWithUrl.this)) {
|
|
456
|
+
showAnimated((AnimatedImageDrawable) resource, transition);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// Another view shows it: this one shows its own, once it's
|
|
460
|
+
// decoded (meanwhile, what it showed), if it's still this
|
|
461
|
+
// view's latest image. Failing that, it shares that view's.
|
|
462
|
+
Drawable shared = resource;
|
|
463
|
+
FastImageAnimated.copy(resource, copy -> {
|
|
464
|
+
Request request = getRequest();
|
|
465
|
+
if (ready != mReady || request == null || !request.isComplete()) return;
|
|
466
|
+
showAnimated((AnimatedImageDrawable) (copy != null ? copy : shared), transition);
|
|
467
|
+
});
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
409
470
|
super.onResourceReady(resource, transition);
|
|
410
471
|
}
|
|
411
472
|
|
|
473
|
+
@RequiresApi(Build.VERSION_CODES.P)
|
|
474
|
+
private void showAnimated(AnimatedImageDrawable animated, @Nullable Transition<? super Drawable> transition) {
|
|
475
|
+
applyRepeatCount(animated);
|
|
476
|
+
// From its first frame, with this view's repeat count: the target
|
|
477
|
+
// starts it, but a drawable another view showed can still be
|
|
478
|
+
// playing (start() does nothing then), and a repeat count set
|
|
479
|
+
// during a play doesn't always apply to that play. Paused, it
|
|
480
|
+
// waits on its first frame.
|
|
481
|
+
animated.stop();
|
|
482
|
+
super.onResourceReady(animated, transition);
|
|
483
|
+
mAnimated = animated;
|
|
484
|
+
if (mPaused) animated.stop();
|
|
485
|
+
}
|
|
486
|
+
|
|
412
487
|
// The target starts animations again when the Activity does: not a
|
|
413
488
|
// paused one, and the view's own GIF continues its loop count (start()
|
|
414
489
|
// would count again).
|
|
@@ -431,6 +506,9 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
431
506
|
mOwnGif.recycle();
|
|
432
507
|
mOwnGif = null;
|
|
433
508
|
}
|
|
509
|
+
if (mAnimated != null && mAnimated != resource) {
|
|
510
|
+
mAnimated = null;
|
|
511
|
+
}
|
|
434
512
|
}
|
|
435
513
|
}
|
|
436
514
|
|
|
@@ -440,6 +518,21 @@ class FastImageViewWithUrl extends AppCompatImageView {
|
|
|
440
518
|
: mLoopCount);
|
|
441
519
|
}
|
|
442
520
|
|
|
521
|
+
// The loop prop for an animated WebP or AVIF: its repeat count is the
|
|
522
|
+
// plays after the first. Not set, it plays as many times as the file says:
|
|
523
|
+
// the repeat count it was decoded with, which an earlier loop (this
|
|
524
|
+
// view's, or another view's that showed the same drawable) may have
|
|
525
|
+
// changed.
|
|
526
|
+
@RequiresApi(Build.VERSION_CODES.P)
|
|
527
|
+
private void applyRepeatCount(AnimatedImageDrawable animated) {
|
|
528
|
+
if (mLoopCount == -1) {
|
|
529
|
+
Integer own = FastImageAnimated.repeatCount(animated);
|
|
530
|
+
if (own != null) animated.setRepeatCount(own);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
animated.setRepeatCount(mLoopCount == 0 ? AnimatedImageDrawable.REPEAT_INFINITE : mLoopCount - 1);
|
|
534
|
+
}
|
|
535
|
+
|
|
443
536
|
// resizeMode repeat: the image (a GIF's first frame) repeated from the
|
|
444
537
|
// top-left at its own size, scaled down to fit the view if it's larger,
|
|
445
538
|
// as React Native's Image does. The view fills with it (FIT_XY).
|
package/dist/index.d.ts
CHANGED
|
@@ -133,9 +133,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
133
133
|
*/
|
|
134
134
|
recyclingKey?: string | null;
|
|
135
135
|
/**
|
|
136
|
-
* How many times an animated image (GIF, animated WebP
|
|
137
|
-
* own loop count by default, `true` to
|
|
138
|
-
* or a number of times. Changing it
|
|
136
|
+
* How many times an animated image (GIF, animated WebP, APNG, and animated
|
|
137
|
+
* AVIF on Android) plays: the file's own loop count by default, `true` to
|
|
138
|
+
* loop forever, `false` to play once, or a number of times. Changing it
|
|
139
|
+
* restarts the animation.
|
|
139
140
|
*/
|
|
140
141
|
loop?: boolean | number;
|
|
141
142
|
/**
|
|
@@ -147,9 +148,11 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
147
148
|
*/
|
|
148
149
|
imageRendering?: 'auto' | 'smooth' | 'pixelated';
|
|
149
150
|
/**
|
|
150
|
-
* Pauses an animated image (GIF,
|
|
151
|
-
* it's showing; `false` plays it again from there
|
|
152
|
-
*
|
|
151
|
+
* Pauses an animated image (GIF, animated WebP, APNG, and animated AVIF on
|
|
152
|
+
* Android) on the frame it's showing; `false` plays it again from there
|
|
153
|
+
* (on Android, an animated WebP or AVIF plays again from its first frame:
|
|
154
|
+
* Android can't resume one). Each image animates on its own, so pausing
|
|
155
|
+
* one doesn't pause others showing the same file.
|
|
153
156
|
*/
|
|
154
157
|
paused?: boolean;
|
|
155
158
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import <SDWebImageWebPCoder/SDImageWebPCoder.h>
|
|
2
|
+
|
|
3
|
+
// libwebp's WebP coder (SDWebImageWebPCoder), for animated WebPs only:
|
|
4
|
+
// SDWebImage's own coders decode a WebP's first frame. Still WebPs keep
|
|
5
|
+
// ImageIO's decoder. Registered with SDWebImage's coders by FFFastImageSource.
|
|
6
|
+
@interface FFFAnimatedWebPCoder : SDImageWebPCoder
|
|
7
|
+
|
|
8
|
+
@property (nonatomic, class, readonly, nonnull) FFFAnimatedWebPCoder* sharedCoder;
|
|
9
|
+
|
|
10
|
+
@end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#import "FFFAnimatedWebPCoder.h"
|
|
2
|
+
|
|
3
|
+
// Whether the data is an animated WebP: a RIFF WEBP file whose first chunk is
|
|
4
|
+
// VP8X (the extended format) with the animation flag set. The header comes
|
|
5
|
+
// first, so a partial download can be checked too.
|
|
6
|
+
static BOOL FFFIsAnimatedWebP(NSData* data) {
|
|
7
|
+
if (data.length < 21) {
|
|
8
|
+
return NO;
|
|
9
|
+
}
|
|
10
|
+
const uint8_t* bytes = data.bytes;
|
|
11
|
+
return memcmp(bytes, "RIFF", 4) == 0
|
|
12
|
+
&& memcmp(bytes + 8, "WEBPVP8X", 8) == 0
|
|
13
|
+
&& (bytes[20] & 0x02) != 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@implementation FFFAnimatedWebPCoder
|
|
17
|
+
|
|
18
|
+
// SDImageWebPCoder's sharedCoder is always one of its own.
|
|
19
|
+
+ (FFFAnimatedWebPCoder*) sharedCoder {
|
|
20
|
+
static FFFAnimatedWebPCoder* coder;
|
|
21
|
+
static dispatch_once_t once;
|
|
22
|
+
dispatch_once(&once, ^{
|
|
23
|
+
coder = [FFFAnimatedWebPCoder new];
|
|
24
|
+
});
|
|
25
|
+
return coder;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (BOOL) canDecodeFromData: (NSData*)data {
|
|
29
|
+
return FFFIsAnimatedWebP(data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
- (BOOL) canIncrementalDecodeFromData: (NSData*)data {
|
|
33
|
+
return FFFIsAnimatedWebP(data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Only for decoding: encoding WebP is left to the coders that did it before.
|
|
37
|
+
- (BOOL) canEncodeToFormat: (SDImageFormat)format {
|
|
38
|
+
return NO;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@end
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
#import <SDWebImage/SDWebImageDownloaderOperation.h>
|
|
7
7
|
#import <SDWebImage/NSData+ImageContentType.h>
|
|
8
8
|
#import <SDWebImage/SDImageCodersManager.h>
|
|
9
|
+
#import <SDWebImage/SDImageAWebPCoder.h>
|
|
10
|
+
#import "FFFAnimatedWebPCoder.h"
|
|
9
11
|
#import <objc/message.h>
|
|
10
12
|
|
|
11
13
|
// In FFFastImageView.m.
|
|
@@ -15,7 +17,7 @@ static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
|
15
17
|
|
|
16
18
|
// The photo's own size in pixels, looked up by its identifier (the url after
|
|
17
19
|
// ph://), or zero. Through the runtime, as the Photos framework is only there
|
|
18
|
-
//
|
|
20
|
+
// with SDWebImagePhotosPlugin (on iOS, always). Kept once looked up.
|
|
19
21
|
static CGSize FFFLookUpPhotoPixelSize(NSURL *url)
|
|
20
22
|
{
|
|
21
23
|
Class assetClass = NSClassFromString(@"PHAsset");
|
|
@@ -59,11 +61,11 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// Loads photo library images (ph://<localIdentifier>) with
|
|
62
|
-
// SDWebImagePhotosPlugin's loader
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
64
|
+
// SDWebImagePhotosPlugin's loader: a dependency on iOS (the podspec), found
|
|
65
|
+
// at runtime so that tvOS builds without it (an app can add it there). Its
|
|
66
|
+
// own instance, so the app's use of the plugin is left as it is. Without the
|
|
67
|
+
// plugin, or for a source it can't load (assets-library://), the load fails
|
|
68
|
+
// with `failure`.
|
|
67
69
|
@interface FFFPhotosLoader : NSObject <SDImageLoader>
|
|
68
70
|
@property (nonatomic, strong) id<SDImageLoader> plugin;
|
|
69
71
|
@property (nonatomic, copy) NSString *failure;
|
|
@@ -243,7 +245,22 @@ static id<SDImageCoder> FFFSVGCoder;
|
|
|
243
245
|
if (self != [FFFastImageSource class]) {
|
|
244
246
|
return;
|
|
245
247
|
}
|
|
246
|
-
//
|
|
248
|
+
// Animated WebP: SDWebImage's own coders only decode a WebP's first frame
|
|
249
|
+
// (with ImageIO), so libwebp's coder decodes animated ones (still ones
|
|
250
|
+
// keep ImageIO's). Not if the app has registered a WebP coder (libwebp's,
|
|
251
|
+
// or ImageIO's animated one), which is its choice.
|
|
252
|
+
BOOL hasWebPCoder = NO;
|
|
253
|
+
for (id<SDImageCoder> registered in SDImageCodersManager.sharedManager.coders) {
|
|
254
|
+
if ([registered isKindOfClass:[SDImageWebPCoder class]] || [registered isKindOfClass:[SDImageAWebPCoder class]]) {
|
|
255
|
+
hasWebPCoder = YES;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!hasWebPCoder) {
|
|
260
|
+
[SDImageCodersManager.sharedManager addCoder:FFFAnimatedWebPCoder.sharedCoder];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// SVG: registered with SDWebImage's coders, as the pod's setup does (unless
|
|
247
264
|
// the app has already): SDWebImage then picks it for SVG data, including
|
|
248
265
|
// for FastImage's animated and downsampled image classes. Giving the
|
|
249
266
|
// coder per load instead (SDWebImageContextImageCoder) would make it the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-fast-image",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.30.0",
|
|
4
4
|
"description": "FastImage, performant React Native image component.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cache",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"android/build.gradle",
|
|
29
29
|
"android/consumer-rules.pro",
|
|
30
30
|
"android/src/main",
|
|
31
|
+
"android/src/glide-4.15",
|
|
32
|
+
"android/src/glide-older",
|
|
31
33
|
"ios/FastImage",
|
|
32
34
|
"ios/FastImage.xcodeproj/project.pbxproj",
|
|
33
35
|
"ios/FastImage.xcodeproj/xcshareddata",
|