Merge branch 'master' into 5.x

This commit is contained in:
Bogdan Poplauschi 2017-10-27 10:17:44 +03:00
commit ab06d2370b
17 changed files with 239 additions and 50 deletions

View File

@ -1,3 +1,28 @@
## [4.2.0 - Pluginable coders, on Oct XX, 2017](https://github.com/rs/SDWebImage/releases/tag/4.2.0)
See [all tickets marked for the 4.2.0 release](https://github.com/rs/SDWebImage/milestone/16)
#### Features
- Refactor decoding code and provide decoding plugin #1991
- HEIC format support #2080 #1853 #2038
- Welcome back our previous `UIImage+GIF` category for animated GIF! Not enabled by default. #2064
- Add the animated GIF encoding support for `SDWebImageGIFCoder` on `macOS` (use `NSImage` API) #2067
- Implemented `-[NSImage isGIF]` method to return whether current `NSImage` has GIF representation #2071
- Allow user to provide reading options such as mapped file to improve performance in `SDImageCache` disk cache #2057
- Add progressive image load for WebP images #1991 #1987 #1986
- CI builds use Xcode 9
- Fixed our tests and improved the code coverage #2068
#### Fixes
- Fixed lost orientation issues #1991 #2034 #2026 #1014 #1040 #815
- Fixed `UIImage(GIF) sd_animatedGIFWithData` crash #1991 #1837
- Fixed progressive WebP height issue #2066
- Fixed `SDWebImageManager.m __destroy_helper_block_` crash #2048 #1941
- Fixed cached image filename are sometimes generated with invalid path extensions #2061
- Fixed GIF performance problem #1901 by creating `FLAnimatedImage` instance on global queue #2047
#### Docs
- Updated diagrams
## [4.1.2 - 4.1 patch, on Oct 9th, 2017](https://github.com/rs/SDWebImage/releases/tag/4.1.2)
See [all tickets marked for the 4.1.2 release](https://github.com/rs/SDWebImage/milestone/17)

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '4.1.2'
s.version = '4.2.0'
s.osx.deployment_target = '10.8'
s.ios.deployment_target = '7.0'

View File

@ -52,17 +52,25 @@
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
// This setImageBlock may not called from main queue
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
FLAnimatedImage *animatedImage;
if (imageFormat == SDImageFormatGIF) {
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
weakSelf.image = nil;
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
}
dispatch_main_async_safe(^{
if (animatedImage) {
weakSelf.animatedImage = animatedImage;
weakSelf.image = nil;
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
}
});
}
progress:progressBlock
completed:completedBlock];
completed:completedBlock
context:@{SDWebImageInternalSetImageInGlobalQueueKey: @(YES)}];
}
@end

View File

@ -16,7 +16,8 @@ typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP
SDImageFormatWebP,
SDImageFormatHEIC
};
@interface NSData (ImageContentType)

View File

@ -14,6 +14,11 @@
#import <MobileCoreServices/MobileCoreServices.h>
#endif
// Currently Image/IO does not support WebP
#define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp")
// AVFileTypeHEIC is defined in AVFoundation via iOS 11, we use this without import AVFoundation
#define kSDUTTypeHEIC ((__bridge CFStringRef)@"public.heic")
@implementation NSData (ImageContentType)
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
@ -21,6 +26,7 @@
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
@ -33,16 +39,26 @@
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52:
// R as RIFF for WEBP
if (data.length < 12) {
return SDImageFormatUndefined;
case 0x52: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]) {
return SDImageFormatHEIC;
}
}
break;
}
}
return SDImageFormatUndefined;
}
@ -62,6 +78,12 @@
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
break;
case SDImageFormatHEIC:
UTType = kSDUTTypeHEIC;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;

View File

@ -23,7 +23,18 @@
}
- (BOOL)isGIF {
return NO;
BOOL isGIF = NO;
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
NSUInteger frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
if (frameCount > 1) {
isGIF = YES;
break;
}
}
}
return isGIF;
}
@end

View File

@ -174,10 +174,11 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}

View File

@ -33,10 +33,12 @@ CG_EXTERN BOOL SDCGImageRefContainsAlpha(_Nullable CGImageRef imageRef);
/**
This is the image coder protocol to provide custom image decoding/encoding.
These methods are all required to implement.
@note Pay attention that these methods are not called from main queue.
*/
@protocol SDWebImageCoder <NSObject>
@required
#pragma mark - Decoding
/**
Returns YES if this coder can decode some data. Otherwise, the data should be passed to another coder.
@ -90,10 +92,12 @@ CG_EXTERN BOOL SDCGImageRefContainsAlpha(_Nullable CGImageRef imageRef);
/**
This is the image coder protocol to provide custom progressive image decoding.
These methods are all required to implement.
@note Pay attention that these methods are not called from main queue.
*/
@protocol SDWebImageProgressiveCoder <SDWebImageCoder>
@required
/**
Returns YES if this coder can incremental decode some data. Otherwise, it should be passed to another coder.

View File

@ -11,13 +11,13 @@
/**
Global object holding the array of coders, so that we avoid passing them from object to object.
Uses a priority queue behind scenes, which means the latest added coders have priority.
Uses a priority queue behind scenes, which means the latest added coders have the highest priority.
This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data.
That way, users can add their custom coders while preserving our existing prebuilt ones
Note: the `coders` getter will return the coders in their reversed order
Example:
- by default we internally set coders = `IOCoder`, `WebPCoder`
- by default we internally set coders = `IOCoder`, `WebPCoder`. (`GIFCoder` is not recommended to add only if you want to get GIF support without `FLAnimatedImage`)
- calling `coders` will return `@[WebPCoder, IOCoder]`
- call `[addCoder:[MyCrazyCoder new]]`
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, IOCoder]`

View File

@ -332,6 +332,7 @@ didReceiveResponse:(NSURLResponse *)response
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] init];
break;
}
}
}

View File

@ -13,7 +13,8 @@
Built in coder using ImageIO that supports GIF encoding/decoding
@note `SDWebImageIOCoder` supports GIF but only as static (will use the 1st frame).
@note Use `SDWebImageGIFCoder` for fully animated GIFs - less performant than `FLAnimatedImage`
@note The recommended approach for animated GIFs is using `FLAnimatedImage`
@note If you decide to make all `UIImageView`(including `FLAnimatedImageView`) instance support GIF. You should add this coder to `SDWebImageCodersManager` and make sure that it has a higher priority than `SDWebImageIOCoder`
@note The recommended approach for animated GIFs is using `FLAnimatedImage`. It's more performant than `UIImageView` for GIF displaying
*/
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder>

View File

@ -10,9 +10,15 @@
#import "SDWebImageCoder.h"
/**
Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding
Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding.
GIF
Also supports static GIF (meaning will only handle the 1st frame).
For a full GIF support, we recommend `FLAnimatedImage` or our less performant `SDWebImageGIFCoder`
HEIC
This coder also supports HEIC format because ImageIO supports it natively. But it depends on the system capabilities, so it won't work on all devices.
Hardware works if: (iOS 11 || macOS 10.13) && (isMac || isIPhoneAndA10FusionChipAbove) && (!Simulator)
*/
@interface SDWebImageImageIOCoder : NSObject <SDWebImageProgressiveCoder>

View File

@ -66,8 +66,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
// Do not support WebP decoding
case SDImageFormatWebP:
// Do not support WebP decoding
return NO;
default:
return YES;
@ -76,8 +76,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
// Support static GIF progressive decoding
case SDImageFormatWebP:
// Do not support WebP progressive decoding
return NO;
default:
return YES;
@ -100,7 +100,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
if (format == SDImageFormatGIF) {
// static single GIF need to be created animated for FLAnimatedImageView logic
// static single GIF need to be created animated for `FLAnimatedImage` logic
// GIF does not support EXIF image orientation
image = [UIImage animatedImageWithImages:@[image] duration:image.duration];
return image;
@ -213,7 +213,16 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (!shouldScaleDown) {
return [self sd_decompressedImageWithImage:image];
} else {
return [self sd_decompressedAndScaledDownImageWithImage:image];
UIImage *scaledDownImage = [self sd_decompressedAndScaledDownImageWithImage:image];
if (scaledDownImage && !CGSizeEqualToSize(scaledDownImage.size, image.size)) {
// if the image is scaled down, need to modify the data pointer as well
SDImageFormat format = [NSData sd_imageFormatForImageData:*data];
NSData *imageData = [self encodedDataWithImage:scaledDownImage format:format];
if (imageData) {
*data = imageData;
}
}
return scaledDownImage;
}
#endif
}
@ -385,9 +394,12 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
// Do not support WebP encoding
case SDImageFormatWebP:
// Do not support WebP encoding
return NO;
case SDImageFormatHEIC:
// Check HEIC encoding compatibility
return [[self class] canEncodeToHEICFormat];
default:
return YES;
}
@ -460,6 +472,27 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return YES;
}
+ (BOOL)canEncodeToHEICFormat {
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can't encode to HEIC
canEncode = NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
canEncode = YES;
}
});
return canEncode;
}
#if SD_UIKIT || SD_WATCH
#pragma mark EXIF orientation tag converter
+ (UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {

View File

@ -12,6 +12,8 @@
#import "SDWebImageManager.h"
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageInGlobalQueueKey;
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
@interface UIView (WebCache)
@ -50,6 +52,34 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
* @param context A context with extra information to perform specify changes or processes.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context;
/**
* Cancel the current download
*/

View File

@ -13,6 +13,8 @@
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
NSString * const SDWebImageInternalSetImageInGlobalQueueKey = @"setImageInGlobalQueue";
static char imageURLKey;
#if SD_UIKIT
@ -34,6 +36,17 @@ static char TAG_ACTIVITY_SHOW;
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
@ -54,28 +67,48 @@ static char TAG_ACTIVITY_SHOW;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
if (!sself) { return; }
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_async(targetQueue, ^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

View File

@ -218,6 +218,19 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
[self waitForExpectationsWithCommonTimeout];
}
- (void)test34CachePathForSimpleKeyWithExtension {
NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:kTestJpegURL inPath:@""];
expect(cachePath).toNot.beNil();
expect([cachePath pathExtension]).to.equal(@"jpg");
}
- (void)test35CachePathForKeyWithDotButNoExtension {
NSString *urlString = @"https://maps.googleapis.com/maps/api/staticmap?center=48.8566,2.3522&format=png&maptype=roadmap&scale=2&size=375x200&zoom=15";
NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:urlString inPath:@""];
expect(cachePath).toNot.beNil();
expect([cachePath pathExtension]).to.equal(@"");
}
- (void)test40InsertionOfImageData {
XCTestExpectation *expectation = [self expectationWithDescription:@"Insertion of image data works"];

View File

@ -15,11 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.1.2</string>
<string>4.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4.1.2</string>
<string>4.2.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>