Merge pull request #3115 from Insofan/gif
Add some animation playback mode
This commit is contained in:
commit
dd538591f3
|
@ -10,6 +10,25 @@
|
|||
#import "SDWebImageCompat.h"
|
||||
#import "SDImageCoder.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, SDAnimatedImagePlaybackMode) {
|
||||
/**
|
||||
* From first to last frame and stop or next loop.
|
||||
*/
|
||||
SDAnimatedImagePlaybackModeNormal = 0,
|
||||
/**
|
||||
* From last frame to first frame and stop or next loop.
|
||||
*/
|
||||
SDAnimatedImagePlaybackModeReverse,
|
||||
/**
|
||||
* From first frame to last frame and reverse again, like reciprocating.
|
||||
*/
|
||||
SDAnimatedImagePlaybackModeBounce,
|
||||
/**
|
||||
* From last frame to first frame and reverse again, like reversed reciprocating.
|
||||
*/
|
||||
SDAnimatedImagePlaybackModeReversedBounce,
|
||||
};
|
||||
|
||||
/// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering.
|
||||
@interface SDAnimatedImagePlayer : NSObject
|
||||
|
||||
|
@ -37,6 +56,9 @@
|
|||
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
|
||||
@property (nonatomic, assign) double playbackRate;
|
||||
|
||||
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
|
||||
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
|
||||
|
||||
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
|
||||
/// `0` means automatically adjust by calculating current memory usage.
|
||||
/// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
@property (nonatomic, assign) NSTimeInterval currentTime;
|
||||
@property (nonatomic, assign) BOOL bufferMiss;
|
||||
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
|
||||
@property (nonatomic, assign) BOOL shouldReverse;
|
||||
@property (nonatomic, assign) NSUInteger maxBufferCount;
|
||||
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
|
||||
@property (nonatomic, strong) SDDisplayLink *displayLink;
|
||||
|
@ -136,7 +137,12 @@
|
|||
if (self.currentFrameIndex != 0) {
|
||||
return;
|
||||
}
|
||||
if ([self.animatedProvider isKindOfClass:[UIImage class]]) {
|
||||
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
|
||||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
|
||||
self.currentFrameIndex = self.totalFrameCount - 1;
|
||||
}
|
||||
|
||||
if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
|
||||
UIImage *image = (UIImage *)self.animatedProvider;
|
||||
// Use the poster image if available
|
||||
#if SD_MAC
|
||||
|
@ -152,6 +158,7 @@
|
|||
[self handleFrameChange];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)resetCurrentFrameStatus {
|
||||
|
@ -174,9 +181,7 @@
|
|||
- (void)startPlaying {
|
||||
[self.displayLink start];
|
||||
// Setup frame
|
||||
if (self.currentFrameIndex == 0 && !self.currentFrame) {
|
||||
[self setupCurrentFrame];
|
||||
}
|
||||
[self setupCurrentFrame];
|
||||
// Calculate max buffer size
|
||||
[self calculateMaxBufferCount];
|
||||
}
|
||||
|
@ -236,6 +241,21 @@
|
|||
NSUInteger currentFrameIndex = self.currentFrameIndex;
|
||||
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
|
||||
|
||||
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
|
||||
nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
|
||||
|
||||
} else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
|
||||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
|
||||
if (currentFrameIndex == 0) {
|
||||
self.shouldReverse = false;
|
||||
} else if (currentFrameIndex == totalFrameCount - 1) {
|
||||
self.shouldReverse = true;
|
||||
}
|
||||
nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
|
||||
nextFrameIndex %= totalFrameCount;
|
||||
}
|
||||
|
||||
|
||||
// Check if we need to display new frame firstly
|
||||
BOOL bufferFull = NO;
|
||||
if (self.needsDisplayWhenImageBecomesAvailable) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#if SD_UIKIT || SD_MAC
|
||||
|
||||
#import "SDAnimatedImage.h"
|
||||
#import "SDAnimatedImagePlayer.h"
|
||||
|
||||
/**
|
||||
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
|
||||
|
@ -19,6 +20,12 @@
|
|||
For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed.
|
||||
*/
|
||||
@interface SDAnimatedImageView : UIImageView
|
||||
/**
|
||||
The internal animation player.
|
||||
This property is only used for advanced usage, like inspecting/debugging animation status, control progressive loading, complicated animation frame index control, etc.
|
||||
@warning Pay attention if you directly update the player's property like `totalFrameCount`, `totalLoopCount`, the same property on `SDAnimatedImageView` may not get synced.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
|
||||
|
||||
/**
|
||||
Current display frame image. This value is KVO Compliance.
|
||||
|
@ -52,6 +59,10 @@
|
|||
`< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
|
||||
*/
|
||||
@property (nonatomic, assign) double playbackRate;
|
||||
|
||||
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
|
||||
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
|
||||
|
||||
/**
|
||||
Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
|
||||
`0` means automatically adjust by calculating current memory usage.
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#if SD_UIKIT || SD_MAC
|
||||
|
||||
#import "SDAnimatedImagePlayer.h"
|
||||
#import "UIImage+Metadata.h"
|
||||
#import "NSImage+Compatibility.h"
|
||||
#import "SDInternalMacros.h"
|
||||
|
@ -24,14 +23,15 @@
|
|||
NSRunLoopMode _runLoopMode;
|
||||
NSUInteger _maxBufferSize;
|
||||
double _playbackRate;
|
||||
SDAnimatedImagePlaybackMode _playbackMode;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong, readwrite) SDAnimatedImagePlayer *player;
|
||||
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
|
||||
@property (nonatomic, assign) BOOL shouldAnimate;
|
||||
@property (nonatomic, assign) BOOL isProgressive;
|
||||
@property (nonatomic,strong) SDAnimatedImagePlayer *player; // The animation player.
|
||||
@property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer.
|
||||
|
||||
@end
|
||||
|
@ -164,6 +164,9 @@
|
|||
// Play Rate
|
||||
self.player.playbackRate = self.playbackRate;
|
||||
|
||||
// Play Mode
|
||||
self.player.playbackMode = self.playbackMode;
|
||||
|
||||
// Setup handler
|
||||
@weakify(self);
|
||||
self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) {
|
||||
|
@ -239,6 +242,19 @@
|
|||
return _playbackRate;
|
||||
}
|
||||
|
||||
- (void)setPlaybackMode:(SDAnimatedImagePlaybackMode)playbackMode {
|
||||
_playbackMode = playbackMode;
|
||||
self.player.playbackMode = playbackMode;
|
||||
}
|
||||
|
||||
- (SDAnimatedImagePlaybackMode)playbackMode {
|
||||
if (!_initFinished) {
|
||||
return SDAnimatedImagePlaybackModeNormal; // Default mode is normal
|
||||
}
|
||||
return _playbackMode;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)shouldIncrementalLoad
|
||||
{
|
||||
if (!_initFinished) {
|
||||
|
|
|
@ -622,6 +622,149 @@ static BOOL _isCalled;
|
|||
}
|
||||
#endif
|
||||
|
||||
- (void)test33AnimatedImagePlaybackModeReverse {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse mode"];
|
||||
|
||||
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
|
||||
|
||||
#if SD_UIKIT
|
||||
[self.window addSubview:imageView];
|
||||
#else
|
||||
[self.window.contentView addSubview:imageView];
|
||||
#endif
|
||||
|
||||
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
|
||||
imageView.autoPlayAnimatedImage = NO;
|
||||
imageView.image = image;
|
||||
|
||||
__weak SDAnimatedImagePlayer *player = imageView.player;
|
||||
player.playbackMode = SDAnimatedImagePlaybackModeReverse;
|
||||
|
||||
__block NSInteger i = player.totalFrameCount - 1;
|
||||
__weak typeof(imageView) wimageView = imageView;
|
||||
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
|
||||
expect(index).equal(i);
|
||||
expect(frame).notTo.beNil();
|
||||
if (index == 0) {
|
||||
[expectation fulfill];
|
||||
// Stop Animation to avoid extra callback
|
||||
[wimageView.player stopPlaying];
|
||||
[wimageView removeFromSuperview];
|
||||
return;
|
||||
}
|
||||
i--;
|
||||
}];
|
||||
|
||||
[player startPlaying];
|
||||
|
||||
[self waitForExpectationsWithTimeout:15 handler:nil];
|
||||
}
|
||||
|
||||
- (void)test34AnimatedImagePlaybackModeBounce {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback bounce mode"];
|
||||
|
||||
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
|
||||
|
||||
#if SD_UIKIT
|
||||
[self.window addSubview:imageView];
|
||||
#else
|
||||
[self.window.contentView addSubview:imageView];
|
||||
#endif
|
||||
|
||||
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
|
||||
imageView.autoPlayAnimatedImage = NO;
|
||||
imageView.image = image;
|
||||
|
||||
__weak SDAnimatedImagePlayer *player = imageView.player;
|
||||
player.playbackMode = SDAnimatedImagePlaybackModeBounce;
|
||||
|
||||
__block NSInteger i = 0;
|
||||
__block BOOL flag = false;
|
||||
__block NSUInteger cnt = 0;
|
||||
__weak typeof(imageView) wimageView = imageView;
|
||||
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
|
||||
expect(index).equal(i);
|
||||
expect(frame).notTo.beNil();
|
||||
|
||||
if (index >= player.totalFrameCount - 1) {
|
||||
cnt++;
|
||||
flag = true;
|
||||
} else if (cnt != 0 && index == 0) {
|
||||
cnt++;
|
||||
flag = false;
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
i++;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
|
||||
if (cnt > 3) {
|
||||
[expectation fulfill];
|
||||
// Stop Animation to avoid extra callback
|
||||
[wimageView.player stopPlaying];
|
||||
[wimageView removeFromSuperview];
|
||||
}
|
||||
}];
|
||||
|
||||
[player startPlaying];
|
||||
|
||||
[self waitForExpectationsWithTimeout:15 handler:nil];
|
||||
}
|
||||
|
||||
- (void)test35AnimatedImagePlaybackModeReversedBounce{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse bounce mode"];
|
||||
|
||||
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
|
||||
|
||||
#if SD_UIKIT
|
||||
[self.window addSubview:imageView];
|
||||
#else
|
||||
[self.window.contentView addSubview:imageView];
|
||||
#endif
|
||||
|
||||
SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
|
||||
imageView.autoPlayAnimatedImage = NO;
|
||||
imageView.image = image;
|
||||
|
||||
__weak SDAnimatedImagePlayer *player = imageView.player;
|
||||
player.playbackMode = SDAnimatedImagePlaybackModeReversedBounce;
|
||||
|
||||
__block NSInteger i = player.totalFrameCount - 1;
|
||||
__block BOOL flag = false;
|
||||
__block NSUInteger cnt = 0;
|
||||
__weak typeof(imageView) wimageView = imageView;
|
||||
[player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
|
||||
expect(index).equal(i);
|
||||
expect(frame).notTo.beNil();
|
||||
|
||||
if (cnt != 0 && index >= player.totalFrameCount - 1) {
|
||||
cnt++;
|
||||
flag = false;
|
||||
} else if (index == 0) {
|
||||
cnt++;
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
i++;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
|
||||
if (cnt > 3) {
|
||||
[expectation fulfill];
|
||||
// Stop Animation to avoid extra callback
|
||||
[wimageView.player stopPlaying];
|
||||
[wimageView removeFromSuperview];
|
||||
}
|
||||
}];
|
||||
[player startPlaying];
|
||||
|
||||
[self waitForExpectationsWithTimeout:15 handler:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Helper
|
||||
- (UIWindow *)window {
|
||||
if (!_window) {
|
||||
|
|
Loading…
Reference in New Issue