// // IDMPhoto.m // IDMPhotoBrowser // // Created by Michael Waterfall on 17/10/2010. // Copyright 2010 d3i. All rights reserved. // #import "IDMPhoto.h" #import "IDMPhotoBrowser.h" // Private @interface IDMPhoto () { // Image Sources NSString *_photoPath; // Image UIImage *_underlyingImage; // Other NSString *_caption; BOOL _loadingInProgress; } // Properties @property (nonatomic, strong) UIImage *underlyingImage; // Methods - (void)imageLoadingComplete; @end // IDMPhoto @implementation IDMPhoto // Properties @synthesize underlyingImage = _underlyingImage, photoURL = _photoURL, caption = _caption; #pragma mark Class Methods + (IDMPhoto *)photoWithImage:(UIImage *)image { return [[IDMPhoto alloc] initWithImage:image]; } + (IDMPhoto *)photoWithFilePath:(NSString *)path { return [[IDMPhoto alloc] initWithFilePath:path]; } + (IDMPhoto *)photoWithURL:(NSURL *)url { return [[IDMPhoto alloc] initWithURL:url]; } + (NSArray *)photosWithImages:(NSArray *)imagesArray { NSMutableArray *photos = [NSMutableArray arrayWithCapacity:imagesArray.count]; for (UIImage *image in imagesArray) { if ([image isKindOfClass:[UIImage class]]) { IDMPhoto *photo = [IDMPhoto photoWithImage:image]; [photos addObject:photo]; } } return photos; } + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray { NSMutableArray *photos = [NSMutableArray arrayWithCapacity:pathsArray.count]; for (NSString *path in pathsArray) { if ([path isKindOfClass:[NSString class]]) { IDMPhoto *photo = [IDMPhoto photoWithFilePath:path]; [photos addObject:photo]; } } return photos; } + (NSArray *)photosWithURLs:(NSArray *)urlsArray { NSMutableArray *photos = [NSMutableArray arrayWithCapacity:urlsArray.count]; for (id url in urlsArray) { if ([url isKindOfClass:[NSURL class]]) { IDMPhoto *photo = [IDMPhoto photoWithURL:url]; [photos addObject:photo]; } else if ([url isKindOfClass:[NSString class]]) { IDMPhoto *photo = [IDMPhoto photoWithURL:[NSURL URLWithString:url]]; [photos addObject:photo]; } } return photos; } #pragma mark NSObject - (id)initWithImage:(UIImage *)image { if ((self = [super init])) { self.underlyingImage = image; } return self; } - (id)initWithFilePath:(NSString *)path { if ((self = [super init])) { _photoPath = [path copy]; } return self; } - (id)initWithURL:(NSURL *)url { if ((self = [super init])) { _photoURL = [url copy]; } return self; } #pragma mark IDMPhoto Protocol Methods - (UIImage *)underlyingImage { return _underlyingImage; } - (void)loadUnderlyingImageAndNotify { NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); _loadingInProgress = YES; if (self.underlyingImage) { // Image already loaded [self imageLoadingComplete]; } else { if (_photoPath) { // Load async from file [self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil]; } else if (_photoURL) { // Load async from web (using SDWebImageManager) SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager loadImageWithURL:_photoURL options:SDWebImageRetryFailed|SDWebImageHandleCookies progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { CGFloat progress = ((CGFloat)receivedSize)/((CGFloat)expectedSize); if (self.progressUpdateBlock) { self.progressUpdateBlock(progress); } } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (image) { self.underlyingImage = image; [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; } }]; } else { // Failed - no source self.underlyingImage = nil; [self imageLoadingComplete]; } } } // Release if we can get it again from path or url - (void)unloadUnderlyingImage { _loadingInProgress = NO; if (self.underlyingImage && (_photoPath || _photoURL)) { self.underlyingImage = nil; } } #pragma mark - Async Loading /*- (UIImage *)decodedImageWithImage:(UIImage *)image { CGImageRef imageRef = image.CGImage; // System only supports RGB, set explicitly and prevent context error // if the downloaded image is not the supported format CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, // width * 4 will be enough because are in ARGB format, don't read from the image CGImageGetWidth(imageRef) * 4, colorSpace, // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little // makes system don't need to do extra conversion when displayed. kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); CGColorSpaceRelease(colorSpace); if ( ! context) { return nil; } CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}; CGContextDrawImage(context, rect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef]; CGImageRelease(decompressedImageRef); return decompressedImage; }*/ - (UIImage *)decodedImageWithImage:(UIImage *)image { if (image.images) { // Do not decode animated images return image; } CGImageRef imageRef = image.CGImage; CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || infoMask == kCGImageAlphaNoneSkipFirst || infoMask == kCGImageAlphaNoneSkipLast); // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. // https://developer.apple.com/library/mac/#qa/qa1037/_index.html if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) { // Unset the old alpha info. bitmapInfo &= ~kCGBitmapAlphaInfoMask; // Set noneSkipFirst. bitmapInfo |= kCGImageAlphaNoneSkipFirst; } // Some PNGs tell us they have alpha but only 3 components. Odd. else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) { // Unset the old alpha info. bitmapInfo &= ~kCGBitmapAlphaInfoMask; bitmapInfo |= kCGImageAlphaPremultipliedFirst; } // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. CGContextRef context = CGBitmapContextCreate(NULL, imageSize.width, imageSize.height, CGImageGetBitsPerComponent(imageRef), 0, colorSpace, bitmapInfo); CGColorSpaceRelease(colorSpace); // If failed, return undecompressed image if (!context) return image; CGContextDrawImage(context, imageRect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; CGImageRelease(decompressedImageRef); return decompressedImage; } // Called in background // Load image in background from local file - (void)loadImageFromFileAsync { @autoreleasepool { @try { self.underlyingImage = [UIImage imageWithContentsOfFile:_photoPath]; if (!_underlyingImage) { //IDMLog(@"Error loading photo from path: %@", _photoPath); } } @finally { self.underlyingImage = [self decodedImageWithImage: self.underlyingImage]; [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; } } } // Called on main - (void)imageLoadingComplete { NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); // Complete so notify _loadingInProgress = NO; [[NSNotificationCenter defaultCenter] postNotificationName:IDMPhoto_LOADING_DID_END_NOTIFICATION object:self]; } @end