Skip to content

Commit

Permalink
Add support for optional progressive downloading using ImageIO (fix S…
Browse files Browse the repository at this point in the history
…DWebImage#114)

Thanks to @Xenofex for his idea and implementation example
  • Loading branch information
Olivier Poitrey committed May 9, 2012
1 parent 16d6614 commit ba71333
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 5 deletions.
5 changes: 5 additions & 0 deletions SDWebImage/MKAnnotationView+WebCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ - (void)cancelCurrentImageLoad
[[SDWebImageManager sharedManager] cancelForDelegate:self];
}

- (void)webImageManager:(SDWebImageManager *)imageManager didProgressWithPartialImage:(UIImage *)image forURL:(NSURL *)url
{
self.image = image;
}

- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
self.image = image;
Expand Down
4 changes: 4 additions & 0 deletions SDWebImage/SDWebImageDownloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ extern NSString *const SDWebImageDownloadStopNotification;
NSMutableData *imageData;
id userInfo;
BOOL lowPriority;
NSUInteger expectedSize;
BOOL progressive;
size_t width, height;
}

@property (nonatomic, retain) NSURL *url;
@property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate;
@property (nonatomic, retain) NSMutableData *imageData;
@property (nonatomic, retain) id userInfo;
@property (nonatomic, readwrite) BOOL lowPriority;
@property (nonatomic, readwrite) BOOL progressive;

+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority;
+ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo;
Expand Down
85 changes: 81 additions & 4 deletions SDWebImage/SDWebImageDownloader.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
*/

#import "SDWebImageDownloader.h"

#import "SDWebImageDecoder.h"
#import <ImageIO/ImageIO.h>

@interface SDWebImageDownloader (ImageDecoder) <SDWebImageDecoderDelegate>
@end

Expand All @@ -20,7 +21,7 @@ @interface SDWebImageDownloader ()
@end

@implementation SDWebImageDownloader
@synthesize url, delegate, connection, imageData, userInfo, lowPriority;
@synthesize url, delegate, connection, imageData, userInfo, lowPriority, progressive;

#pragma mark Public Methods

Expand Down Expand Up @@ -89,7 +90,6 @@ - (void)start

if (connection)
{
self.imageData = [NSMutableData data];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:nil];
}
else
Expand All @@ -115,7 +115,12 @@ - (void)cancel

- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)response
{
if ([response respondsToSelector:@selector(statusCode)] && [((NSHTTPURLResponse *)response) statusCode] >= 400)
if (![response respondsToSelector:@selector(statusCode)] || [((NSHTTPURLResponse *)response) statusCode] < 400)
{
expectedSize = response.expectedContentLength > 0 ? response.expectedContentLength : 0;
self.imageData = SDWIReturnAutoreleased([[NSMutableData alloc] initWithCapacity:expectedSize]);
}
else
{
[aConnection cancel];

Expand All @@ -138,6 +143,78 @@ - (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLRespo
- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data
{
[imageData appendData:data];

if (CGImageSourceCreateImageAtIndex == NULL)
{
// ImageIO isn't present in iOS < 4
self.progressive = NO;
}

if (self.progressive && expectedSize > 0 && [delegate respondsToSelector:@selector(imageDownloader:didUpdatePartialImage:)])
{
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf

/// Get the total bytes downloaded
const NSUInteger totalSize = [imageData length];

// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateIncremental(NULL);
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, totalSize == expectedSize);

if (width + height == 0)
{
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties)
{
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
CFRelease(properties);
}
}

if (width + height > 0 && totalSize < expectedSize)
{
/// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);

#ifdef TARGET_OS_IPHONE
// Workaround for iOS anamorphic image
if (partialImageRef)
{
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext)
{
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else
{
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif

if (partialImageRef)
{
UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef];
[delegate imageDownloader:self didUpdatePartialImage:image];
SDWIRelease(image);

CGImageRelease(partialImageRef);
}
}

CFRelease(imageSource);
}
}

#pragma GCC diagnostic ignored "-Wundeclared-selector"
Expand Down
1 change: 1 addition & 0 deletions SDWebImage/SDWebImageDownloaderDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@optional

- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader;
- (void)imageDownloader:(SDWebImageDownloader *)downloader didUpdatePartialImage:(UIImage *)image;
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;

Expand Down
3 changes: 2 additions & 1 deletion SDWebImage/SDWebImageManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ typedef enum
{
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3
} SDWebImageOptions;

@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate, SDImageCacheDelegate>
Expand Down
27 changes: 27 additions & 0 deletions SDWebImage/SDWebImageManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,39 @@ - (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)
downloader.lowPriority = (options & SDWebImageLowPriority);
}

if ((options & SDWebImageProgressiveDownload) && !downloader.progressive)
{
// Turn progressive download support on demand
downloader.progressive = YES;
}

[downloadDelegates addObject:delegate];
[downloaders addObject:downloader];
}

#pragma mark SDWebImageDownloaderDelegate

- (void)imageDownloader:(SDWebImageDownloader *)downloader didUpdatePartialImage:(UIImage *)image
{
// Notify all the downloadDelegates with this downloader
for (NSInteger idx = (NSInteger)[downloaders count] - 1; idx >= 0; idx--)
{
NSUInteger uidx = (NSUInteger)idx;
SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:uidx];
if (aDownloader == downloader)
{
id<SDWebImageManagerDelegate> delegate = [downloadDelegates objectAtIndex:uidx];
SDWIRetain(delegate);
SDWIAutorelease(delegate);

if ([delegate respondsToSelector:@selector(webImageManager:didProgressWithPartialImage:forURL:)])
{
objc_msgSend(delegate, @selector(webImageManager:didProgressWithPartialImage:forURL:), self, image, downloader.url);
}
}
}
}

- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
{
SDWIRetain(downloader);
Expand Down
1 change: 1 addition & 0 deletions SDWebImage/SDWebImageManagerDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

@optional

- (void)webImageManager:(SDWebImageManager *)imageManager didProgressWithPartialImage:(UIImage *)image forURL:(NSURL *)url;
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image;
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image forURL:(NSURL *)url;
- (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error;
Expand Down
8 changes: 8 additions & 0 deletions SDWebImage/UIButton+WebCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ - (void)cancelCurrentImageLoad
[[SDWebImageManager sharedManager] cancelForDelegate:self];
}

- (void)webImageManager:(SDWebImageManager *)imageManager didProgressWithPartialImage:(UIImage *)image forURL:(NSURL *)url
{
[self setImage:image forState:UIControlStateNormal];
[self setImage:image forState:UIControlStateSelected];
[self setImage:image forState:UIControlStateHighlighted];
}


- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
[self setImage:image forState:UIControlStateNormal];
Expand Down
5 changes: 5 additions & 0 deletions SDWebImage/UIImageView+WebCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ - (void)cancelCurrentImageLoad
[[SDWebImageManager sharedManager] cancelForDelegate:self];
}

- (void)webImageManager:(SDWebImageManager *)imageManager didProgressWithPartialImage:(UIImage *)image forURL:(NSURL *)url
{
self.image = image;
}

- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
self.image = image;
Expand Down

0 comments on commit ba71333

Please sign in to comment.