123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- //
- // NYXProgressiveImageView.m
- // NYXImagesKit
- //
- // Created by @Nyx0uf on 13/01/12.
- // Copyright 2012 Nyx0uf. All rights reserved.
- // www.cocoaintheshell.com
- // Caching stuff by raphaelp
- //
- #import "NYXProgressiveImageView.h"
- #import "NYXImagesHelper.h"
- #import "UIImage+Saving.h"
- #import <ImageIO/ImageIO.h>
- #import <CommonCrypto/CommonDigest.h>
- #define kNyxDefaultCacheTimeValue 604800.0f // 7 days
- #define kNyxDefaultTimeoutValue 10.0f
- typedef struct
- {
- unsigned int delegateImageDidLoadWithImage:1;
- unsigned int delegateImageDownloadCompletedWithImage:1;
- unsigned int delegateImageDownloadFailedWithData:1;
- } NyxDelegateFlags;
- @interface NYXProgressiveImageView()
- -(void)initializeAttributes;
- -(CGImageRef)createTransitoryImage:(CGImageRef)partialImage CF_RETURNS_RETAINED;
- +(NSString*)cacheDirectoryAddress;
- -(NSString*)cachedImageSystemName;
- -(void)resetCache;
- @end
- @implementation NYXProgressiveImageView
- {
- /// Image download connection
- NSURLConnection* _connection;
- /// Downloaded data
- NSMutableData* _dataTemp;
- /// Image source for progressive display
- CGImageSourceRef _imageSource;
- /// Width of the downloaded image
- int _imageWidth;
- /// Height of the downloaded image
- int _imageHeight;
- /// Expected image size
- long long _expectedSize;
- /// Image orientation
- UIImageOrientation _imageOrientation;
- /// Connection queue
- dispatch_queue_t _queue;
- /// Url
- NSURL* _url;
- /// Delegate flags, avoid to many respondsToSelector
- NyxDelegateFlags _delegateFlags;
- }
- @synthesize delegate = _delegate;
- @synthesize caching = _caching;
- @synthesize cacheTime = _cacheTime;
- @synthesize downloading = _downloading;
- #pragma mark - Allocations / Deallocations
- -(id)init
- {
- if ((self = [super init]))
- {
- [self initializeAttributes];
- }
- return self;
- }
- -(id)initWithCoder:(NSCoder*)aDecoder
- {
- if ((self = [super initWithCoder:aDecoder]))
- {
- [self initializeAttributes];
- }
- return self;
- }
- -(id)initWithFrame:(CGRect)frame
- {
- if ((self = [super initWithFrame:frame]))
- {
- [self initializeAttributes];
- }
- return self;
- }
- -(id)initWithImage:(UIImage*)image
- {
- if ((self = [super initWithImage:image]))
- {
- [self initializeAttributes];
- }
- return self;
- }
- -(id)initWithImage:(UIImage*)image highlightedImage:(UIImage*)highlightedImage
- {
- if ((self = [super initWithImage:image highlightedImage:highlightedImage]))
- {
- [self initializeAttributes];
- }
- return self;
- }
- -(void)dealloc
- {
- NYX_DISPATCH_RELEASE(_queue);
- _queue = NULL;
- }
- #pragma mark - Public
- -(void)setDelegate:(id<NYXProgressiveImageViewDelegate>)delegate
- {
- if (delegate != _delegate)
- {
- _delegate = delegate;
- _delegateFlags.delegateImageDidLoadWithImage = (unsigned)[delegate respondsToSelector:@selector(imageDidLoadWithImage:)];
- _delegateFlags.delegateImageDownloadCompletedWithImage = (unsigned)[delegate respondsToSelector:@selector(imageDownloadCompletedWithImage:)];
- _delegateFlags.delegateImageDownloadFailedWithData = (unsigned)[delegate respondsToSelector:@selector(imageDownloadFailedWithData:)];
- }
- }
- -(void)loadImageAtURL:(NSURL*)url
- {
- if (_downloading)
- return;
- _url = url;
- if (_caching)
- {
- NSFileManager* fileManager = [[NSFileManager alloc] init];
- // check if file exists on cache
- NSString* cacheDir = [NYXProgressiveImageView cacheDirectoryAddress];
- NSString* cachedImagePath = [cacheDir stringByAppendingPathComponent:[self cachedImageSystemName]];
- if ([fileManager fileExistsAtPath:cachedImagePath])
- {
- NSDate* mofificationDate = [[fileManager attributesOfItemAtPath:cachedImagePath error:nil] objectForKey:NSFileModificationDate];
- // check modification date
- if (-[mofificationDate timeIntervalSinceNow] > _cacheTime)
- {
- // Removes old cache file...
- [self resetCache];
- }
- else
- {
- // Loads image from cache without networking
- UIImage* localImage = [[UIImage alloc] initWithContentsOfFile:cachedImagePath];
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = localImage;
- if (_delegateFlags.delegateImageDidLoadWithImage)
- [_delegate imageDidLoadWithImage:localImage];
- });
- return;
- }
- }
- }
- dispatch_async(_queue, ^{
- NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:kNyxDefaultTimeoutValue];
- _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
- [_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- _downloading = YES;
- [_connection start];
- CFRunLoopRun();
- });
- }
- +(void)resetImageCache
- {
- [[NSFileManager defaultManager] removeItemAtPath:[NYXProgressiveImageView cacheDirectoryAddress] error:nil];
- }
- #pragma mark - NSURLConnectionDelegate
- -(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
- {
- #pragma unused(connection)
- _imageSource = CGImageSourceCreateIncremental(NULL);
- _imageWidth = _imageHeight = -1;
- _expectedSize = [response expectedContentLength];
- _dataTemp = [[NSMutableData alloc] init];
- }
- -(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
- {
- #pragma unused(connection)
- [_dataTemp appendData:data];
-
- const NSUInteger len = [_dataTemp length];
- CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)_dataTemp, (len == _expectedSize) ? true : false);
-
- if (_imageHeight > 0 && _imageWidth > 0)
- {
- CGImageRef cgImage = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
- if (cgImage)
- {
- //if (NYX_IOS_VERSION_LESS_THAN(@"5.0"))
- //{
- /// iOS 4.x fix to correctly handle JPEG images ( http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/ )
- /// If the image doesn't have a transparency layer, the background is black-filled
- /// So we still need to render the image, it's teh sux.
- CGImageRef imgTmp = [self createTransitoryImage:cgImage];
- if (imgTmp)
- {
- __block UIImage* img = [[UIImage alloc] initWithCGImage:imgTmp scale:1.0f orientation:_imageOrientation];
- CGImageRelease(imgTmp);
-
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = img;
- });
- }
- //}
- //else
- //{
- //__block UIImage* img = [[UIImage alloc] initWithCGImage:cgImage];
- //dispatch_async(dispatch_get_main_queue(), ^{
- //self.image = img;
- //});
- //}
- CGImageRelease(cgImage);
- }
- }
- else
- {
- CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
- if (dic)
- {
- CFTypeRef val = CFDictionaryGetValue(dic, kCGImagePropertyPixelHeight);
- if (val)
- CFNumberGetValue(val, kCFNumberIntType, &_imageHeight);
- val = CFDictionaryGetValue(dic, kCGImagePropertyPixelWidth);
- if (val)
- CFNumberGetValue(val, kCFNumberIntType, &_imageWidth);
- val = CFDictionaryGetValue(dic, kCGImagePropertyOrientation);
- if (val)
- {
- int orientation; // Note: This is an EXIF int for orientation, a number between 1 and 8
- CFNumberGetValue(val, kCFNumberIntType, &orientation);
- _imageOrientation = [NYXProgressiveImageView exifOrientationToiOSOrientation:orientation];
- }
- else
- _imageOrientation = UIImageOrientationUp;
- CFRelease(dic);
- }
- }
- }
- -(void)connectionDidFinishLoading:(NSURLConnection*)connection
- {
- #pragma unused(connection)
- if (_dataTemp)
- {
- UIImage* img = [[UIImage alloc] initWithData:_dataTemp];
- dispatch_sync(dispatch_get_main_queue(), ^{
- if (_delegateFlags.delegateImageDownloadCompletedWithImage)
- [_delegate imageDownloadCompletedWithImage:img];
- self.image = img;
- if (_delegateFlags.delegateImageDidLoadWithImage)
- [_delegate imageDidLoadWithImage:img];
- });
- if (_caching)
- {
- // Create cache directory if it doesn't exist
- BOOL isDir = YES;
-
- NSFileManager* fileManager = [[NSFileManager alloc] init];
-
- NSString* cacheDir = [NYXProgressiveImageView cacheDirectoryAddress];
- if (![fileManager fileExistsAtPath:cacheDir isDirectory:&isDir])
- [fileManager createDirectoryAtPath:cacheDir withIntermediateDirectories:NO attributes:nil error:nil];
-
- NSString* path = [cacheDir stringByAppendingPathComponent:[self cachedImageSystemName]];
- [img saveToPath:path uti:CGImageSourceGetType(_imageSource) backgroundFillColor:nil];
- }
-
- _dataTemp = nil;
- }
-
- if (_imageSource)
- CFRelease(_imageSource);
- _connection = nil;
- _url = nil;
- _downloading = NO;
- CFRunLoopStop(CFRunLoopGetCurrent());
- }
- -(void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
- {
- #pragma unused(connection)
- #pragma unused(error)
- if (_delegateFlags.delegateImageDownloadFailedWithData)
- {
- dispatch_sync(dispatch_get_main_queue(), ^{
- [_delegate imageDownloadFailedWithData:_dataTemp];
- });
- }
- _dataTemp = nil;
- if (_imageSource)
- CFRelease(_imageSource);
- _connection = nil;
- _url = nil;
- _downloading = NO;
- CFRunLoopStop(CFRunLoopGetCurrent());
- }
- #pragma mark - Private
- -(void)initializeAttributes
- {
- _cacheTime = kNyxDefaultCacheTimeValue;
- _caching = NO;
- _queue = dispatch_queue_create("com.cits.pdlqueue", DISPATCH_QUEUE_SERIAL);
- _imageOrientation = UIImageOrientationUp;
- }
- -(CGImageRef)createTransitoryImage:(CGImageRef)partialImage
- {
- const size_t partialHeight = CGImageGetHeight(partialImage);
- CGContextRef bmContext = NYXCreateARGBBitmapContext((size_t)_imageWidth, (size_t)_imageHeight, (size_t)_imageWidth * 4);
- if (!bmContext)
- return NULL;
- CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _imageWidth, .size.height = partialHeight}, partialImage);
- CGImageRef goodImageRef = CGBitmapContextCreateImage(bmContext);
- CGContextRelease(bmContext);
- return goodImageRef;
- }
- +(NSString*)cacheDirectoryAddress
- {
- NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
- NSString* documentsDirectoryPath = [paths objectAtIndex:0];
- return [documentsDirectoryPath stringByAppendingPathComponent:@"NYXProgressiveImageViewCache"];
- }
- -(NSString*)cachedImageSystemName
- {
- const char* concat_str = [[_url absoluteString] UTF8String];
- if (!concat_str)
- return @"";
- unsigned char result[CC_MD5_DIGEST_LENGTH];
- CC_MD5(concat_str, (int)strlen(concat_str), result);
- NSMutableString* hash = [[NSMutableString alloc] init];
- for (unsigned int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
- [hash appendFormat:@"%02X", result[i]];
- return [hash lowercaseString];
- }
- -(void)resetCache
- {
- [[[NSFileManager alloc] init] removeItemAtPath:[[NYXProgressiveImageView cacheDirectoryAddress] stringByAppendingPathComponent:[self cachedImageSystemName]] error:nil];
- }
- +(UIImageOrientation)exifOrientationToiOSOrientation:(int)exifOrientation
- {
- UIImageOrientation orientation = UIImageOrientationUp;
- switch (exifOrientation)
- {
- case 1:
- orientation = UIImageOrientationUp;
- break;
- case 3:
- orientation = UIImageOrientationDown;
- break;
- case 8:
- orientation = UIImageOrientationLeft;
- break;
- case 6:
- orientation = UIImageOrientationRight;
- break;
- case 2:
- orientation = UIImageOrientationUpMirrored;
- break;
- case 4:
- orientation = UIImageOrientationDownMirrored;
- break;
- case 5:
- orientation = UIImageOrientationLeftMirrored;
- break;
- case 7:
- orientation = UIImageOrientationRightMirrored;
- break;
- default:
- break;
- }
- return orientation;
- }
- @end
|