Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1dea54c

Browse files
authored
Merge pull request #3761 from dreampiggy/feature/animationTransformer
Added `animationTransformer` on SDAnimatedImageView, allows the animated image to apply post-transform
2 parents 780aa6d + e669dee commit 1dea54c

File tree

6 files changed

+159
-2
lines changed

6 files changed

+159
-2
lines changed

Examples/SDWebImage Demo/DetailViewController.m

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@interface DetailViewController ()
1313

1414
@property (strong, nonatomic) IBOutlet SDAnimatedImageView *imageView;
15+
@property (assign) BOOL tintApplied;
1516

1617
@end
1718

@@ -37,6 +38,39 @@ - (void)viewDidLoad {
3738
style:UIBarButtonItemStylePlain
3839
target:self
3940
action:@selector(toggleAnimation:)];
41+
// Add a secret title click action to apply tint color
42+
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
43+
[button addTarget:self
44+
action:@selector(toggleTint:)
45+
forControlEvents:UIControlEventTouchUpInside];
46+
[button setTitle:@"Tint" forState:UIControlStateNormal];
47+
self.navigationItem.titleView = button;
48+
}
49+
50+
- (void)toggleTint:(UIResponder *)sender {
51+
// tint for non-opaque animation
52+
if (!self.imageView.isAnimating) {
53+
return;
54+
}
55+
SDAnimatedImage *animatedImage = (SDAnimatedImage *)self.imageView.image;
56+
if (animatedImage.sd_imageFormat == SDImageFormatGIF) {
57+
// GIF is opaque
58+
return;
59+
}
60+
BOOL containsAlpha = [SDImageCoderHelper CGImageContainsAlpha:animatedImage.CGImage];
61+
if (!containsAlpha) {
62+
return;
63+
}
64+
if (self.tintApplied) {
65+
self.imageView.animationTransformer = nil;
66+
} else {
67+
self.imageView.animationTransformer = [SDImageTintTransformer transformerWithColor:UIColor.blackColor];
68+
}
69+
self.tintApplied = !self.tintApplied;
70+
// refresh
71+
UIImage *image = self.imageView.image;
72+
self.imageView.image = nil;
73+
self.imageView.image = image;
4074
}
4175

4276
- (void)toggleAnimation:(UIResponder *)sender {

SDWebImage/Core/SDAnimatedImageView.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#import "SDAnimatedImage.h"
1414
#import "SDAnimatedImagePlayer.h"
15+
#import "SDImageTransformer.h"
1516

1617
/**
1718
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
@@ -28,6 +29,19 @@ NS_SWIFT_UI_ACTOR
2829
*/
2930
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
3031

32+
/**
33+
The transformer for each decoded animated image frame.
34+
We supports post-transform on animated image frame from version 5.20.
35+
When you configure the transformer on `SDAnimatedImageView` and animation is playing, the `transformedImageWithImage:forKey:` will be called just after the frame is decoded. (note: The `key` arg is always empty for backward-compatible and may be removed in the future)
36+
37+
Example to tint the alpha animated image frame with a black color:
38+
* @code
39+
imageView.animationTransformer = [SDImageTintTransformer transformerWithColor:UIColor.blackColor];
40+
* @endcode
41+
@note The `transformerKey` property is used to ensure the buffer cache available. So make sure it's correct value match the actual logic on transformer. Which means, for the `same frame index + same transformer key`, the transformed image should always be the same.
42+
*/
43+
@property (nonatomic, strong, nullable) id<SDImageTransformer> animationTransformer;
44+
3145
/**
3246
Current display frame image. This value is KVO Compliance.
3347
*/

SDWebImage/Core/SDAnimatedImageView.m

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,70 @@
1515
#import "SDInternalMacros.h"
1616
#import "objc/runtime.h"
1717

18+
// A wrapper to implements the transformer on animated image, like tint color
19+
@interface SDAnimatedImageFrameProvider : NSObject <SDAnimatedImageProvider>
20+
@property (nonatomic, strong) id<SDAnimatedImageProvider> provider;
21+
@property (nonatomic, strong) id<SDImageTransformer> transformer;
22+
@end
23+
24+
@implementation SDAnimatedImageFrameProvider
25+
26+
- (instancetype)initWithProvider:(id<SDAnimatedImageProvider>)provider transformer:(id<SDImageTransformer>)transformer {
27+
self = [super init];
28+
if (self) {
29+
_provider = provider;
30+
_transformer = transformer;
31+
}
32+
return self;
33+
}
34+
35+
- (NSUInteger)hash {
36+
NSUInteger prime = 31;
37+
NSUInteger result = 1;
38+
NSUInteger providerHash = self.provider.hash;
39+
NSUInteger transformerHash = self.transformer.transformerKey.hash;
40+
result = prime * result + providerHash;
41+
result = prime * result + transformerHash;
42+
return result;
43+
}
44+
45+
- (BOOL)isEqual:(id)object {
46+
if (nil == object) {
47+
return NO;
48+
}
49+
if (self == object) {
50+
return YES;
51+
}
52+
if (![object isKindOfClass:[self class]]) {
53+
return NO;
54+
}
55+
return self.provider == [object provider]
56+
&& [self.transformer.transformerKey isEqualToString:[object transformer].transformerKey];
57+
}
58+
59+
- (NSData *)animatedImageData {
60+
return self.provider.animatedImageData;
61+
}
62+
63+
- (NSUInteger)animatedImageFrameCount {
64+
return self.provider.animatedImageFrameCount;
65+
}
66+
67+
- (NSUInteger)animatedImageLoopCount {
68+
return self.provider.animatedImageLoopCount;
69+
}
70+
71+
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
72+
return [self.provider animatedImageDurationAtIndex:index];
73+
}
74+
75+
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
76+
UIImage *frame = [self.provider animatedImageFrameAtIndex:index];
77+
return [self.transformer transformedImageWithImage:frame forKey:@""];
78+
}
79+
80+
@end
81+
1882
@interface UIImageView () <CALayerDelegate>
1983
@end
2084

@@ -139,7 +203,14 @@ - (void)setImage:(UIImage *)image
139203
provider = (id<SDAnimatedImage>)image;
140204
}
141205
// Create animated player
142-
self.player = [SDAnimatedImagePlayer playerWithProvider:provider];
206+
if (self.animationTransformer) {
207+
// Check if post-transform animation available
208+
provider = [[SDAnimatedImageFrameProvider alloc] initWithProvider:provider transformer:self.animationTransformer];
209+
self.player = [SDAnimatedImagePlayer playerWithProvider:provider];
210+
} else {
211+
// Normal animation without post-transform
212+
self.player = [SDAnimatedImagePlayer playerWithProvider:provider];
213+
}
143214
} else {
144215
// Update Frame Count
145216
self.player.totalFrameCount = [(id<SDAnimatedImage>)image animatedImageFrameCount];

SDWebImage/Core/SDImageTransformer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullab
3131
/**
3232
A transformer protocol to transform the image load from cache or from download.
3333
You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`).
34+
From v5.20, the transformer class also can be used on animated image frame post-transform logic, see `SDAnimatedImageView`.
3435
3536
@note The transform process is called from a global queue in order to not to block the main queue.
3637
*/
@@ -50,6 +51,7 @@ FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullab
5051
@required
5152
/**
5253
For each transformer, it must contains its cache key to used to store the image cache or query from the cache. This key will be appened after the original cache key generated by URL or from user.
54+
Which means, the cache should match what your transformer logic do. The same `input image` + `transformer key`, should always generate the same `output image`.
5355
5456
@return The cache key to appended after the original cache key. Should not be nil.
5557
*/

SDWebImage/Private/SDImageFramePool.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ + (NSMapTable *)providerFramePoolMap {
3131
static NSMapTable *providerFramePoolMap;
3232
static dispatch_once_t onceToken;
3333
dispatch_once(&onceToken, ^{
34-
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
34+
// Key use `hash` && `isEqual:`
35+
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
3536
});
3637
return providerFramePoolMap;
3738
}

Tests/Tests/SDAnimatedImageTest.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "SDTestCase.h"
1111
#import "SDInternalMacros.h"
1212
#import "SDImageFramePool.h"
13+
#import "SDWebImageTestTransformer.h"
1314
#import <KVOController/KVOController.h>
1415

1516
static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop count
@@ -808,6 +809,40 @@ - (void)test37AnimatedImageWithStaticDataBehavior {
808809
expect(scaledImage).notTo.equal(image);
809810
}
810811

812+
- (void)testAnimationTransformerWorks {
813+
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView animationTransformer works"];
814+
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
815+
// Setup transformer, showing which hook all frames into the test image
816+
UIImage *testImage = [[UIImage alloc] initWithData:[self testJPEGData]];
817+
SDWebImageTestTransformer *transformer = [SDWebImageTestTransformer new];
818+
transformer.testImage = testImage;
819+
imageView.animationTransformer = transformer;
820+
821+
#if SD_UIKIT
822+
[self.window addSubview:imageView];
823+
#else
824+
[self.window.contentView addSubview:imageView];
825+
#endif
826+
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testGIFData]];
827+
imageView.image = image;
828+
#if SD_UIKIT
829+
[imageView startAnimating];
830+
#else
831+
imageView.animates = YES;
832+
#endif
833+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
834+
// 0.5s is not finished, frame index should not be 0
835+
expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
836+
expect(imageView.currentFrameIndex).beGreaterThan(0);
837+
// Test the current frame image is hooked by transformer
838+
expect(imageView.currentFrame).equal(testImage);
839+
840+
[expectation fulfill];
841+
});
842+
843+
[self waitForExpectationsWithCommonTimeout];
844+
}
845+
811846
#pragma mark - Helper
812847

813848
- (NSString *)testGIFPath {

0 commit comments

Comments
 (0)