SDAnimatedImageTest.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. * (c) Matt Galloway
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. #import "SDTestCase.h"
  10. #import "SDInternalMacros.h"
  11. #import "SDImageFramePool.h"
  12. #import <KVOController/KVOController.h>
  13. static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop count
  14. // Check whether the coder is called
  15. @interface SDImageAPNGTestCoder : SDImageAPNGCoder
  16. @property (nonatomic, class, assign) BOOL isCalled;
  17. @end
  18. @implementation SDImageAPNGTestCoder
  19. static BOOL _isCalled;
  20. + (BOOL)isCalled {
  21. return _isCalled;
  22. }
  23. + (void)setIsCalled:(BOOL)isCalled {
  24. _isCalled = isCalled;
  25. }
  26. + (instancetype)sharedCoder {
  27. static SDImageAPNGTestCoder *coder;
  28. static dispatch_once_t onceToken;
  29. dispatch_once(&onceToken, ^{
  30. coder = [[SDImageAPNGTestCoder alloc] init];
  31. });
  32. return coder;
  33. }
  34. - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOptions *)options {
  35. SDImageAPNGTestCoder.isCalled = YES;
  36. return [super initWithAnimatedImageData:data options:options];
  37. }
  38. @end
  39. // Internal header
  40. @interface SDAnimatedImageView ()
  41. @property (nonatomic, assign) BOOL isProgressive;
  42. @property (nonatomic, strong) SDAnimatedImagePlayer *player;
  43. @end
  44. @interface SDAnimatedImagePlayer ()
  45. @property (nonatomic, strong) SDImageFramePool *framePool;
  46. @end
  47. @interface SDAnimatedImageTest : SDTestCase
  48. @end
  49. @implementation SDAnimatedImageTest
  50. - (void)test01AnimatedImageInitWithData {
  51. NSData *invalidData = [@"invalid data" dataUsingEncoding:NSUTF8StringEncoding];
  52. SDAnimatedImage *image = [[SDAnimatedImage alloc] initWithData:invalidData];
  53. expect(image).beNil();
  54. NSData *validData = [self testGIFData];
  55. image = [[SDAnimatedImage alloc] initWithData:validData scale:2];
  56. expect(image).notTo.beNil(); // image
  57. expect(image.scale).equal(2); // scale
  58. expect(image.animatedImageData).equal(validData); // data
  59. expect(image.animatedImageFormat).equal(SDImageFormatGIF); // format
  60. expect(image.animatedImageLoopCount).equal(0); // loop count
  61. expect(image.animatedImageFrameCount).equal(kTestGIFFrameCount); // frame count
  62. expect([image animatedImageFrameAtIndex:1]).notTo.beNil(); // 1 frame
  63. }
  64. - (void)test02AnimatedImageInitWithContentsOfFile {
  65. SDAnimatedImage *image = [[SDAnimatedImage alloc] initWithContentsOfFile:[self testGIFPath]];
  66. expect(image).notTo.beNil();
  67. expect(image.scale).equal(1); // scale
  68. // Test Retina File Path should result @2x scale
  69. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  70. NSString *testPath = [testBundle pathForResource:@"1@2x" ofType:@"gif"];
  71. image = [[SDAnimatedImage alloc] initWithContentsOfFile:testPath];
  72. expect(image).notTo.beNil();
  73. expect(image.scale).equal(2); // scale
  74. }
  75. - (void)test03AnimatedImageInitWithAnimatedCoder {
  76. NSData *validData = [self testGIFData];
  77. SDImageGIFCoder *coder = [[SDImageGIFCoder alloc] initWithAnimatedImageData:validData options:nil];
  78. SDAnimatedImage *image = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
  79. expect(image).notTo.beNil();
  80. // enough, other can be test with InitWithData
  81. }
  82. - (void)test04AnimatedImageImageNamed {
  83. NSBundle *bundle = [NSBundle bundleForClass:[self class]];
  84. expect([SDAnimatedImage imageNamed:@"TestImage.gif"]).beNil(); // Not in main bundle
  85. #if SD_UIKIT
  86. SDAnimatedImage *image = [SDAnimatedImage imageNamed:@"TestImage.gif" inBundle:bundle compatibleWithTraitCollection:nil];
  87. #else
  88. SDAnimatedImage *image = [SDAnimatedImage imageNamed:@"TestImage.gif" inBundle:bundle];
  89. #endif
  90. expect(image).notTo.beNil();
  91. expect([image.animatedImageData isEqualToData:[self testGIFData]]).beTruthy();
  92. }
  93. - (void)test05AnimatedImagePreloadFrames {
  94. NSData *validData = [self testGIFData];
  95. SDAnimatedImage *image = [SDAnimatedImage imageWithData:validData];
  96. // Preload all frames
  97. [image preloadAllFrames];
  98. NSArray *loadedAnimatedImageFrames = [image valueForKey:@"loadedAnimatedImageFrames"]; // Access the internal property, only for test and may be changed in the future
  99. expect(loadedAnimatedImageFrames.count).equal(kTestGIFFrameCount);
  100. // Test one frame
  101. UIImage *frame = [image animatedImageFrameAtIndex:0];
  102. expect(frame).notTo.beNil();
  103. // Unload all frames
  104. [image unloadAllFrames];
  105. }
  106. - (void)test06AnimatedImageViewSetImage {
  107. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  108. UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
  109. imageView.image = image;
  110. expect(imageView.image).notTo.beNil();
  111. expect(imageView.currentFrame).beNil(); // current frame
  112. }
  113. - (void)test08AnimatedImageViewSetAnimatedImageGIF {
  114. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  115. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testGIFData]];
  116. imageView.image = image;
  117. expect(imageView.image).notTo.beNil();
  118. expect(imageView.player).notTo.beNil();
  119. }
  120. - (void)test09AnimatedImageViewSetAnimatedImageAPNG {
  121. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  122. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  123. imageView.image = image;
  124. expect(imageView.image).notTo.beNil();
  125. expect(imageView.player).notTo.beNil();
  126. }
  127. - (void)test10AnimatedImageInitWithCoder {
  128. SDAnimatedImage *image1 = [SDAnimatedImage imageWithContentsOfFile:[self testGIFPath]];
  129. expect(image1).notTo.beNil();
  130. NSMutableData *encodedData = [NSMutableData data];
  131. NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:encodedData];
  132. archiver.requiresSecureCoding = YES;
  133. [archiver encodeObject:image1 forKey:NSKeyedArchiveRootObjectKey];
  134. [archiver finishEncoding];
  135. expect(encodedData).notTo.beNil();
  136. NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedData];
  137. unarchiver.requiresSecureCoding = YES;
  138. SDAnimatedImage *image2 = [unarchiver decodeObjectOfClass:SDAnimatedImage.class forKey:NSKeyedArchiveRootObjectKey];
  139. [unarchiver finishDecoding];
  140. expect(image2).notTo.beNil();
  141. // Check each property
  142. expect(image1.scale).equal(image2.scale);
  143. expect(image1.size).equal(image2.size);
  144. expect(image1.animatedImageFormat).equal(image2.animatedImageFormat);
  145. expect(image1.animatedImageData).equal(image2.animatedImageData);
  146. expect(image1.animatedImageLoopCount).equal(image2.animatedImageLoopCount);
  147. expect(image1.animatedImageFrameCount).equal(image2.animatedImageFrameCount);
  148. }
  149. - (void)test11AnimatedImageViewIntrinsicContentSize {
  150. // Test that SDAnimatedImageView.intrinsicContentSize return the correct value of image size
  151. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  152. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  153. imageView.image = image;
  154. expect(imageView.intrinsicContentSize).equal(image.size);
  155. }
  156. - (void)test12AnimatedImageViewLayerContents {
  157. // Test that SDAnimatedImageView with built-in UIImage/NSImage will actually setup the layer for display
  158. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  159. UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
  160. imageView.image = image;
  161. #if SD_MAC
  162. expect(imageView.wantsUpdateLayer).beTruthy();
  163. #else
  164. expect(imageView.layer).notTo.beNil();
  165. #endif
  166. }
  167. - (void)test13AnimatedImageViewInitWithImage {
  168. // Test that -[SDAnimatedImageView initWithImage:] this convenience initializer not crash
  169. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  170. SDAnimatedImageView *imageView;
  171. #if SD_UIKIT
  172. imageView = [[SDAnimatedImageView alloc] initWithImage:image];
  173. #else
  174. if (@available(macOS 10.12, *)) {
  175. imageView = [SDAnimatedImageView imageViewWithImage:image];
  176. }
  177. #endif
  178. expect(imageView.image).equal(image);
  179. }
  180. - (void)test14AnimatedImageViewStopPlayingWhenHidden {
  181. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  182. #if SD_UIKIT
  183. [self.window addSubview:imageView];
  184. #else
  185. [self.window.contentView addSubview:imageView];
  186. #endif
  187. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testGIFData]];
  188. imageView.image = image;
  189. #if SD_UIKIT
  190. [imageView startAnimating];
  191. #else
  192. imageView.animates = YES;
  193. #endif
  194. SDAnimatedImagePlayer *player = imageView.player;
  195. expect(player).notTo.beNil();
  196. expect(player.isPlaying).beTruthy();
  197. imageView.hidden = YES;
  198. expect(player.isPlaying).beFalsy();
  199. }
  200. - (void)test20AnimatedImageViewRendering {
  201. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView rendering"];
  202. SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] init];
  203. #if SD_UIKIT
  204. [self.window addSubview:imageView];
  205. #else
  206. [self.window.contentView addSubview:imageView];
  207. #endif
  208. NSMutableDictionary *frames = [NSMutableDictionary dictionaryWithCapacity:kTestGIFFrameCount];
  209. [self.KVOController observe:imageView keyPaths:@[NSStringFromSelector(@selector(currentFrameIndex)), NSStringFromSelector(@selector(currentLoopCount))] options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
  210. NSUInteger frameIndex = imageView.currentFrameIndex;
  211. NSUInteger loopCount = imageView.currentLoopCount;
  212. [frames setObject:@(YES) forKey:@(frameIndex)];
  213. BOOL framesRendered = NO;
  214. if (frames.count >= kTestGIFFrameCount) {
  215. // All frames rendered
  216. framesRendered = YES;
  217. }
  218. BOOL loopFinished = NO;
  219. if (loopCount >= 1) {
  220. // One loop finished
  221. loopFinished = YES;
  222. }
  223. if (framesRendered && loopFinished) {
  224. #if SD_UIKIT
  225. [imageView stopAnimating];
  226. #else
  227. imageView.animates = NO;
  228. #endif
  229. [imageView removeFromSuperview];
  230. [expectation fulfill];
  231. }
  232. }];
  233. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testGIFData]];
  234. imageView.image = image;
  235. [self waitForExpectationsWithCommonTimeout];
  236. }
  237. - (void)test21AnimatedImageViewSetProgressiveAnimatedImage {
  238. NSData *gifData = [self testGIFData];
  239. SDImageGIFCoder *progressiveCoder = [[SDImageGIFCoder alloc] initIncrementalWithOptions:nil];
  240. // simulate progressive decode, pass partial data
  241. NSData *partialData = [gifData subdataWithRange:NSMakeRange(0, gifData.length - 1)];
  242. [progressiveCoder updateIncrementalData:partialData finished:NO];
  243. SDAnimatedImage *partialImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:progressiveCoder scale:1];
  244. partialImage.sd_isIncremental = YES;
  245. SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] init];
  246. imageView.image = partialImage;
  247. BOOL isProgressive = imageView.isProgressive;
  248. expect(isProgressive).equal(YES);
  249. // pass full data
  250. [progressiveCoder updateIncrementalData:gifData finished:YES];
  251. SDAnimatedImage *fullImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:progressiveCoder scale:1];
  252. imageView.image = fullImage;
  253. isProgressive = imageView.isProgressive;
  254. expect(isProgressive).equal(NO);
  255. }
  256. - (void)test22AnimatedImageViewCategory {
  257. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView view category"];
  258. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  259. NSURL *testURL = [NSURL URLWithString:kTestGIFURL];
  260. [imageView sd_setImageWithURL:testURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  261. expect(error).to.beNil();
  262. expect(image).notTo.beNil();
  263. expect([image isKindOfClass:[SDAnimatedImage class]]).beTruthy();
  264. [expectation fulfill];
  265. }];
  266. [self waitForExpectationsWithCommonTimeout];
  267. }
  268. - (void)test23AnimatedImageViewCategoryProgressive {
  269. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView view category progressive"];
  270. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  271. NSURL *testURL = [NSURL URLWithString:kTestGIFURL];
  272. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:testURL.absoluteString];
  273. [SDImageCache.sharedImageCache removeImageFromDiskForKey:testURL.absoluteString];
  274. [imageView sd_setImageWithURL:testURL placeholderImage:nil options:SDWebImageProgressiveLoad progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
  275. dispatch_async(dispatch_get_main_queue(), ^{
  276. UIImage *image = imageView.image;
  277. // Progressive image may be nil when download data is not enough
  278. if (image) {
  279. expect(image.sd_isIncremental).beTruthy();
  280. expect([image.class conformsToProtocol:@protocol(SDAnimatedImage)]).beTruthy();
  281. BOOL isProgressive = imageView.isProgressive;
  282. expect(isProgressive).equal(YES);
  283. }
  284. });
  285. } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  286. expect(error).to.beNil();
  287. expect(image).notTo.beNil();
  288. expect([image isKindOfClass:[SDAnimatedImage class]]).beTruthy();
  289. expect(cacheType).equal(SDImageCacheTypeNone);
  290. [expectation fulfill];
  291. }];
  292. [self waitForExpectationsWithTimeout:kAsyncTestTimeout * 2 handler:nil];
  293. }
  294. - (void)test24AnimatedImageViewCategoryDiskCache {
  295. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView view category disk cache"];
  296. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  297. NSURL *testURL = [NSURL URLWithString:kTestGIFURL];
  298. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:testURL.absoluteString];
  299. [imageView sd_setImageWithURL:testURL placeholderImage:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  300. expect(error).to.beNil();
  301. expect(image).notTo.beNil();
  302. expect(cacheType).equal(SDImageCacheTypeDisk);
  303. expect([image isKindOfClass:[SDAnimatedImage class]]).beTruthy();
  304. [expectation fulfill];
  305. }];
  306. [self waitForExpectationsWithCommonTimeout];
  307. }
  308. - (void)test25AnimatedImageStopAnimatingNormal {
  309. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView stopAnimating normal behavior"];
  310. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  311. #if SD_UIKIT
  312. [self.window addSubview:imageView];
  313. #else
  314. [self.window.contentView addSubview:imageView];
  315. #endif
  316. // This APNG duration is 2s
  317. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  318. imageView.image = image;
  319. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  320. // 0.5s is not finished, frame index should not be 0
  321. expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
  322. expect(imageView.currentFrameIndex).beGreaterThan(0);
  323. });
  324. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  325. #if SD_UIKIT
  326. [imageView stopAnimating];
  327. #else
  328. imageView.animates = NO;
  329. #endif
  330. expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
  331. expect(imageView.currentFrameIndex).beGreaterThan(0);
  332. [imageView removeFromSuperview];
  333. [expectation fulfill];
  334. });
  335. [self waitForExpectationsWithCommonTimeout];
  336. }
  337. - (void)test26AnimatedImageStopAnimatingClearBuffer {
  338. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView stopAnimating clear buffer when stopped"];
  339. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  340. imageView.clearBufferWhenStopped = YES;
  341. imageView.resetFrameIndexWhenStopped = YES;
  342. #if SD_UIKIT
  343. [self.window addSubview:imageView];
  344. #else
  345. [self.window.contentView addSubview:imageView];
  346. #endif
  347. // This APNG duration is 2s
  348. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  349. imageView.image = image;
  350. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  351. // 0.5s is not finished, frame index should not be 0
  352. expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
  353. expect(imageView.currentFrameIndex).beGreaterThan(0);
  354. });
  355. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  356. #if SD_UIKIT
  357. [imageView stopAnimating];
  358. #else
  359. imageView.animates = NO;
  360. #endif
  361. expect(imageView.player.framePool.currentFrameCount).equal(0);
  362. [imageView removeFromSuperview];
  363. [expectation fulfill];
  364. });
  365. [self waitForExpectationsWithCommonTimeout];
  366. }
  367. - (void)test27AnimatedImageProgressiveAnimation {
  368. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView progressive animation rendering"];
  369. // Simulate progressive download
  370. NSData *fullData = [self testAPNGPData];
  371. NSUInteger length = fullData.length;
  372. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  373. #if SD_UIKIT
  374. [self.window addSubview:imageView];
  375. #else
  376. [self.window.contentView addSubview:imageView];
  377. #endif
  378. __block NSUInteger previousFrameIndex = 0;
  379. @weakify(imageView);
  380. // Observe to check rendering behavior using frame index
  381. [self.KVOController observe:imageView keyPath:NSStringFromSelector(@selector(currentFrameIndex)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
  382. @strongify(imageView);
  383. NSUInteger currentFrameIndex = [change[NSKeyValueChangeNewKey] unsignedIntegerValue];
  384. printf("Animation Frame Index: %lu\n", (unsigned long)currentFrameIndex);
  385. // The last time should not be progressive
  386. if (currentFrameIndex == 0 && !imageView.isProgressive) {
  387. [self.KVOController unobserve:imageView];
  388. [expectation fulfill];
  389. } else {
  390. // Each progressive rendering should render new frame index, no backward and should stop at last frame index
  391. expect(currentFrameIndex - previousFrameIndex).beGreaterThanOrEqualTo(0);
  392. previousFrameIndex = currentFrameIndex;
  393. }
  394. }];
  395. SDImageAPNGCoder *coder = [[SDImageAPNGCoder alloc] initIncrementalWithOptions:nil];
  396. // Setup Data
  397. NSData *setupData = [fullData subdataWithRange:NSMakeRange(0, length / 3.0)];
  398. [coder updateIncrementalData:setupData finished:NO];
  399. imageView.shouldIncrementalLoad = YES;
  400. __block SDAnimatedImage *progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
  401. progressiveImage.sd_isIncremental = YES;
  402. imageView.image = progressiveImage;
  403. expect(imageView.isProgressive).beTruthy();
  404. __block NSUInteger partialFrameCount;
  405. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  406. // Partial Data
  407. NSData *partialData = [fullData subdataWithRange:NSMakeRange(0, length * 2.0 / 3.0)];
  408. [coder updateIncrementalData:partialData finished:NO];
  409. partialFrameCount = [coder animatedImageFrameCount];
  410. expect(partialFrameCount).beGreaterThan(1);
  411. progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
  412. progressiveImage.sd_isIncremental = YES;
  413. imageView.image = progressiveImage;
  414. expect(imageView.isProgressive).beTruthy();
  415. });
  416. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  417. // Full Data
  418. [coder updateIncrementalData:fullData finished:YES];
  419. progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
  420. progressiveImage.sd_isIncremental = NO;
  421. imageView.image = progressiveImage;
  422. NSUInteger fullFrameCount = [coder animatedImageFrameCount];
  423. expect(fullFrameCount).beGreaterThan(partialFrameCount);
  424. expect(imageView.isProgressive).beFalsy();
  425. });
  426. [self waitForExpectationsWithCommonTimeout];
  427. }
  428. - (void)test28AnimatedImageAutoPlayAnimatedImage {
  429. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView AutoPlayAnimatedImage behavior"];
  430. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  431. imageView.autoPlayAnimatedImage = NO;
  432. #if SD_UIKIT
  433. [self.window addSubview:imageView];
  434. #else
  435. [self.window.contentView addSubview:imageView];
  436. #endif
  437. // This APNG duration is 2s
  438. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  439. imageView.image = image;
  440. #if SD_UIKIT
  441. expect(imageView.animating).equal(NO);
  442. #else
  443. expect(imageView.animates).equal(NO);
  444. #endif
  445. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  446. #if SD_UIKIT
  447. expect(imageView.animating).equal(NO);
  448. #else
  449. expect(imageView.animates).equal(NO);
  450. #endif
  451. #if SD_UIKIT
  452. [imageView startAnimating];
  453. #else
  454. imageView.animates = YES;
  455. #endif
  456. });
  457. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  458. #if SD_UIKIT
  459. expect(imageView.animating).equal(YES);
  460. #else
  461. expect(imageView.animates).equal(YES);
  462. #endif
  463. #if SD_UIKIT
  464. [imageView stopAnimating];
  465. #else
  466. imageView.animates = NO;
  467. #endif
  468. [imageView removeFromSuperview];
  469. [expectation fulfill];
  470. });
  471. [self waitForExpectationsWithCommonTimeout];
  472. }
  473. - (void)test29AnimatedImageSeekFrame {
  474. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView stopAnimating normal behavior"];
  475. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  476. #if SD_UIKIT
  477. [self.window addSubview:imageView];
  478. #else
  479. [self.window.contentView addSubview:imageView];
  480. #endif
  481. // seeking through local image should return non-null images
  482. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  483. imageView.autoPlayAnimatedImage = NO;
  484. imageView.image = image;
  485. __weak SDAnimatedImagePlayer *player = imageView.player;
  486. __block NSUInteger i = 0;
  487. [player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
  488. expect(index).equal(i);
  489. expect(frame).notTo.beNil();
  490. i++;
  491. if (i < player.totalFrameCount) {
  492. [player seekToFrameAtIndex:i loopCount:0];
  493. } else {
  494. [expectation fulfill];
  495. }
  496. }];
  497. [player seekToFrameAtIndex:i loopCount:0];
  498. [self waitForExpectationsWithCommonTimeout];
  499. }
  500. - (void)test30AnimatedImageCoderPriority {
  501. [SDImageCodersManager.sharedManager addCoder:SDImageAPNGTestCoder.sharedCoder];
  502. [SDAnimatedImage imageWithData:[self testAPNGPData]];
  503. expect(SDImageAPNGTestCoder.isCalled).equal(YES);
  504. }
  505. #if SD_UIKIT
  506. - (void)test31AnimatedImageViewSetAnimationImages {
  507. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  508. UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
  509. imageView.animationImages = @[image];
  510. expect(imageView.animationImages).notTo.beNil();
  511. }
  512. - (void)test32AnimatedImageViewNotStopPlayingAnimationImagesWhenHidden {
  513. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  514. [self.window addSubview:imageView];
  515. UIImage *image = [[UIImage alloc] initWithData:[self testJPEGData]];
  516. imageView.animationImages = @[image];
  517. [imageView startAnimating];
  518. expect(imageView.animating).beTruthy();
  519. imageView.hidden = YES;
  520. expect(imageView.animating).beTruthy();
  521. }
  522. #endif
  523. - (void)test33AnimatedImagePlaybackModeReverse {
  524. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse mode"];
  525. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  526. #if SD_UIKIT
  527. [self.window addSubview:imageView];
  528. #else
  529. [self.window.contentView addSubview:imageView];
  530. #endif
  531. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  532. imageView.autoPlayAnimatedImage = NO;
  533. imageView.image = image;
  534. __weak SDAnimatedImagePlayer *player = imageView.player;
  535. player.playbackMode = SDAnimatedImagePlaybackModeReverse;
  536. __block NSInteger i = player.totalFrameCount - 1;
  537. __weak typeof(imageView) wimageView = imageView;
  538. [player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
  539. expect(index).equal(i);
  540. expect(frame).notTo.beNil();
  541. if (index == 0) {
  542. [expectation fulfill];
  543. // Stop Animation to avoid extra callback
  544. [wimageView.player stopPlaying];
  545. [wimageView removeFromSuperview];
  546. return;
  547. }
  548. i--;
  549. }];
  550. [player startPlaying];
  551. [self waitForExpectationsWithTimeout:15 handler:nil];
  552. }
  553. - (void)test34AnimatedImagePlaybackModeBounce {
  554. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback bounce mode"];
  555. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  556. #if SD_UIKIT
  557. [self.window addSubview:imageView];
  558. #else
  559. [self.window.contentView addSubview:imageView];
  560. #endif
  561. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  562. imageView.autoPlayAnimatedImage = NO;
  563. imageView.image = image;
  564. __weak SDAnimatedImagePlayer *player = imageView.player;
  565. player.playbackMode = SDAnimatedImagePlaybackModeBounce;
  566. __block NSInteger i = 0;
  567. __block BOOL flag = NO;
  568. __block NSUInteger cnt = 0;
  569. __weak typeof(imageView) wimageView = imageView;
  570. [player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
  571. expect(index).equal(i);
  572. expect(frame).notTo.beNil();
  573. if (index >= player.totalFrameCount - 1) {
  574. cnt++;
  575. flag = YES;
  576. } else if (cnt != 0 && index == 0) {
  577. cnt++;
  578. flag = NO;
  579. }
  580. if (!flag) {
  581. i++;
  582. } else {
  583. i--;
  584. }
  585. if (cnt >= 2) {
  586. [expectation fulfill];
  587. // Stop Animation to avoid extra callback
  588. [wimageView.player stopPlaying];
  589. [wimageView removeFromSuperview];
  590. }
  591. }];
  592. [player startPlaying];
  593. [self waitForExpectationsWithTimeout:15 handler:nil];
  594. }
  595. - (void)test35AnimatedImagePlaybackModeReversedBounce {
  596. XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView playback reverse bounce mode"];
  597. SDAnimatedImageView *imageView = [SDAnimatedImageView new];
  598. #if SD_UIKIT
  599. [self.window addSubview:imageView];
  600. #else
  601. [self.window.contentView addSubview:imageView];
  602. #endif
  603. SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]];
  604. imageView.autoPlayAnimatedImage = NO;
  605. imageView.image = image;
  606. __weak SDAnimatedImagePlayer *player = imageView.player;
  607. player.playbackMode = SDAnimatedImagePlaybackModeReversedBounce;
  608. __block NSInteger i = player.totalFrameCount - 1;
  609. __block BOOL flag = false;
  610. __block NSUInteger cnt = 0;
  611. __weak typeof(imageView) wimageView = imageView;
  612. [player setAnimationFrameHandler:^(NSUInteger index, UIImage * _Nonnull frame) {
  613. expect(index).equal(i);
  614. expect(frame).notTo.beNil();
  615. if (cnt != 0 && index >= player.totalFrameCount - 1) {
  616. cnt++;
  617. flag = false;
  618. } else if (index == 0) {
  619. cnt++;
  620. flag = YES;
  621. }
  622. if (flag) {
  623. i++;
  624. } else {
  625. i--;
  626. }
  627. if (cnt >= 2) {
  628. [expectation fulfill];
  629. // Stop Animation to avoid extra callback
  630. [wimageView.player stopPlaying];
  631. [wimageView removeFromSuperview];
  632. }
  633. }];
  634. [player startPlaying];
  635. [self waitForExpectationsWithTimeout:15 handler:nil];
  636. }
  637. #if !SD_TV
  638. - (void)test36AnimatedImageMemoryCost {
  639. if (@available(iOS 14, tvOS 14, macOS 11, watchOS 7, *)) {
  640. [[SDImageCodersManager sharedManager] addCoder:[SDImageAWebPCoder sharedCoder]];
  641. UIImage *image = [UIImage sd_imageWithData:[NSData dataWithContentsOfFile:[self testMemotyCostImagePath]]];
  642. NSUInteger cost = [image sd_memoryCost];
  643. #if SD_UIKIT
  644. expect(image.images.count).equal(5333);
  645. #endif
  646. expect(image.sd_imageFrameCount).equal(16);
  647. expect(image.scale).equal(1);
  648. #if SD_MAC
  649. /// Frame count is 1 in mac.
  650. expect(cost).equal(image.size.width * image.size.height * 4);
  651. #else
  652. expect(cost).equal(16 * image.size.width * image.size.height * 4);
  653. #endif
  654. [[SDImageCodersManager sharedManager] removeCoder:[SDImageAWebPCoder sharedCoder]];
  655. }
  656. }
  657. #endif
  658. - (void)test37AnimatedImageWithStaticDataBehavior {
  659. UIImage *image = [[SDAnimatedImage alloc] initWithData:[self testJPEGData]];
  660. // UIImage+Metadata.h
  661. expect(image).notTo.beNil();
  662. expect(image.sd_isAnimated).beFalsy();
  663. expect(image.sd_imageFormat).equal(SDImageFormatJPEG);
  664. expect(image.sd_imageFrameCount).equal(1);
  665. expect(image.sd_imageLoopCount).equal(0);
  666. // SDImageCoderHelper.h
  667. UIImage *decodedImage = [SDImageCoderHelper decodedImageWithImage:image policy:SDImageForceDecodePolicyAutomatic];
  668. expect(decodedImage).notTo.equal(image);
  669. // SDWebImageDefine.h
  670. UIImage *scaledImage = SDScaledImageForScaleFactor(2.0, image);
  671. expect(scaledImage).notTo.equal(image);
  672. }
  673. #pragma mark - Helper
  674. - (NSString *)testGIFPath {
  675. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  676. NSString *testPath = [testBundle pathForResource:@"TestImage" ofType:@"gif"];
  677. return testPath;
  678. }
  679. - (NSData *)testGIFData {
  680. NSData *testData = [NSData dataWithContentsOfFile:[self testGIFPath]];
  681. return testData;
  682. }
  683. - (NSString *)testAPNGPPath {
  684. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  685. NSString *testPath = [testBundle pathForResource:@"TestImageAnimated" ofType:@"apng"];
  686. return testPath;
  687. }
  688. - (NSString *)testMemotyCostImagePath {
  689. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  690. NSString *testPath = [testBundle pathForResource:@"TestAnimatedImageMemory" ofType:@"webp"];
  691. return testPath;
  692. }
  693. - (NSData *)testAPNGPData {
  694. return [NSData dataWithContentsOfFile:[self testAPNGPPath]];
  695. }
  696. - (NSString *)testJPEGPath {
  697. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  698. NSString *testPath = [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
  699. return testPath;
  700. }
  701. - (NSData *)testJPEGData {
  702. NSData *testData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
  703. return testData;
  704. }
  705. @end