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.
- package/README.md +24 -24
- package/RNFastImage.podspec +6 -4
- package/android/build.gradle +16 -24
- package/android/consumer-rules.pro +4 -15
- package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +1 -1
- package/android/src/{glide-4.15 → main}/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +139 -24
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +1 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +3 -14
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +6 -35
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +6 -6
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -7
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +1 -1
- package/dist/index.d.ts +12 -10
- package/ios/FastImage/FFFastImageSource.h +1 -2
- package/ios/FastImage/FFFastImageSource.m +100 -74
- package/package.json +2 -6
- package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +0 -36
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +0 -152
- package/ios/FastImage.xcodeproj/project.pbxproj +0 -479
- 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
|
-
|
|
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 (
|
|
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://`),
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* [
|
|
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?:
|
|
224
|
-
tintColor?:
|
|
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.
|
|
496
|
-
*
|
|
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,
|
|
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 <
|
|
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.
|
|
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 (!
|
|
28
|
+
if (![string hasPrefix:prefix]) {
|
|
30
29
|
return CGSizeZero;
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
67
|
-
//
|
|
68
|
-
//
|
|
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)
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
280
|
-
// the app has already): SDWebImage then picks it
|
|
281
|
-
// for FastImage's animated and downsampled image
|
|
282
|
-
// coder per load instead (SDWebImageContextImageCoder)
|
|
283
|
-
// only coder for everything in that load.
|
|
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:
|
|
330
|
+
if ([registered isKindOfClass:[SDImageSVGCoder class]]) {
|
|
297
331
|
return;
|
|
298
332
|
}
|
|
299
333
|
}
|
|
300
|
-
[SDImageCodersManager.sharedManager addCoder:
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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": "
|
|
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.
|
|
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
|
-
}
|