react-native-fast-image 8.31.1 → 9.0.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.
Files changed (22) hide show
  1. package/README.md +24 -24
  2. package/RNFastImage.podspec +6 -4
  3. package/android/build.gradle +16 -24
  4. package/android/consumer-rules.pro +4 -15
  5. package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +1 -1
  6. package/android/src/{glide-4.15 → main}/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -3
  7. package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +139 -24
  8. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +1 -22
  9. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +3 -14
  10. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +6 -35
  11. package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +1 -1
  12. package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +6 -6
  13. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -7
  14. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +1 -1
  15. package/dist/index.d.ts +12 -10
  16. package/ios/FastImage/FFFastImageSource.h +1 -2
  17. package/ios/FastImage/FFFastImageSource.m +100 -74
  18. package/package.json +2 -6
  19. package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -36
  20. package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +0 -152
  21. package/ios/FastImage.xcodeproj/project.pbxproj +0 -479
  22. package/ios/FastImage.xcodeproj/xcshareddata/xcschemes/FastImage-tvOS.xcscheme +0 -80
@@ -8,7 +8,6 @@ import android.app.Activity;
8
8
  import android.content.Context;
9
9
  import android.content.ContextWrapper;
10
10
  import android.graphics.PorterDuff;
11
- import android.os.Build;
12
11
 
13
12
  import androidx.annotation.NonNull;
14
13
 
@@ -240,12 +239,7 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
240
239
  }
241
240
 
242
241
  private static boolean isActivityDestroyed(Activity activity) {
243
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
244
- return activity.isDestroyed() || activity.isFinishing();
245
- } else {
246
- return activity.isFinishing() || activity.isChangingConfigurations();
247
- }
248
-
242
+ return activity.isDestroyed() || activity.isFinishing();
249
243
  }
250
244
 
251
245
  // Legacy architecture only; the New Architecture doesn't use shadow nodes.
@@ -443,7 +443,7 @@ class FastImageViewWithUrl extends AppCompatImageView {
443
443
  }
444
444
  }
445
445
  if (resource instanceof FastImageAnimatable) {
446
- // An APNG: each view's is its own (FastImageApngRenderer).
446
+ // An APNG: each view's is its own (FastImageApng).
447
447
  FastImageAnimatable animatable = (FastImageAnimatable) resource;
448
448
  animatable.setLoopCount(mLoopCount, false);
449
449
  // The target starts it; paused, it stays on its first frame.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
- import { StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
2
+ import { ColorValue, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
3
3
  import { cacheControl, priority, resizeMode } from './constants';
4
- type ColorValue = NonNullable<ViewStyle['backgroundColor']>;
5
4
  /** How the image fits the view: see [`resizeMode`](#resizemode). */
6
5
  export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
7
6
  /** An image's load priority: see [`source.priority`](#sourcepriority). */
@@ -13,10 +12,11 @@ export type Source = {
13
12
  /**
14
13
  * Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
15
14
  *
16
- * Also loads local files (`file://`, and on Android `content://`), photo
17
- * library images on iOS (`ph://`, see
18
- * [Photo library images](#photo-library-images-ios)) and SVG images (see
19
- * [SVG images](docs/formats.md#svg-images)).
15
+ * Also loads local files (`file://`, and on Android `content://`), images
16
+ * in the app by name (e.g. `'my_image'`: in its asset catalog on iOS, a
17
+ * drawable on Android), photo library images on iOS and tvOS (`ph://`,
18
+ * see [Photo library images](#photo-library-images-ios-and-tvos)) and SVG
19
+ * images (see [SVG images](docs/formats.md#svg-images)).
20
20
  */
21
21
  uri?: string;
22
22
  /** Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`. */
@@ -220,8 +220,8 @@ export interface OnProgressEvent {
220
220
  };
221
221
  }
222
222
  export interface ImageStyle extends ViewStyle {
223
- overlayColor?: ViewStyle['backgroundColor'];
224
- tintColor?: ViewStyle['backgroundColor'];
223
+ overlayColor?: ColorValue;
224
+ tintColor?: ColorValue;
225
225
  }
226
226
  export interface FastImageProps extends AccessibilityProps, ViewProps {
227
227
  /**
@@ -492,8 +492,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
492
492
  */
493
493
  tintColor?: ColorValue;
494
494
  /**
495
- * Render children within the image. In the next major version,
496
- * `FastImage` won't render children: use `FastImageBackground`.
495
+ * Render children within the image.
496
+ *
497
+ * @deprecated In the next major version, `FastImage` won't render
498
+ * children: use `FastImageBackground`.
497
499
  */
498
500
  children?: React.ReactNode;
499
501
  }
@@ -64,8 +64,7 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
64
64
  // which is decoded at about the view's size.
65
65
  - (BOOL)isPhotoLibrary;
66
66
 
67
- // The error's message, or for an SVG image that couldn't be decoded because
68
- // the app doesn't have SDWebImageSVGCoder, what to add.
67
+ // The error's message, for onError.
69
68
  - (NSString *)errorMessage:(NSError *)error;
70
69
 
71
70
  // A photo library image's own size in pixels (for onLoad when it comes from
@@ -9,7 +9,9 @@
9
9
  #import <SDWebImage/SDImageAWebPCoder.h>
10
10
  #import "FFFAnimatedWebPCoder.h"
11
11
  #import "FFFAnimatedAVIFCoder.h"
12
- #import <objc/message.h>
12
+ #import <SDWebImageSVGCoder/SDImageSVGCoder.h>
13
+ #import <SDWebImagePhotosPlugin/SDImagePhotosLoader.h>
14
+ #import <Photos/Photos.h>
13
15
  #import <ImageIO/ImageIO.h>
14
16
 
15
17
  // In FFFastImageView.m.
@@ -18,24 +20,16 @@ FOUNDATION_EXTERN NSString *FFFErrorMessage(NSError *error);
18
20
  static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
19
21
 
20
22
  // The photo's own size in pixels, looked up by its identifier (the url after
21
- // ph://), or zero. Through the runtime, as the Photos framework is only there
22
- // with SDWebImagePhotosPlugin (on iOS, always). Kept once looked up.
23
+ // ph://), or zero.
23
24
  static CGSize FFFLookUpPhotoPixelSize(NSURL *url)
24
25
  {
25
- Class assetClass = NSClassFromString(@"PHAsset");
26
- SEL fetch = NSSelectorFromString(@"fetchAssetsWithLocalIdentifiers:options:");
27
26
  NSString *prefix = @"ph://";
28
27
  NSString *string = url.absoluteString;
29
- if (!assetClass || ![assetClass respondsToSelector:fetch] || ![string hasPrefix:prefix]) {
28
+ if (![string hasPrefix:prefix]) {
30
29
  return CGSizeZero;
31
30
  }
32
- id (*fetchAssets)(id, SEL, NSArray *, id) = (void *)objc_msgSend;
33
- id result = fetchAssets(assetClass, fetch, @[[string substringFromIndex:prefix.length]], nil);
34
- id asset = [result respondsToSelector:@selector(firstObject)] ? [result firstObject] : nil;
35
- if (!asset) {
36
- return CGSizeZero;
37
- }
38
- return CGSizeMake([[asset valueForKey:@"pixelWidth"] doubleValue], [[asset valueForKey:@"pixelHeight"] doubleValue]);
31
+ PHAsset *asset = [PHAsset fetchAssetsWithLocalIdentifiers:@[[string substringFromIndex:prefix.length]] options:nil].firstObject;
32
+ return asset ? CGSizeMake(asset.pixelWidth, asset.pixelHeight) : CGSizeZero;
39
33
  }
40
34
 
41
35
  static NSCache<NSString *, NSValue *> *FFFPhotoSizes(void)
@@ -62,14 +56,81 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
62
56
  return size;
63
57
  }
64
58
 
59
+ // An image in the app by its name (`uri: 'my_image'`), such as one in the
60
+ // app's asset catalog, as React Native's Image takes: RCTConvert makes a uri
61
+ // without a scheme a file in the app's resources, where such an image isn't a
62
+ // file. The name, or nil if the url is a file that exists or isn't in the
63
+ // app's resources.
64
+ static NSString *FFFBundleAssetName(NSURL *url)
65
+ {
66
+ if (!url.isFileURL) {
67
+ return nil;
68
+ }
69
+ NSString *resources = [NSBundle.mainBundle.resourcePath stringByAppendingString:@"/"];
70
+ NSString *path = url.path;
71
+ if (![path hasPrefix:resources] || path.length == resources.length ||
72
+ [NSFileManager.defaultManager fileExistsAtPath:path]) {
73
+ return nil;
74
+ }
75
+ return [path substringFromIndex:resources.length];
76
+ }
77
+
78
+ // Loads images in the app by name (FFFBundleAssetName) with UIImage
79
+ // imageNamed:, which finds them in the asset catalog (and image files in the
80
+ // app's resources, e.g. with @2x and @3x versions), as React Native's Image
81
+ // does.
82
+ @interface FFFBundleAssetLoader : NSObject <SDImageLoader>
83
+ @end
84
+
85
+ @implementation FFFBundleAssetLoader
86
+
87
+ + (FFFBundleAssetLoader *)sharedLoader
88
+ {
89
+ static FFFBundleAssetLoader *loader;
90
+ static dispatch_once_t once;
91
+ dispatch_once(&once, ^{
92
+ loader = [FFFBundleAssetLoader new];
93
+ });
94
+ return loader;
95
+ }
96
+
97
+ - (BOOL)canRequestImageForURL:(NSURL *)url
98
+ {
99
+ return YES;
100
+ }
101
+
102
+ - (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url
103
+ options:(SDWebImageOptions)options
104
+ context:(SDWebImageContext *)context
105
+ progress:(SDImageLoaderProgressBlock)progressBlock
106
+ completed:(SDImageLoaderCompletedBlock)completedBlock
107
+ {
108
+ NSString *name = FFFBundleAssetName(url);
109
+ UIImage *image = name ? [UIImage imageNamed:name] : nil;
110
+ if (completedBlock) {
111
+ if (image) {
112
+ completedBlock(image, nil, nil, YES);
113
+ } else {
114
+ NSString *message = [NSString stringWithFormat:@"No image named %@ in the app", name ?: url.path];
115
+ completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: message}], YES);
116
+ }
117
+ }
118
+ return nil;
119
+ }
120
+
121
+ - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error
122
+ {
123
+ return NO;
124
+ }
125
+
126
+ @end
127
+
65
128
  // Loads photo library images (ph://<localIdentifier>) with
66
- // SDWebImagePhotosPlugin's loader: a dependency on iOS (the podspec), found
67
- // at runtime so that tvOS builds without it (an app can add it there). Its
68
- // own instance, so the app's use of the plugin is left as it is. Without the
69
- // plugin, or for a source it can't load (assets-library://), the load fails
70
- // with `failure`.
129
+ // SDWebImagePhotosPlugin's loader, its own instance, so the app's use of the
130
+ // plugin is left as it is. For a source it can't load (assets-library://),
131
+ // the load fails with `failure`.
71
132
  @interface FFFPhotosLoader : NSObject <SDImageLoader>
72
- @property (nonatomic, strong) id<SDImageLoader> plugin;
133
+ @property (nonatomic, strong) SDImagePhotosLoader *plugin;
73
134
  @property (nonatomic, copy) NSString *failure;
74
135
  @end
75
136
 
@@ -81,24 +142,12 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
81
142
  static dispatch_once_t once;
82
143
  dispatch_once(&once, ^{
83
144
  loader = [FFFPhotosLoader new];
84
- Class pluginClass = NSClassFromString(@"SDImagePhotosLoader");
85
- id<SDImageLoader> plugin = pluginClass ? [pluginClass new] : nil;
86
- if ([plugin conformsToProtocol:@protocol(SDImageLoader)]) {
87
- // One image per load, at full quality. By default the plugin
88
- // first sends a quick low quality one, which a view would show
89
- // (and fade in) before the one it asked for.
90
- // PHImageRequestOptionsDeliveryModeHighQualityFormat. Skipped if
91
- // a later version of the plugin renames these.
92
- NSObject *requestOptions = [(NSObject *)plugin respondsToSelector:NSSelectorFromString(@"imageRequestOptions")]
93
- ? [(NSObject *)plugin valueForKey:@"imageRequestOptions"]
94
- : nil;
95
- if ([requestOptions respondsToSelector:NSSelectorFromString(@"setDeliveryMode:")]) {
96
- [requestOptions setValue:@1 forKey:@"deliveryMode"];
97
- }
98
- loader.plugin = plugin;
99
- } else {
100
- loader.failure = @"Photo library images (ph://) need SDWebImagePhotosPlugin: add pod 'SDWebImagePhotosPlugin' to the app's Podfile";
101
- }
145
+ SDImagePhotosLoader *plugin = [SDImagePhotosLoader new];
146
+ // One image per load, at full quality. By default the plugin first
147
+ // sends a quick low quality one, which a view would show (and fade
148
+ // in) before the one it asked for.
149
+ plugin.imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
150
+ loader.plugin = plugin;
102
151
  });
103
152
  return loader;
104
153
  }
@@ -235,10 +284,6 @@ static BOOL FFFLooksLikeSVG(NSData *data)
235
284
 
236
285
  @end
237
286
 
238
- // SDWebImageSVGCoder's coder, when the app has the pod (SVG images): found at
239
- // runtime, so FastImage doesn't depend on it. Nil otherwise.
240
- static id<SDImageCoder> FFFSVGCoder;
241
-
242
287
  @implementation FFFastImageSource
243
288
 
244
289
  + (void)initialize
@@ -276,28 +321,17 @@ static id<SDImageCoder> FFFSVGCoder;
276
321
  [SDImageCodersManager.sharedManager addCoder:FFFAnimatedAVIFCoder.sharedCoder];
277
322
  }
278
323
 
279
- // SVG: registered with SDWebImage's coders, as the pod's setup does (unless
280
- // the app has already): SDWebImage then picks it for SVG data, including
281
- // for FastImage's animated and downsampled image classes. Giving the
282
- // coder per load instead (SDWebImageContextImageCoder) would make it the
283
- // only coder for everything in that load. So the app's own SDWebImage
284
- // use can decode SVG too, which is what the pod is for.
285
- Class coderClass = NSClassFromString(@"SDImageSVGCoder");
286
- SEL shared = NSSelectorFromString(@"sharedCoder");
287
- if (!coderClass || ![coderClass respondsToSelector:shared]) {
288
- return;
289
- }
290
- id coder = ((id (*)(id, SEL))objc_msgSend)(coderClass, shared);
291
- if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
292
- return;
293
- }
294
- FFFSVGCoder = coder;
324
+ // SVG (SDWebImageSVGCoder): registered with SDWebImage's coders, as the
325
+ // pod's setup does (unless the app has already): SDWebImage then picks it
326
+ // for SVG data, including for FastImage's animated and downsampled image
327
+ // classes. Giving the coder per load instead (SDWebImageContextImageCoder)
328
+ // would make it the only coder for everything in that load.
295
329
  for (id<SDImageCoder> registered in SDImageCodersManager.sharedManager.coders) {
296
- if ([registered isKindOfClass:coderClass]) {
330
+ if ([registered isKindOfClass:[SDImageSVGCoder class]]) {
297
331
  return;
298
332
  }
299
333
  }
300
- [SDImageCodersManager.sharedManager addCoder:coder];
334
+ [SDImageCodersManager.sharedManager addCoder:SDImageSVGCoder.sharedCoder];
301
335
  }
302
336
 
303
337
  - (instancetype)initWithURL:(NSURL *)url
@@ -343,10 +377,6 @@ static id<SDImageCoder> FFFSVGCoder;
343
377
 
344
378
  - (NSString *)errorMessage:(NSError *)error
345
379
  {
346
- if (!FFFSVGCoder && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorBadImageData &&
347
- [_url.path.lowercaseString hasSuffix:@".svg"]) {
348
- return @"SVG images need SDWebImageSVGCoder: add pod 'SDWebImageSVGCoder' to the app's Podfile";
349
- }
350
380
  return FFFErrorMessage(error);
351
381
  }
352
382
 
@@ -368,6 +398,9 @@ static id<SDImageCoder> FFFSVGCoder;
368
398
  if ([_url.scheme isEqualToString:@"assets-library"]) {
369
399
  return [FFFPhotosLoader assetsLibraryLoader];
370
400
  }
401
+ if (FFFBundleAssetName(_url)) {
402
+ return [FFFBundleAssetLoader sharedLoader];
403
+ }
371
404
  if (_cacheControl != FFFCacheControlWeb) {
372
405
  return nil;
373
406
  }
@@ -436,17 +469,10 @@ static id<SDImageCoder> FFFSVGCoder;
436
469
  static dispatch_once_t once;
437
470
  dispatch_once(&once, ^{
438
471
  NSString *name = @"fast-image-http-cache";
439
- if (@available(iOS 13.0, tvOS 13.0, *)) {
440
- NSURL *caches = [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject;
441
- cache = [[NSURLCache alloc] initWithMemoryCapacity:0
442
- diskCapacity:FFFWebCacheSize
443
- directoryURL:[caches URLByAppendingPathComponent:name]];
444
- } else {
445
- // In the app's caches directory.
446
- cache = [[NSURLCache alloc] initWithMemoryCapacity:0
447
- diskCapacity:FFFWebCacheSize
448
- diskPath:name];
449
- }
472
+ NSURL *caches = [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject;
473
+ cache = [[NSURLCache alloc] initWithMemoryCapacity:0
474
+ diskCapacity:FFFWebCacheSize
475
+ directoryURL:[caches URLByAppendingPathComponent:name]];
450
476
  });
451
477
  return cache;
452
478
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.31.1",
3
+ "version": "9.0.0",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",
@@ -28,11 +28,7 @@
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",
33
31
  "ios/FastImage",
34
- "ios/FastImage.xcodeproj/project.pbxproj",
35
- "ios/FastImage.xcodeproj/xcshareddata",
36
32
  "app.plugin.js",
37
33
  "dist",
38
34
  "RNFastImage.podspec"
@@ -73,6 +69,6 @@
73
69
  },
74
70
  "peerDependencies": {
75
71
  "react": "^17 || ^18 || ^19",
76
- "react-native": ">=0.60.0"
72
+ "react-native": ">=0.65.0"
77
73
  }
78
74
  }
@@ -1,36 +0,0 @@
1
- package com.dylanvann.fastimage;
2
-
3
- import android.content.Context;
4
- import android.graphics.drawable.Drawable;
5
-
6
- import androidx.annotation.NonNull;
7
- import androidx.annotation.Nullable;
8
-
9
- import com.bumptech.glide.Glide;
10
- import com.bumptech.glide.Registry;
11
-
12
- // Built instead of src/glide-4.15's FastImageAnimated with a Glide before
13
- // 4.15 (build.gradle), which has no decoder for animated WebP or AVIF: they
14
- // show their first frame, so there's nothing to register, remember or copy.
15
- final class FastImageAnimated {
16
- private FastImageAnimated() {}
17
-
18
- interface CopyCallback {
19
- void onCopy(@Nullable Drawable copy);
20
- }
21
-
22
- static void register(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {}
23
-
24
- @Nullable
25
- static Integer repeatCount(@NonNull Drawable drawable) {
26
- return null;
27
- }
28
-
29
- static boolean shownElsewhere(@NonNull Drawable drawable, @NonNull Drawable.Callback view) {
30
- return false;
31
- }
32
-
33
- static void copy(@NonNull Drawable drawable, @NonNull CopyCallback callback) {
34
- callback.onCopy(null);
35
- }
36
- }
@@ -1,152 +0,0 @@
1
- package com.dylanvann.fastimage;
2
-
3
- import android.graphics.Rect;
4
- import android.graphics.drawable.Drawable;
5
-
6
- import androidx.annotation.NonNull;
7
- import androidx.annotation.Nullable;
8
-
9
- import com.bumptech.glide.load.engine.Resource;
10
- import com.github.penfeizhou.animation.apng.APNGDrawable;
11
- import com.github.penfeizhou.animation.apng.decode.APNGDecoder;
12
- import com.github.penfeizhou.animation.loader.ByteBufferLoader;
13
- import com.github.penfeizhou.animation.loader.Loader;
14
-
15
- import java.nio.ByteBuffer;
16
-
17
- // APNGs with APNG4Android (FastImageApng, which only uses this when it's
18
- // there). Glide keeps the image's data in its memory cache, and each view
19
- // gets a drawable of its own over it, so views don't share playback: making
20
- // one only reads the image's header, and it decodes its frames (at a sample
21
- // size from the size it's drawn at) on APNG4Android's threads as it plays.
22
- final class FastImageApngRenderer {
23
- private FastImageApngRenderer() {
24
- }
25
-
26
- // Throws NoClassDefFoundError without APNG4Android.
27
- static boolean available() {
28
- return APNGDrawable.class.getName() != null;
29
- }
30
-
31
- private static Loader loader(ByteBuffer data) {
32
- return new ByteBufferLoader() {
33
- @Override
34
- public ByteBuffer getByteBuffer() {
35
- ByteBuffer buffer = data.duplicate();
36
- buffer.position(0);
37
- return buffer;
38
- }
39
- };
40
- }
41
-
42
- // Null if APNG4Android can't read it (Glide then decodes it as a PNG).
43
- @Nullable
44
- static Resource<Drawable> decode(@NonNull ByteBuffer data, int plays) {
45
- // Reads the header, waiting for APNG4Android's thread.
46
- Rect bounds = new APNGDecoder(loader(data), null).getBounds();
47
- if (bounds.isEmpty()) return null;
48
- return new ApngResource(data, plays, bounds);
49
- }
50
-
51
- private static final class ApngResource implements Resource<Drawable> {
52
- private final ByteBuffer data;
53
- private final int plays;
54
- private final Rect bounds;
55
-
56
- ApngResource(ByteBuffer data, int plays, Rect bounds) {
57
- this.data = data;
58
- this.plays = plays;
59
- this.bounds = bounds;
60
- }
61
-
62
- @NonNull
63
- @Override
64
- public Class<Drawable> getResourceClass() {
65
- return Drawable.class;
66
- }
67
-
68
- @NonNull
69
- @Override
70
- public Drawable get() {
71
- return new ApngDrawable(loader(data), plays);
72
- }
73
-
74
- // The data, and a frame at full size (each view's drawable has its
75
- // own, at the size it's drawn at).
76
- @Override
77
- public int getSize() {
78
- return data.limit() + bounds.width() * bounds.height() * 4;
79
- }
80
-
81
- @Override
82
- public void recycle() {
83
- }
84
- }
85
-
86
- private static final class ApngDrawable extends APNGDrawable implements FastImageAnimatable {
87
- // How many times the file says it plays (0: forever).
88
- private final int plays;
89
- private volatile boolean paused = false;
90
- // Once a frame has been rendered (on the decoder's thread).
91
- private volatile boolean rendered = false;
92
-
93
- ApngDrawable(Loader loader, int plays) {
94
- super(loader);
95
- this.plays = plays;
96
- }
97
-
98
- @Override
99
- public void setLoopCount(int loopCount, boolean restart) {
100
- // Plays (0: forever), as APNG4Android counts them.
101
- setLoopLimit(loopCount == -1 ? plays : loopCount);
102
- if (restart) {
103
- reset();
104
- start();
105
- }
106
- }
107
-
108
- @Override
109
- public void setPaused(boolean paused) {
110
- this.paused = paused;
111
- // It starts and stops itself as it's shown and hidden, but not
112
- // while paused.
113
- setAutoPlay(!paused);
114
- if (paused) {
115
- // Before the first frame, onStart pauses once it's rendered:
116
- // the decoder's start clears a pause, then skips rendering if
117
- // one came in meanwhile, which left the view blank.
118
- if (rendered) pause();
119
- } else {
120
- resume();
121
- }
122
- }
123
-
124
- // From its first frame. The target starts it right after
125
- // setVisible(true) has (auto play): APNG4Android's start() stops and
126
- // starts a running decoder, and a stop made while the decoder is
127
- // still starting can win over that start, leaving it stopped on its
128
- // first frame. A running one is reset to its first frame instead.
129
- @Override
130
- public void start() {
131
- if (isRunning()) {
132
- getFrameSeqDecoder().reset();
133
- return;
134
- }
135
- super.start();
136
- }
137
-
138
- @Override
139
- public void onRender(ByteBuffer byteBuffer) {
140
- super.onRender(byteBuffer);
141
- rendered = true;
142
- }
143
-
144
- // On its decoder thread, once starting has drawn the first frame:
145
- // starting clears a pause made before it got there, so pause again.
146
- @Override
147
- public void onStart() {
148
- super.onStart();
149
- if (paused) getFrameSeqDecoder().pause();
150
- }
151
- }
152
- }