Added `animationTransformer` on SDAnimatedImageView

This allows the animated image to apply post-transform

Currently we don't change the old `SDWebImageTransformAnimatedImage` behavior. This feature is opt-in
This commit is contained in:
DreamPiggy 2024-10-19 23:45:26 +08:00
parent 0b10fcb544
commit 41dc9bef7b
5 changed files with 124 additions and 2 deletions

View File

@ -12,6 +12,7 @@
@interface DetailViewController () @interface DetailViewController ()
@property (strong, nonatomic) IBOutlet SDAnimatedImageView *imageView; @property (strong, nonatomic) IBOutlet SDAnimatedImageView *imageView;
@property (assign) BOOL tintApplied;
@end @end
@ -37,6 +38,39 @@
style:UIBarButtonItemStylePlain style:UIBarButtonItemStylePlain
target:self target:self
action:@selector(toggleAnimation:)]; action:@selector(toggleAnimation:)];
// Add a secret title click action to apply tint color
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button addTarget:self
action:@selector(toggleTint:)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Tint" forState:UIControlStateNormal];
self.navigationItem.titleView = button;
}
- (void)toggleTint:(UIResponder *)sender {
// tint for non-opaque animation
if (!self.imageView.isAnimating) {
return;
}
SDAnimatedImage *animatedImage = (SDAnimatedImage *)self.imageView.image;
if (animatedImage.sd_imageFormat == SDImageFormatGIF) {
// GIF is opaque
return;
}
BOOL containsAlpha = [SDImageCoderHelper CGImageContainsAlpha:animatedImage.CGImage];
if (!containsAlpha) {
return;
}
if (self.tintApplied) {
self.imageView.animationTransformer = nil;
} else {
self.imageView.animationTransformer = [SDImageTintTransformer transformerWithColor:UIColor.blackColor];
}
self.tintApplied = !self.tintApplied;
// refresh
UIImage *image = self.imageView.image;
self.imageView.image = nil;
self.imageView.image = image;
} }
- (void)toggleAnimation:(UIResponder *)sender { - (void)toggleAnimation:(UIResponder *)sender {

View File

@ -12,6 +12,7 @@
#import "SDAnimatedImage.h" #import "SDAnimatedImage.h"
#import "SDAnimatedImagePlayer.h" #import "SDAnimatedImagePlayer.h"
#import "SDImageTransformer.h"
/** /**
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering. A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
@ -28,6 +29,19 @@ NS_SWIFT_UI_ACTOR
*/ */
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player; @property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
/**
The transformer for each decoded animated image frame.
We supports post-transform on animated image frame from version 5.20.
When you configure the transformer on `SDAnimatedImageView` and animation is playing, the `transformedImageWithImage:forKey:` will be called just after the frame is decoded. (The `key` arg is always empty for backward-compatible)
Example to tint the animated image with alpha channel into template:
* @code
imageView.animationTransformer = [SDImageTintTransformer transformerWithColor:UIColor.blackColor];
* @endcode
@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.
*/
@property (nonatomic, strong, nullable) id<SDImageTransformer> animationTransformer;
/** /**
Current display frame image. This value is KVO Compliance. Current display frame image. This value is KVO Compliance.
*/ */

View File

@ -15,6 +15,70 @@
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
#import "objc/runtime.h" #import "objc/runtime.h"
// A wrapper to implements the transformer on animated image, like tint color
@interface SDAnimatedImageFrameProvider : NSObject <SDAnimatedImageProvider>
@property (nonatomic, strong) id<SDAnimatedImageProvider> provider;
@property (nonatomic, strong) id<SDImageTransformer> transformer;
@end
@implementation SDAnimatedImageFrameProvider
- (instancetype)initWithProvider:(id<SDAnimatedImageProvider>)provider transformer:(id<SDImageTransformer>)transformer {
self = [super init];
if (self) {
_provider = provider;
_transformer = transformer;
}
return self;
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
NSUInteger providerHash = self.provider.hash;
NSUInteger transformerHash = self.transformer.transformerKey.hash;
result = prime * result + providerHash;
result = prime * result + transformerHash;
return result;
}
- (BOOL)isEqual:(id)object {
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return self.provider == [object provider]
&& [self.transformer.transformerKey isEqualToString:[object transformer].transformerKey];
}
- (NSData *)animatedImageData {
return self.provider.animatedImageData;
}
- (NSUInteger)animatedImageFrameCount {
return self.provider.animatedImageFrameCount;
}
- (NSUInteger)animatedImageLoopCount {
return self.provider.animatedImageLoopCount;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
return [self.provider animatedImageDurationAtIndex:index];
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
UIImage *frame = [self.provider animatedImageFrameAtIndex:index];
return [self.transformer transformedImageWithImage:frame forKey:@""];
}
@end
@interface UIImageView () <CALayerDelegate> @interface UIImageView () <CALayerDelegate>
@end @end
@ -139,7 +203,14 @@
provider = (id<SDAnimatedImage>)image; provider = (id<SDAnimatedImage>)image;
} }
// Create animated player // Create animated player
self.player = [SDAnimatedImagePlayer playerWithProvider:provider]; if (self.animationTransformer) {
// Check if post-transform animation available
provider = [[SDAnimatedImageFrameProvider alloc] initWithProvider:provider transformer:self.animationTransformer];
self.player = [SDAnimatedImagePlayer playerWithProvider:provider];
} else {
// Normal animation without post-transform
self.player = [SDAnimatedImagePlayer playerWithProvider:provider];
}
} else { } else {
// Update Frame Count // Update Frame Count
self.player.totalFrameCount = [(id<SDAnimatedImage>)image animatedImageFrameCount]; self.player.totalFrameCount = [(id<SDAnimatedImage>)image animatedImageFrameCount];

View File

@ -31,6 +31,7 @@ FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullab
/** /**
A transformer protocol to transform the image load from cache or from download. A transformer protocol to transform the image load from cache or from download.
You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`). You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`).
From v5.20, the transformer class also can be used on animated image frame post-transform logic, see `SDAnimatedImageView`.
@note The transform process is called from a global queue in order to not to block the main queue. @note The transform process is called from a global queue in order to not to block the main queue.
*/ */
@ -50,6 +51,7 @@ FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullab
@required @required
/** /**
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. 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.
Which means, the cache should match what your transformer logic do. The same `input image` + `transformer key`, should always generate the same `output image`.
@return The cache key to appended after the original cache key. Should not be nil. @return The cache key to appended after the original cache key. Should not be nil.
*/ */

View File

@ -31,7 +31,8 @@ SD_LOCK_DECLARE_STATIC(_providerFramePoolMapLock);
static NSMapTable *providerFramePoolMap; static NSMapTable *providerFramePoolMap;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality]; // Key use `hash` && `isEqual:`
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
}); });
return providerFramePoolMap; return providerFramePoolMap;
} }