IDMPhoto.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. //
  2. // IDMPhoto.m
  3. // IDMPhotoBrowser
  4. //
  5. // Created by Michael Waterfall on 17/10/2010.
  6. // Copyright 2010 d3i. All rights reserved.
  7. //
  8. #import "IDMPhoto.h"
  9. #import "IDMPhotoBrowser.h"
  10. // Private
  11. @interface IDMPhoto () {
  12. // Image Sources
  13. NSString *_photoPath;
  14. // Image
  15. UIImage *_underlyingImage;
  16. // Other
  17. NSString *_caption;
  18. BOOL _loadingInProgress;
  19. }
  20. // Properties
  21. @property (nonatomic, strong) UIImage *underlyingImage;
  22. // Methods
  23. - (void)imageLoadingComplete;
  24. @end
  25. // IDMPhoto
  26. @implementation IDMPhoto
  27. // Properties
  28. @synthesize underlyingImage = _underlyingImage,
  29. photoURL = _photoURL,
  30. caption = _caption;
  31. #pragma mark Class Methods
  32. + (IDMPhoto *)photoWithImage:(UIImage *)image {
  33. return [[IDMPhoto alloc] initWithImage:image];
  34. }
  35. + (IDMPhoto *)photoWithFilePath:(NSString *)path {
  36. return [[IDMPhoto alloc] initWithFilePath:path];
  37. }
  38. + (IDMPhoto *)photoWithURL:(NSURL *)url {
  39. return [[IDMPhoto alloc] initWithURL:url];
  40. }
  41. + (NSArray *)photosWithImages:(NSArray *)imagesArray {
  42. NSMutableArray *photos = [NSMutableArray arrayWithCapacity:imagesArray.count];
  43. for (UIImage *image in imagesArray) {
  44. if ([image isKindOfClass:[UIImage class]]) {
  45. IDMPhoto *photo = [IDMPhoto photoWithImage:image];
  46. [photos addObject:photo];
  47. }
  48. }
  49. return photos;
  50. }
  51. + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray {
  52. NSMutableArray *photos = [NSMutableArray arrayWithCapacity:pathsArray.count];
  53. for (NSString *path in pathsArray) {
  54. if ([path isKindOfClass:[NSString class]]) {
  55. IDMPhoto *photo = [IDMPhoto photoWithFilePath:path];
  56. [photos addObject:photo];
  57. }
  58. }
  59. return photos;
  60. }
  61. + (NSArray *)photosWithURLs:(NSArray *)urlsArray {
  62. NSMutableArray *photos = [NSMutableArray arrayWithCapacity:urlsArray.count];
  63. for (id url in urlsArray) {
  64. if ([url isKindOfClass:[NSURL class]]) {
  65. IDMPhoto *photo = [IDMPhoto photoWithURL:url];
  66. [photos addObject:photo];
  67. }
  68. else if ([url isKindOfClass:[NSString class]]) {
  69. IDMPhoto *photo = [IDMPhoto photoWithURL:[NSURL URLWithString:url]];
  70. [photos addObject:photo];
  71. }
  72. }
  73. return photos;
  74. }
  75. #pragma mark NSObject
  76. - (id)initWithImage:(UIImage *)image {
  77. if ((self = [super init])) {
  78. self.underlyingImage = image;
  79. }
  80. return self;
  81. }
  82. - (id)initWithFilePath:(NSString *)path {
  83. if ((self = [super init])) {
  84. _photoPath = [path copy];
  85. }
  86. return self;
  87. }
  88. - (id)initWithURL:(NSURL *)url {
  89. if ((self = [super init])) {
  90. _photoURL = [url copy];
  91. }
  92. return self;
  93. }
  94. #pragma mark IDMPhoto Protocol Methods
  95. - (UIImage *)underlyingImage {
  96. return _underlyingImage;
  97. }
  98. - (void)loadUnderlyingImageAndNotify {
  99. NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
  100. _loadingInProgress = YES;
  101. if (self.underlyingImage) {
  102. // Image already loaded
  103. [self imageLoadingComplete];
  104. } else {
  105. if (_photoPath) {
  106. // Load async from file
  107. [self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil];
  108. } else if (_photoURL) {
  109. // Load async from web (using SDWebImageManager)
  110. SDWebImageManager *manager = [SDWebImageManager sharedManager];
  111. [manager loadImageWithURL:_photoURL options:SDWebImageRetryFailed|SDWebImageHandleCookies progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
  112. CGFloat progress = ((CGFloat)receivedSize)/((CGFloat)expectedSize);
  113. if (self.progressUpdateBlock) {
  114. self.progressUpdateBlock(progress);
  115. }
  116. } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  117. if (image) {
  118. self.underlyingImage = image;
  119. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  120. }
  121. }];
  122. } else {
  123. // Failed - no source
  124. self.underlyingImage = nil;
  125. [self imageLoadingComplete];
  126. }
  127. }
  128. }
  129. // Release if we can get it again from path or url
  130. - (void)unloadUnderlyingImage {
  131. _loadingInProgress = NO;
  132. if (self.underlyingImage && (_photoPath || _photoURL)) {
  133. self.underlyingImage = nil;
  134. }
  135. }
  136. #pragma mark - Async Loading
  137. /*- (UIImage *)decodedImageWithImage:(UIImage *)image {
  138. CGImageRef imageRef = image.CGImage;
  139. // System only supports RGB, set explicitly and prevent context error
  140. // if the downloaded image is not the supported format
  141. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  142. CGContextRef context = CGBitmapContextCreate(NULL,
  143. CGImageGetWidth(imageRef),
  144. CGImageGetHeight(imageRef),
  145. 8,
  146. // width * 4 will be enough because are in ARGB format, don't read from the image
  147. CGImageGetWidth(imageRef) * 4,
  148. colorSpace,
  149. // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
  150. // makes system don't need to do extra conversion when displayed.
  151. kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
  152. CGColorSpaceRelease(colorSpace);
  153. if ( ! context) {
  154. return nil;
  155. }
  156. CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)};
  157. CGContextDrawImage(context, rect, imageRef);
  158. CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
  159. CGContextRelease(context);
  160. UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef];
  161. CGImageRelease(decompressedImageRef);
  162. return decompressedImage;
  163. }*/
  164. - (UIImage *)decodedImageWithImage:(UIImage *)image {
  165. if (image.images) {
  166. // Do not decode animated images
  167. return image;
  168. }
  169. CGImageRef imageRef = image.CGImage;
  170. CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
  171. CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize};
  172. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  173. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
  174. int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask);
  175. BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone ||
  176. infoMask == kCGImageAlphaNoneSkipFirst ||
  177. infoMask == kCGImageAlphaNoneSkipLast);
  178. // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB.
  179. // https://developer.apple.com/library/mac/#qa/qa1037/_index.html
  180. if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1)
  181. {
  182. // Unset the old alpha info.
  183. bitmapInfo &= ~kCGBitmapAlphaInfoMask;
  184. // Set noneSkipFirst.
  185. bitmapInfo |= kCGImageAlphaNoneSkipFirst;
  186. }
  187. // Some PNGs tell us they have alpha but only 3 components. Odd.
  188. else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3)
  189. {
  190. // Unset the old alpha info.
  191. bitmapInfo &= ~kCGBitmapAlphaInfoMask;
  192. bitmapInfo |= kCGImageAlphaPremultipliedFirst;
  193. }
  194. // It calculates the bytes-per-row based on the bitsPerComponent and width arguments.
  195. CGContextRef context = CGBitmapContextCreate(NULL,
  196. imageSize.width,
  197. imageSize.height,
  198. CGImageGetBitsPerComponent(imageRef),
  199. 0,
  200. colorSpace,
  201. bitmapInfo);
  202. CGColorSpaceRelease(colorSpace);
  203. // If failed, return undecompressed image
  204. if (!context) return image;
  205. CGContextDrawImage(context, imageRect, imageRef);
  206. CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
  207. CGContextRelease(context);
  208. UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation];
  209. CGImageRelease(decompressedImageRef);
  210. return decompressedImage;
  211. }
  212. // Called in background
  213. // Load image in background from local file
  214. - (void)loadImageFromFileAsync {
  215. @autoreleasepool {
  216. @try {
  217. self.underlyingImage = [UIImage imageWithContentsOfFile:_photoPath];
  218. if (!_underlyingImage) {
  219. //IDMLog(@"Error loading photo from path: %@", _photoPath);
  220. }
  221. } @finally {
  222. self.underlyingImage = [self decodedImageWithImage: self.underlyingImage];
  223. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  224. }
  225. }
  226. }
  227. // Called on main
  228. - (void)imageLoadingComplete {
  229. NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
  230. // Complete so notify
  231. _loadingInProgress = NO;
  232. [[NSNotificationCenter defaultCenter] postNotificationName:IDMPhoto_LOADING_DID_END_NOTIFICATION
  233. object:self];
  234. }
  235. @end