YBIBImageData.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. //
  2. // YBIBImageData.m
  3. // YBImageBrowserDemo
  4. //
  5. // Created by 波儿菜 on 2019/6/5.
  6. // Copyright © 2019 波儿菜. All rights reserved.
  7. //
  8. #import "YBImage.h"
  9. #import "YBIBImageData.h"
  10. #import "YBIBImageCell.h"
  11. #import "YBIBPhotoAlbumManager.h"
  12. #import "YBIBImageData+Internal.h"
  13. #import "YBIBUtilities.h"
  14. #import "YBIBImageCache+Internal.h"
  15. #import "YBIBSentinel.h"
  16. #import "YBIBCopywriter.h"
  17. #import <AssetsLibrary/AssetsLibrary.h>
  18. extern CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
  19. static dispatch_queue_t YBIBImageProcessingQueue(void) {
  20. static dispatch_queue_t queue;
  21. static dispatch_once_t onceToken;
  22. dispatch_once(&onceToken, ^{
  23. queue = dispatch_queue_create("com.yangbo.imagebrowser.imageprocessing", DISPATCH_QUEUE_CONCURRENT);
  24. });
  25. return queue;
  26. }
  27. @implementation YBIBImageData {
  28. __weak id _downloadToken;
  29. YBIBSentinel *_cuttingSentinel;
  30. /// Stop processing tasks when in freeze.
  31. BOOL _freezing;
  32. }
  33. #pragma mark - life cycle
  34. - (void)dealloc {
  35. if (_downloadToken && [self.yb_webImageMediator() respondsToSelector:@selector(yb_cancelTaskWithDownloadToken:)]) {
  36. [self.yb_webImageMediator() yb_cancelTaskWithDownloadToken:_downloadToken];
  37. }
  38. [self.imageCache removeForKey:self.cacheKey];
  39. }
  40. - (instancetype)init {
  41. self = [super init];
  42. if (self) {
  43. [self initValue];
  44. }
  45. return self;
  46. }
  47. - (void)initValue {
  48. _defaultLayout = _layout = [YBIBImageLayout new];
  49. _loadingStatus = YBIBImageLoadingStatusNone;
  50. _compressingSize = 4096 * 4096;
  51. _shouldPreDecodeAsync = YES;
  52. _freezing = NO;
  53. _cuttingSentinel = [YBIBSentinel new];
  54. _interactionProfile = [YBIBInteractionProfile new];
  55. _allowSaveToPhotoAlbum = YES;
  56. }
  57. #pragma mark - load data
  58. - (void)loadData {
  59. _freezing = NO;
  60. // Avoid handling asynchronous tasks repeatedly.
  61. if (self.loadingStatus != YBIBImageLoadingStatusNone) {
  62. [self loadThumbImage];
  63. self.loadingStatus = self.loadingStatus;
  64. return;
  65. }
  66. if (self.originImage) {
  67. [self loadOriginImage];
  68. } else if (self.imageName || self.imagePath || self.imageData) {
  69. [self loadYBImage];
  70. } else if (self.image) {
  71. [self loadImageBlock];
  72. } else if (self.imageURL) {
  73. [self loadThumbImage];
  74. [self loadURL];
  75. } else if (self.imagePHAsset) {
  76. [self loadThumbImage];
  77. [self loadPHAsset];
  78. } else {
  79. [self.delegate yb_imageIsInvalidForData:self];
  80. }
  81. }
  82. - (void)loadOriginImage {
  83. if (_freezing) return;
  84. if (!self.originImage) return;
  85. if ([self shouldCompress]) {
  86. if (self.compressedImage) {
  87. [self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage];
  88. } else {
  89. [self loadThumbImage];
  90. [self loadOriginImage_compress];
  91. }
  92. } else {
  93. [self.delegate yb_imageData:self readyForImage:self.originImage];
  94. }
  95. }
  96. - (void)loadOriginImage_compress {
  97. if (_freezing) return;
  98. if (!self.originImage) return;
  99. self.loadingStatus = YBIBImageLoadingStatusCompressing;
  100. __weak typeof(self) wSelf = self;
  101. CGSize size = [self bestSizeOfCompressing];
  102. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
  103. if (self->_freezing) {
  104. self.loadingStatus = YBIBImageLoadingStatusNone;
  105. return;
  106. }
  107. // Ensure the best display effect.
  108. UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
  109. [self.originImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
  110. if (self->_freezing) {
  111. UIGraphicsEndImageContext();
  112. self.loadingStatus = YBIBImageLoadingStatusNone;
  113. return;
  114. }
  115. UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
  116. UIGraphicsEndImageContext();
  117. YBIB_DISPATCH_ASYNC_MAIN(^{
  118. __strong typeof(wSelf) self = wSelf;
  119. if (!self) return;
  120. self.loadingStatus = YBIBImageLoadingStatusNone;
  121. [self modifyImageWithModifier:self.compressedImageModifier image:resultImage completion:^(UIImage *processedImage) {
  122. __strong typeof(wSelf) self = wSelf;
  123. if (!self) return;
  124. self.compressedImage = processedImage ?: self.originImage;
  125. [self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage];
  126. }];
  127. })
  128. })
  129. }
  130. - (void)loadYBImage {
  131. if (_freezing) return;
  132. NSString *name = self.imageName.copy;
  133. NSString *path = self.imagePath.copy;
  134. NSData *data = self.imageData ? self.imageData().copy : nil;
  135. if (name.length == 0 && path.length == 0 && data.length == 0) return;
  136. YBImageDecodeDecision decision = [self defaultDecodeDecision];
  137. __block YBImage *image;
  138. __weak typeof(self) wSelf = self;
  139. void(^dealBlock)(void) = ^{
  140. if (name.length > 0) {
  141. image = [YBImage imageNamed:name decodeDecision:decision];
  142. } else if (path.length > 0) {
  143. image = [YBImage imageWithContentsOfFile:path decodeDecision:decision];
  144. } else if (data.length > 0) {
  145. image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision];
  146. }
  147. YBIB_DISPATCH_ASYNC_MAIN(^{
  148. __strong typeof(wSelf) self = wSelf;
  149. if (!self) return;
  150. self.loadingStatus = YBIBImageLoadingStatusNone;
  151. if (image) {
  152. [self setOriginImageAndLoadWithImage:image];
  153. } else {
  154. [self.delegate yb_imageIsInvalidForData:self];
  155. }
  156. })
  157. };
  158. if (self.shouldPreDecodeAsync) {
  159. [self loadThumbImage];
  160. self.loadingStatus = YBIBImageLoadingStatusDecoding;
  161. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock)
  162. } else {
  163. self.loadingStatus = YBIBImageLoadingStatusDecoding;
  164. dealBlock();
  165. }
  166. }
  167. - (void)loadImageBlock {
  168. if (_freezing) return;
  169. __block UIImage *image = self.image ? self.image() : nil;
  170. if (!image) return;
  171. BOOL shouldPreDecode = self.preDecodeDecision ? self.preDecodeDecision(self, image.size, image.scale) : ![self shouldCompressWithImage:image];
  172. __weak typeof(self) wSelf = self;
  173. void(^dealBlock)(void) = ^{
  174. // Do not need to decode If 'image' conformed 'YYAnimatedImage'. (Not entirely accurate.)
  175. if (![image conformsToProtocol:@protocol(YYAnimatedImage)]) {
  176. CGImageRef cgImage = YYCGImageCreateDecodedCopy(image.CGImage, shouldPreDecode);
  177. image = [UIImage imageWithCGImage:cgImage scale:image.scale orientation:image.imageOrientation];
  178. if (cgImage) CGImageRelease(cgImage);
  179. }
  180. YBIB_DISPATCH_ASYNC_MAIN(^{
  181. __strong typeof(wSelf) self = wSelf;
  182. if (!self) return;
  183. self.loadingStatus = YBIBImageLoadingStatusNone;
  184. [self setOriginImageAndLoadWithImage:image];
  185. })
  186. };
  187. if (self.shouldPreDecodeAsync) {
  188. [self loadThumbImage];
  189. self.loadingStatus = YBIBImageLoadingStatusDecoding;
  190. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock)
  191. } else {
  192. self.loadingStatus = YBIBImageLoadingStatusDecoding;
  193. dealBlock();
  194. }
  195. }
  196. - (void)loadURL {
  197. if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
  198. [self loadURL_queryCache];
  199. }
  200. - (void)loadURL_queryCache {
  201. if (_freezing) return;
  202. if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
  203. YBImageDecodeDecision decision = [self defaultDecodeDecision];
  204. self.loadingStatus = YBIBImageLoadingStatusQuerying;
  205. [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) {
  206. if (!imageData || imageData.length == 0) {
  207. YBIB_DISPATCH_ASYNC_MAIN(^{
  208. self.loadingStatus = YBIBImageLoadingStatusNone;
  209. [self loadURL_download];
  210. })
  211. return;
  212. }
  213. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
  214. if (self->_freezing) {
  215. self.loadingStatus = YBIBImageLoadingStatusNone;
  216. return;
  217. }
  218. YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision];
  219. __weak typeof(self) wSelf = self;
  220. YBIB_DISPATCH_ASYNC_MAIN(^{
  221. __strong typeof(wSelf) self = wSelf;
  222. if (!self) return;
  223. self.loadingStatus = YBIBImageLoadingStatusNone;
  224. if (image) { // Maybe the image data is invalid.
  225. [self setOriginImageAndLoadWithImage:image];
  226. } else {
  227. [self loadURL_download];
  228. }
  229. })
  230. })
  231. }];
  232. }
  233. - (void)loadURL_download {
  234. if (_freezing) return;
  235. if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
  236. YBImageDecodeDecision decision = [self defaultDecodeDecision];
  237. self.loadingStatus = YBIBImageLoadingStatusDownloading;
  238. __weak typeof(self) wSelf = self;
  239. _downloadToken = [self.yb_webImageMediator() yb_downloadImageWithURL:self.imageURL requestModifier:^NSURLRequest * _Nullable(NSURLRequest * _Nonnull request) {
  240. return self.requestModifier ? self.requestModifier(self, request) : request;
  241. } progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  242. CGFloat progress = receivedSize * 1.0 / expectedSize ?: 0;
  243. YBIB_DISPATCH_ASYNC_MAIN(^{
  244. __strong typeof(wSelf) self = wSelf;
  245. if (!self) return;
  246. [self.delegate yb_imageData:self downloadProgress:progress];
  247. })
  248. } success:^(NSData * _Nullable imageData, BOOL finished) {
  249. if (!finished) return;
  250. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
  251. if (self->_freezing) {
  252. self.loadingStatus = YBIBImageLoadingStatusNone;
  253. return;
  254. }
  255. YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision];
  256. YBIB_DISPATCH_ASYNC_MAIN(^{
  257. __strong typeof(wSelf) self = wSelf;
  258. if (!self) return;
  259. [self.yb_webImageMediator() yb_storeToDiskWithImageData:imageData forKey:self.imageURL];
  260. self.loadingStatus = YBIBImageLoadingStatusNone;
  261. if (image) {
  262. [self setOriginImageAndLoadWithImage:image];
  263. } else {
  264. [self.delegate yb_imageIsInvalidForData:self];
  265. }
  266. })
  267. })
  268. } failed:^(NSError * _Nullable error, BOOL finished) {
  269. if (!finished) return;
  270. __strong typeof(wSelf) self = wSelf;
  271. if (!self) return;
  272. self.loadingStatus = YBIBImageLoadingStatusNone;
  273. [self.delegate yb_imageDownloadFailedForData:self];
  274. }];
  275. }
  276. - (void)loadPHAsset {
  277. if (_freezing) return;
  278. if (!self.imagePHAsset) return;
  279. YBImageDecodeDecision decision = [self defaultDecodeDecision];
  280. self.loadingStatus = YBIBImageLoadingStatusReadingPHAsset;
  281. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
  282. [YBIBPhotoAlbumManager getImageDataWithPHAsset:self.imagePHAsset completion:^(NSData * _Nullable data) {
  283. if (self->_freezing) {
  284. self.loadingStatus = YBIBImageLoadingStatusNone;
  285. return;
  286. }
  287. YBImage *image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision];
  288. __weak typeof(self) wSelf = self;
  289. YBIB_DISPATCH_ASYNC_MAIN(^{
  290. __strong typeof(wSelf) self = wSelf;
  291. if (!self) return;
  292. self.loadingStatus = YBIBImageLoadingStatusNone;
  293. if (image) {
  294. [self setOriginImageAndLoadWithImage:image];
  295. } else {
  296. [self.delegate yb_imageIsInvalidForData:self];
  297. }
  298. })
  299. }];
  300. })
  301. }
  302. - (void)loadThumbImage {
  303. if (_freezing) return;
  304. if (self.thumbImage) {
  305. [self.delegate yb_imageData:self readyForThumbImage:self.thumbImage];
  306. } else if (self.thumbURL) {
  307. __weak typeof(self) wSelf = self;
  308. [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.thumbURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) {
  309. __strong typeof(wSelf) self = wSelf;
  310. if (!self) return;
  311. UIImage *thumbImage;
  312. if (image) {
  313. thumbImage = image;
  314. } else if (imageData) {
  315. thumbImage = [UIImage imageWithData:imageData];
  316. }
  317. // If the target image is ready, ignore the thumb image.
  318. BOOL shouldIgnore = [self shouldCompress] ? (self.compressedImage != nil) : (self.originImage != nil);
  319. if (!shouldIgnore && thumbImage) {
  320. [self.delegate yb_imageData:self readyForThumbImage:thumbImage];
  321. }
  322. }];
  323. } else if (self.projectiveView && [self.projectiveView isKindOfClass:UIImageView.self] && ((UIImageView *)self.projectiveView).image) {
  324. UIImage *thumbImage = ((UIImageView *)self.projectiveView).image;
  325. [self.delegate yb_imageData:self readyForThumbImage:thumbImage];
  326. }
  327. }
  328. #pragma mark - internal
  329. - (void)cuttingImageToRect:(CGRect)rect complete:(void (^)(UIImage * _Nullable))complete {
  330. if (_freezing) return;
  331. if (!self.originImage) return;
  332. int32_t value = [_cuttingSentinel increase];
  333. BOOL (^isCancelled)(void) = ^BOOL(void) {
  334. if (self->_freezing) return YES;
  335. return value != self->_cuttingSentinel.value;
  336. };
  337. YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
  338. if (isCancelled()) {
  339. complete(nil);
  340. return;
  341. }
  342. CGFloat scale = self.originImage.scale;
  343. CGFloat width = self.originImage.size.width;
  344. CGFloat height = self.originImage.size.height;
  345. BOOL reverseWidthHeight = NO;
  346. CGAffineTransform transform = CGAffineTransformIdentity;
  347. switch (self.originImage.imageOrientation) {
  348. case UIImageOrientationDown:
  349. case UIImageOrientationDownMirrored:
  350. CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI), -width, -height);
  351. break;
  352. case UIImageOrientationLeft:
  353. case UIImageOrientationLeftMirrored:
  354. transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(M_PI_2), 0, -height);
  355. reverseWidthHeight = YES;
  356. break;
  357. case UIImageOrientationRight:
  358. case UIImageOrientationRightMirrored:
  359. transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI_2), -width, 0);
  360. reverseWidthHeight = YES;
  361. break;
  362. default: break;
  363. }
  364. transform = CGAffineTransformScale(transform, scale, scale);
  365. CGRect correctRect = CGRectApplyAffineTransform(rect, transform);
  366. CGImageRef cgImage = CGImageCreateWithImageInRect(self.originImage.CGImage, correctRect);
  367. if (isCancelled()) {
  368. complete(nil);
  369. if (cgImage) CGImageRelease(cgImage);
  370. return;
  371. }
  372. CGFloat cgWidth = reverseWidthHeight ? CGImageGetHeight(cgImage) : CGImageGetWidth(cgImage);
  373. CGFloat cgHeight = reverseWidthHeight ? CGImageGetWidth(cgImage) : CGImageGetHeight(cgImage);
  374. CGSize size = [self bestSizeOfCuttingWithOriginSize:CGSizeMake(cgWidth / scale, cgHeight / scale)];
  375. UIImage *tmpImage = [UIImage imageWithCGImage:cgImage scale:self.originImage.scale orientation:self.originImage.imageOrientation];
  376. if (isCancelled()) {
  377. complete(nil);
  378. if (cgImage) CGImageRelease(cgImage);
  379. return;
  380. }
  381. // Ensure the best display effect.
  382. UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
  383. [tmpImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
  384. if (isCancelled()) {
  385. complete(nil);
  386. UIGraphicsEndImageContext();
  387. if (cgImage) CGImageRelease(cgImage);
  388. return;
  389. }
  390. UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
  391. UIGraphicsEndImageContext();
  392. if (cgImage) CGImageRelease(cgImage);
  393. __weak typeof(self) wSelf = self;
  394. YBIB_DISPATCH_ASYNC_MAIN(^{
  395. __strong typeof(wSelf) self = wSelf;
  396. if (!self) return;
  397. [self modifyImageWithModifier:self.cuttedImageModifier image:resultImage completion:^(UIImage *image) {
  398. __strong typeof(wSelf) self = wSelf;
  399. if (!self) return;
  400. complete(image);
  401. }];
  402. })
  403. })
  404. }
  405. - (BOOL)shouldCompress {
  406. return [self shouldCompressWithImage:self.originImage];
  407. }
  408. #pragma mark - public
  409. - (BOOL)shouldCompressWithImage:(UIImage *)image {
  410. if (!image) return NO;
  411. return [self shouldCompressWithImageSize:image.size scale:image.scale];
  412. }
  413. - (void)stopLoading {
  414. _freezing = YES;
  415. self.loadingStatus = YBIBImageLoadingStatusNone;
  416. }
  417. - (void)clearCache {
  418. [self.imageCache removeForKey:self.cacheKey];
  419. }
  420. #pragma mark - private
  421. /// 'size': logic pixel.
  422. - (BOOL)shouldCompressWithImageSize:(CGSize)size scale:(CGFloat)scale {
  423. return size.width * scale * size.height * scale > self.compressingSize;
  424. }
  425. /// Logic pixel.
  426. - (CGSize)bestSizeOfCompressing {
  427. if (!self.originImage) return CGSizeZero;
  428. UIDeviceOrientation orientation = self.yb_currentOrientation();
  429. CGRect imageViewFrame = [self.layout yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:self.originImage.size orientation:orientation];
  430. return imageViewFrame.size;
  431. }
  432. /// Logic pixel.
  433. - (CGSize)bestSizeOfCuttingWithOriginSize:(CGSize)originSize {
  434. CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
  435. CGFloat maxWidth = containerSize.width, maxHeight = containerSize.height;
  436. CGFloat oWidth = originSize.width, oHeight = originSize.height;
  437. if (maxWidth <= 0 || maxHeight <= 0 || oWidth <= 0 || oHeight <= 0) return CGSizeZero;
  438. if (oWidth <= maxWidth && oHeight <= maxHeight) {
  439. return originSize;
  440. }
  441. CGFloat rWidth = 0, rHeight = 0;
  442. if (oWidth / maxWidth < oHeight / maxHeight) {
  443. rHeight = maxHeight;
  444. rWidth = oWidth / oHeight * rHeight;
  445. } else {
  446. rWidth = maxWidth;
  447. rHeight = oHeight / oWidth * rWidth;
  448. }
  449. return CGSizeMake(rWidth, rHeight);
  450. }
  451. - (YBImageDecodeDecision)defaultDecodeDecision {
  452. __weak typeof(self) wSelf = self;
  453. return ^BOOL(CGSize imageSize, CGFloat scale) {
  454. __strong typeof(wSelf) self = wSelf;
  455. if (!self) return NO;
  456. CGSize logicSize = CGSizeMake(imageSize.width / scale, imageSize.height / scale);
  457. if (self.preDecodeDecision) return self.preDecodeDecision(self, logicSize, scale);
  458. if ([self shouldCompressWithImageSize:logicSize scale:scale]) return NO;
  459. return YES;
  460. };
  461. }
  462. - (void)modifyImageWithModifier:(YBIBImageModifierBlock)modifier image:(UIImage *)image completion:(void(^)(UIImage *processedImage))completion {
  463. if (modifier) {
  464. self.loadingStatus = YBIBImageLoadingStatusProcessing;
  465. __weak typeof(self) wSelf = self;
  466. modifier(self, image, ^(UIImage *processedImage){
  467. // This step is necessary, maybe 'self' is already 'dealloc' if processing code takes too much time.
  468. __strong typeof(wSelf) self = wSelf;
  469. if (!self) return;
  470. self.loadingStatus = YBIBImageLoadingStatusNone;
  471. completion(processedImage);
  472. });
  473. } else {
  474. completion(image);
  475. }
  476. }
  477. - (void)setOriginImageAndLoadWithImage:(UIImage *)image {
  478. __weak typeof(self) wSelf = self;
  479. [self modifyImageWithModifier:self.originImageModifier image:image completion:^(UIImage *processedImage) {
  480. __strong typeof(wSelf) self = wSelf;
  481. if (!self) return;
  482. self.originImage = processedImage;
  483. [self loadOriginImage];
  484. }];
  485. }
  486. - (void)saveToPhotoAlbumCompleteWithError:(nullable NSError *)error {
  487. if (error) {
  488. [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
  489. } else {
  490. [self.yb_auxiliaryViewHandler() yb_showCorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumSuccess];
  491. }
  492. }
  493. - (void)UIImageWriteToSavedPhotosAlbum_completedWithImage:(UIImage *)image error:(NSError *)error context:(void *)context {
  494. [self saveToPhotoAlbumCompleteWithError:error];
  495. }
  496. - (YBIBImageCache *)imageCache {
  497. return self.yb_backView.ybib_imageCache;
  498. }
  499. #pragma mark - <YBIBDataProtocol>
  500. @synthesize yb_isHideTransitioning = _yb_isHideTransitioning;
  501. @synthesize yb_currentOrientation = _yb_currentOrientation;
  502. @synthesize yb_containerSize = _yb_containerSize;
  503. @synthesize yb_containerView = _yb_containerView;
  504. @synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
  505. @synthesize yb_webImageMediator = _yb_webImageMediator;
  506. @synthesize yb_backView = _yb_backView;
  507. - (nonnull Class)yb_classOfCell {
  508. return YBIBImageCell.self;
  509. }
  510. - (UIView *)yb_projectiveView {
  511. return self.projectiveView;
  512. }
  513. - (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
  514. return [self.layout yb_imageViewFrameWithContainerSize:containerSize imageSize:imageSize orientation:orientation];
  515. }
  516. - (void)yb_preload {
  517. if (!self.delegate) {
  518. [self loadData];
  519. }
  520. }
  521. - (BOOL)yb_allowSaveToPhotoAlbum {
  522. return self.allowSaveToPhotoAlbum;
  523. }
  524. - (void)yb_saveToPhotoAlbum {
  525. void(^saveData)(NSData *) = ^(NSData * _Nonnull data){
  526. [[ALAssetsLibrary new] writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
  527. [self saveToPhotoAlbumCompleteWithError:error];
  528. }];
  529. };
  530. void(^saveImage)(UIImage *) = ^(UIImage * _Nonnull image){
  531. UIImageWriteToSavedPhotosAlbum(image, self, @selector(UIImageWriteToSavedPhotosAlbum_completedWithImage:error:context:), NULL);
  532. };
  533. void(^unableToSave)(void) = ^(){
  534. [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].unableToSave];
  535. };
  536. [YBIBPhotoAlbumManager getPhotoAlbumAuthorizationSuccess:^{
  537. if ([self.originImage conformsToProtocol:@protocol(YYAnimatedImage)] && [self.originImage respondsToSelector:@selector(animatedImageData)] && [self.originImage performSelector:@selector(animatedImageData)]) {
  538. NSData *data = [self.originImage performSelector:@selector(animatedImageData)];
  539. data ? saveData(data) : unableToSave();
  540. } else if (self.originImage) {
  541. saveImage(self.originImage);
  542. } else if (self.imageURL) {
  543. [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable data) {
  544. if (data) {
  545. saveData(data);
  546. } else if (image) {
  547. saveImage(image);
  548. } else {
  549. unableToSave();
  550. }
  551. }];
  552. } else {
  553. unableToSave();
  554. }
  555. } failed:^{
  556. [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].getPhotoAlbumAuthorizationFailed];
  557. }];
  558. }
  559. #pragma mark - getters & setters
  560. @synthesize delegate = _delegate;
  561. - (void)setDelegate:(id<YBIBImageDataDelegate>)delegate {
  562. _delegate = delegate;
  563. if (delegate) {
  564. [self loadData];
  565. } else {
  566. _freezing = YES;
  567. // Remove the resident cache if '_delegate' is nil.
  568. [self.imageCache removeResidentForKey:self.cacheKey];
  569. }
  570. }
  571. - (id<YBIBImageDataDelegate>)delegate {
  572. // Stop sending data to the '_delegate' if it is transiting.
  573. return self.yb_isHideTransitioning() ? nil : _delegate;
  574. }
  575. - (void)setImageURL:(NSURL *)imageURL {
  576. _imageURL = [imageURL isKindOfClass:NSString.class] ? [NSURL URLWithString:(NSString *)imageURL] : imageURL;
  577. }
  578. - (NSString *)cacheKey {
  579. return [NSString stringWithFormat:@"%p", self];
  580. }
  581. - (void)setOriginImage:(__kindof UIImage *)originImage {
  582. // 'image' should be resident if '_delegate' exists.
  583. [self.imageCache setImage:originImage type:YBIBImageCacheTypeOrigin forKey:self.cacheKey resident:self->_delegate != nil];
  584. }
  585. - (UIImage *)originImage {
  586. return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeOrigin];
  587. }
  588. - (void)setCompressedImage:(UIImage *)compressedImage {
  589. // 'image' should be resident if '_delegate' exists.
  590. [self.imageCache setImage:compressedImage type:YBIBImageCacheTypeCompressed forKey:self.cacheKey resident:_delegate != nil];
  591. }
  592. - (UIImage *)compressedImage {
  593. return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeCompressed];
  594. }
  595. - (void)setLoadingStatus:(YBIBImageLoadingStatus)loadingStatus {
  596. // Ensure thread safety.
  597. YBIB_DISPATCH_ASYNC_MAIN(^{
  598. self->_loadingStatus = loadingStatus;
  599. [self.delegate yb_imageData:self startLoadingWithStatus:loadingStatus];
  600. });
  601. }
  602. - (CGFloat)cuttingZoomScale {
  603. if (_cuttingZoomScale >= 1) return _cuttingZoomScale;
  604. _cuttingZoomScale = 1.1;
  605. if (!self.originImage) return _cuttingZoomScale;
  606. CGFloat imagePixel = self.originImage.size.width * self.originImage.size.height * self.originImage.scale * self.originImage.scale;
  607. // The larger the image size, the larger the '_cuttingZoomScale', in order to reduce the burden of CPU and memory.
  608. CGFloat scale = YBIBLowMemory() ? 0.28 : 0.19;
  609. _cuttingZoomScale += (imagePixel / self.compressingSize * scale);
  610. return _cuttingZoomScale;
  611. }
  612. @end