Merge branch 'master' into fix-memory-cache

This commit is contained in:
Kinarobin 2020-10-12 19:52:23 +08:00 committed by GitHub
commit 7c70e574c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 291 additions and 68 deletions

View File

@ -1,3 +1,36 @@
## [5.9.2 - 5.9 Patch, on Sep 29th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.2)
See [all tickets marked for the 5.9.2 release](https://github.com/SDWebImage/SDWebImage/milestone/78)
### Fixes
- Fix the issue that SDAnimatedImageView will trigger an empty callback when animation stopped. This will cause some bad effect such as rendering a empty image or placeholder image (especially on iOS 14) #3092
- Fix: `duration` is not used in SDWebImageTransition convenience initializers. #3094
## [5.9.1 - 5.9 Patch, on Sep 11th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.1)
See [all tickets marked for the 5.9.1 release](https://github.com/SDWebImage/SDWebImage/milestone/77)
### Fixes
- Fix the issue of SDAnimatedImage initWithContentsOfFile where the path name less than 3 characters #3081
## [5.9.0 - iOS 14 WebP, on Aug 27th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.9.0)
See [all tickets marked for the 5.9.0 release](https://github.com/SDWebImage/SDWebImage/milestone/72)
### Features
#### Image
- Supports built-in WebP/AWebP codec from ImageIO for iOS 14/tvOS 14/macOS 11/watchOS 7 #3048
- To use, add `SDImageAWebPCoder` to your coders manager. Note built-in WebP currently supports decoding only, for encoding, you still need `SDImageWebPCoder`
- Add the support to pass small bytes to `decodedAndScaledDownLargeImage`, which always scale down (at least 1x1 pixel) but not return the original size #3067
#### Cache
- Supports the user to customize the default disk cache directory, which can be used to share cache for App && App Extension #3066
#### View Category
- Adjust the current behavior to use transition. Now it automatically do transition when manager callback asynchronously (if user see waiting, then do transition) #3074
### Fixes
- Fix the bug when the thumbnail pixel size is larger than the pixel size, and the image has EXIF orientation, the final UIImage will use wrong image orientation #3070
- Fix the image format detection for smaller SVG which less than 100 Bytes #3072
## [5.8.4 - 5.8 Patch, on July 16th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.8.4)
See [all tickets marked for the 5.8.4 release](https://github.com/SDWebImage/SDWebImage/milestone/76)

View File

@ -47,7 +47,7 @@
self.imageView4.sd_imageTransition = SDWebImageTransition.fadeTransition;
self.imageView4.imageScaling = NSImageScaleProportionallyUpOrDown;
self.imageView4.imageAlignment = NSImageAlignLeft; // supports NSImageView's layout properties
[self.imageView4 sd_setImageWithURL:[NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"] placeholderImage:nil options:SDWebImageForceTransition];
[self.imageView4 sd_setImageWithURL:[NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"]];
NSMenu *menu2 = [[NSMenu alloc] initWithTitle:@"Toggle Animation"];
NSMenuItem *item2 = [menu2 addItemWithTitle:@"Toggle Animation" action:@selector(toggleAnimation:) keyEquivalent:@""];
item2.tag = 2;

View File

@ -1,4 +1,4 @@
Copyright (c) 2009-2018 Olivier Poitrey rs@dailymotion.com
Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -38,7 +38,7 @@ This library provides an async image downloader with cache support. For convenie
## Supported Image Formats
- Image formats supported by Apple system (JPEG, PNG, TIFF, HEIC, ...), including GIF/APNG/HEIC animation
- WebP format, including animated WebP (use the [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) project)
- WebP format, including animated WebP (use the [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) project). Note iOS 14/macOS 11.0 supports built-in WebP decoding (no encoding).
- Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List)
## Additional modules and Ecosystem
@ -54,7 +54,7 @@ We support SwiftUI by building a brand new framework called [SDWebImageSwiftUI](
The new framework introduce two View structs `WebImage` and `AnimatedImage` for SwiftUI world, `ImageIndicator` modifier for any View, `ImageManager` observable object for data source. Supports iOS 13+/macOS 10.15+/tvOS 13+/watchOS 6+ and Swift 5.1. Have a nice try and provide feedback!
#### Coders for additional image formats
- [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - coder for WebP format. Based on [libwebp](https://chromium.googlesource.com/webm/libwebp)
- [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - coder for WebP format. iOS 8+/macOS 10.10+. Based on [libwebp](https://chromium.googlesource.com/webm/libwebp)
- [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) - coder for HEIF format, iOS 8+/macOS 10.10+ support. Based on [libheif](https://github.com/strukturag/libheif)
- [SDWebImageBPGCoder](https://github.com/SDWebImage/SDWebImageBPGCoder) - coder for BPG format. Based on [libbpg](https://github.com/mirrorer/libbpg)
- [SDWebImageFLIFCoder](https://github.com/SDWebImage/SDWebImageFLIFCoder) - coder for FLIF format. Based on [libflif](https://github.com/FLIF-hub/FLIF)

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '5.8.4'
s.version = '5.9.2'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'

View File

@ -74,12 +74,9 @@
}
}
case 0x3C: {
if (data.length > 100) {
// Check end with SVG tag
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
if ([testString containsString:kSVGTagEnd]) {
return SDImageFormatSVG;
}
// Check end with SVG tag
if ([data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound) {
return SDImageFormatSVG;
}
}
}

View File

@ -25,9 +25,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.range.location >= 3) {
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}];
return scale;
@ -128,7 +126,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
data = [data copy]; // avoid mutable data
id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:data]) {
if (!options) {
@ -209,7 +207,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
CGFloat scale = self.scale;
id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:animatedImageData]) {
animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];

View File

@ -160,14 +160,14 @@
}
}
- (void)resetCurrentFrameIndex {
self.currentFrame = nil;
self.currentFrameIndex = 0;
self.currentLoopCount = 0;
self.currentTime = 0;
self.bufferMiss = NO;
self.needsDisplayWhenImageBecomesAvailable = NO;
[self handleFrameChange];
- (void)resetCurrentFrameStatus {
// These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback.
_currentFrame = nil;
_currentFrameIndex = 0;
_currentLoopCount = 0;
_currentTime = 0;
_bufferMiss = NO;
_needsDisplayWhenImageBecomesAvailable = NO;
}
- (void)clearFrameBuffer {
@ -191,7 +191,8 @@
[_fetchQueue cancelAllOperations];
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
[_displayLink stop];
[self resetCurrentFrameIndex];
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
[self resetCurrentFrameStatus];
}
- (void)pausePlaying {

View File

@ -103,8 +103,18 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
@property (nonatomic, class, readonly, nonnull) SDImageCache *sharedImageCache;
/**
* Control the default disk cache directory. This will effect all the SDImageCache instance created after modification, even for shared image cache.
* This can be used to share the same disk cache with the App and App Extension (Today/Notification Widget) using `- [NSFileManager.containerURLForSecurityApplicationGroupIdentifier:]`.
* @note If you pass nil, the value will be reset to `~/Library/Caches/com.hackemist.SDImageCache`.
* @note We still preserve the `namespace` arg, which means, if you change this property into `/path/to/use`, the `SDImageCache.sharedImageCache.diskCachePath` should be `/path/to/use/default` because shared image cache use `default` as namespace.
* Defaults to nil.
*/
@property (nonatomic, class, readwrite, null_resettable) NSString *defaultDiskCacheDirectory;
/**
* Init a new cache store with a specific namespace
* The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/)
*
* @param ns The namespace to use for this cache store
*/
@ -112,7 +122,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* Init a new cache store with a specific namespace and directory.
* If you don't provide the disk cache directory, we will use the User Cache directory with prefix (~/Library/Caches/com.hackemist.SDImageCache/).
* The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/)
*
* @param ns The namespace to use for this cache store
* @param directory Directory to cache disk images in
@ -121,7 +131,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
diskCacheDirectory:(nullable NSString *)directory;
/**
* Init a new cache store with a specific namespace, directory and file manager
* Init a new cache store with a specific namespace, directory and config.
* The final disk cache directory should looks like ($directory/$namespace). And the default config of shared cache, should result in (~/Library/Caches/com.hackemist.SDImageCache/default/)
*
* @param ns The namespace to use for this cache store

View File

@ -15,6 +15,8 @@
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
static NSString * _defaultDiskCacheDirectory;
@interface SDImageCache ()
#pragma mark - Properties
@ -40,6 +42,17 @@
return instance;
}
+ (NSString *)defaultDiskCacheDirectory {
if (!_defaultDiskCacheDirectory) {
_defaultDiskCacheDirectory = [[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"];
}
return _defaultDiskCacheDirectory;
}
+ (void)setDefaultDiskCacheDirectory:(NSString *)defaultDiskCacheDirectory {
_defaultDiskCacheDirectory = [defaultDiskCacheDirectory copy];
}
- (instancetype)init {
return [self initWithNamespace:@"default"];
}
@ -72,12 +85,11 @@
_memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:ns];
} else {
NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
_diskCachePath = path;
if (!directory) {
// Use default disk cache directory
directory = [self.class defaultDiskCacheDirectory];
}
_diskCachePath = [directory stringByAppendingPathComponent:ns];
NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
_diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
@ -121,7 +133,7 @@
return [self.diskCache cachePathForKey:key];
}
- (nullable NSString *)userCacheDirectory {
+ (nullable NSString *)userCacheDirectory {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return paths.firstObject;
}
@ -131,9 +143,9 @@
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// ~/Library/Caches/com.hackemist.SDImageCache/default/
NSString *newDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
NSString *newDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
// ~/Library/Caches/default/com.hackemist.SDWebImageCache.default/
NSString *oldDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
NSString *oldDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
dispatch_async(self.ioQueue, ^{
[((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
});
@ -269,7 +281,7 @@
});
}
// Make sure to call form io queue by caller
// Make sure to call from io queue by caller
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
@ -304,7 +316,7 @@
return exists;
}
// Make sure to call form io queue by caller
// Make sure to call from io queue by caller
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
if (!key) {
return NO;
@ -516,14 +528,10 @@
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// the image is from in-memory cache, but need image data
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
@ -541,10 +549,10 @@
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}

View File

@ -91,7 +91,8 @@
+ (UIImage * _Nullable)decodedImageWithImage:(UIImage * _Nullable)image;
/**
Return the decoded and probably scaled down image by the provided image. If the image is large than the limit size, will try to scale down. Or just works as `decodedImageWithImage:`
Return the decoded and probably scaled down image by the provided image. If the image pixels bytes size large than the limit bytes, will try to scale down. Or just works as `decodedImageWithImage:`, never scale up.
@warning You should not pass too small bytes, the suggestion value should be larger than 1MB. Even we use Tile Decoding to avoid OOM, however, small bytes will consume much more CPU time because we need to iterate more times to draw each tile.
@param image The image to be decoded and scaled down
@param bytes The limit bytes size. Provide 0 to use the build-in limit.
@ -101,7 +102,7 @@
/**
Control the default limit bytes to scale down largest images.
This value must be larger than or equal to 1MB. Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
This value must be larger than 4 Bytes (at least 1x1 pixel). Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
*/
@property (class, readwrite) NSUInteger defaultScaleDownLimitBytes;

View File

@ -25,7 +25,6 @@ static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
@ -379,8 +378,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// see kDestImageSizeMB, and how it relates to destTotalPixels.
CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width * imageScale);
destResolution.height = (int)(sourceResolution.height * imageScale);
destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale));
destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale));
// device color space
CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
@ -419,7 +418,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(tileTotalPixels / sourceTile.size.width );
sourceTile.size.height = MAX(1, (int)(tileTotalPixels / sourceTile.size.width));
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
@ -485,7 +484,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
+ (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
if (defaultScaleDownLimitBytes < kBytesPerMB) {
if (defaultScaleDownLimitBytes < kBytesPerPixel) {
return;
}
kDestImageLimitBytes = defaultScaleDownLimitBytes;
@ -596,13 +595,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
CGFloat destTotalPixels;
if (bytes == 0) {
bytes = kDestImageLimitBytes;
bytes = [self defaultScaleDownLimitBytes];
}
bytes = MAX(bytes, kBytesPerPixel);
destTotalPixels = bytes / kBytesPerPixel;
if (destTotalPixels <= kPixelsPerMB) {
// Too small to scale down
return NO;
}
float imageScale = destTotalPixels / sourceTotalPixels;
if (imageScale < 1) {
shouldScaleDown = YES;

View File

@ -212,7 +212,8 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
decodingOptions = [NSMutableDictionary dictionary];
}
CGImageRef imageRef;
if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
if (createFullImage) {
if (isVector) {
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
// Provide the default pixel count for vector images, simply just use the screen size
@ -251,8 +252,8 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
if (!imageRef) {
return nil;
}
if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
// Thumbnail image post-process
if (!createFullImage) {
if (preserveAspectRatio) {
// kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
exifOrientation = kCGImagePropertyOrientationUp;

View File

@ -160,7 +160,8 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageFromLoaderOnly = 1 << 16,
/**
* By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image download from the network. This mask can force to apply view transition for memory and disk cache as well.
* By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image when the callback from manager is asynchronous (from network, or disk cache query)
* This mask can force to apply view transition for any cases, like memory cache query, or sync disk cache query.
*/
SDWebImageForceTransition = 1 << 17,
@ -200,7 +201,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
* We usually don't apply transform on vector images, because vector images supports dynamically changing to any size, rasterize to a fixed size will loss details. To modify vector images, you can process the vector data at runtime (such as modifying PDF tag / SVG element).
* Use this flag to transform them anyway.
*/
SDWebImageTransformVectorImage = 1 << 23,
SDWebImageTransformVectorImage = 1 << 23
};

View File

@ -89,6 +89,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction;
#else
@ -103,6 +104,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction;
#else
@ -117,6 +119,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction;
#else
@ -131,6 +134,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction;
#else
@ -145,6 +149,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction;
#else
@ -159,6 +164,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction;
#else
@ -173,6 +179,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction;
#else

View File

@ -174,7 +174,29 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
BOOL shouldUseTransition = NO;
if (options & SDWebImageForceTransition) {
// Always
shouldUseTransition = YES;
} else if (cacheType == SDImageCacheTypeNone) {
// From network
shouldUseTransition = YES;
} else {
// From disk (and, user don't use sync query)
if (cacheType == SDImageCacheTypeMemory) {
shouldUseTransition = NO;
} else if (cacheType == SDImageCacheTypeDisk) {
if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
shouldUseTransition = NO;
} else {
shouldUseTransition = YES;
}
} else {
// Not valid cache type, fallback
shouldUseTransition = NO;
}
}
if (finished && shouldUseTransition) {
transition = self.sd_imageTransition;
}
#endif

View File

@ -32,6 +32,9 @@
32515F9E24AF1919005E8F79 /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 32515F9824AF1919005E8F79 /* TestImageAnimated.webp */; };
3254C32020641077008D1022 /* SDImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDImageTransformerTests.m */; };
3254C32120641077008D1022 /* SDImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDImageTransformerTests.m */; };
32648067250232F7004FA0FC /* 1@2x.gif in Resources */ = {isa = PBXBuildFile; fileRef = 32648066250232F7004FA0FC /* 1@2x.gif */; };
32648068250232F7004FA0FC /* 1@2x.gif in Resources */ = {isa = PBXBuildFile; fileRef = 32648066250232F7004FA0FC /* 1@2x.gif */; };
32648069250232F7004FA0FC /* 1@2x.gif in Resources */ = {isa = PBXBuildFile; fileRef = 32648066250232F7004FA0FC /* 1@2x.gif */; };
3264FF2F205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; };
3264FF30205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; };
326E69472334C0C300B7252C /* TestLoopCount.gif in Resources */ = {isa = PBXBuildFile; fileRef = 326E69462334C0C200B7252C /* TestLoopCount.gif */; };
@ -127,6 +130,7 @@
32515F9724AF1919005E8F79 /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = "<group>"; };
32515F9824AF1919005E8F79 /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = "<group>"; };
3254C31F20641077008D1022 /* SDImageTransformerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDImageTransformerTests.m; sourceTree = "<group>"; };
32648066250232F7004FA0FC /* 1@2x.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "1@2x.gif"; sourceTree = "<group>"; };
3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestTransformer.h; sourceTree = "<group>"; };
3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestTransformer.m; sourceTree = "<group>"; };
326E69462334C0C200B7252C /* TestLoopCount.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = TestLoopCount.gif; sourceTree = "<group>"; };
@ -245,6 +249,7 @@
32B99EA1203B30DF0017FD66 /* Images */ = {
isa = PBXGroup;
children = (
32648066250232F7004FA0FC /* 1@2x.gif */,
433BBBBA1D7EFA8B0086B6E9 /* MonochromeTestImage.jpg */,
324047432271956F007C53E1 /* TestEXIF.png */,
433BBBB61D7EF8200086B6E9 /* TestImage.gif */,
@ -466,6 +471,7 @@
3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F92440C39B00E5B29D /* TestImageLarge.png in Resources */,
329922892365DC6C00EAFD97 /* TestImageLarge.jpg in Resources */,
32648069250232F7004FA0FC /* 1@2x.gif in Resources */,
3299228A2365DC6C00EAFD97 /* TestImage.png in Resources */,
329922842365DC6C00EAFD97 /* MonochromeTestImage.jpg in Resources */,
329922882365DC6C00EAFD97 /* TestImage.jpg in Resources */,
@ -488,6 +494,7 @@
3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F82440C39B00E5B29D /* TestImageLarge.png in Resources */,
32B99EA6203B31360017FD66 /* TestImage.png in Resources */,
32648068250232F7004FA0FC /* 1@2x.gif in Resources */,
3297A0A023374D1700814590 /* TestImageAnimated.heic in Resources */,
32B99EA2203B31360017FD66 /* MonochromeTestImage.jpg in Resources */,
32905E65211D786E00460FCF /* TestImage.heif in Resources */,
@ -510,6 +517,7 @@
3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F72440C39B00E5B29D /* TestImageLarge.png in Resources */,
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */,
32648067250232F7004FA0FC /* 1@2x.gif in Resources */,
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */,
3297A09F23374D1700814590 /* TestImageAnimated.heic in Resources */,
327054E2206CEFF3006EA328 /* TestImageAnimated.apng in Resources */,

BIN
Tests/Tests/Images/1@2x.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 KiB

View File

@ -13,6 +13,41 @@
static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop count
// Check whether the coder is called
@interface SDImageAPNGTestCoder : SDImageAPNGCoder
@property (nonatomic, class, assign) BOOL isCalled;
@end
@implementation SDImageAPNGTestCoder
static BOOL _isCalled;
+ (BOOL)isCalled {
return _isCalled;
}
+ (void)setIsCalled:(BOOL)isCalled {
_isCalled = isCalled;
}
+ (instancetype)sharedCoder {
static SDImageAPNGTestCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDImageAPNGTestCoder alloc] init];
});
return coder;
}
- (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOptions *)options {
SDImageAPNGTestCoder.isCalled = YES;
return [super initWithAnimatedImageData:data options:options];
}
@end
// Internal header
@interface SDAnimatedImageView ()
@ -55,7 +90,13 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun
SDAnimatedImage *image = [[SDAnimatedImage alloc] initWithContentsOfFile:[self testGIFPath]];
expect(image).notTo.beNil();
expect(image.scale).equal(1); // scale
// enough, other can be test with InitWithData
// Test Retina File Path should result @2x scale
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
NSString *testPath = [testBundle pathForResource:@"1@2x" ofType:@"gif"];
image = [[SDAnimatedImage alloc] initWithContentsOfFile:testPath];
expect(image).notTo.beNil();
expect(image.scale).equal(2); // scale
}
- (void)test03AnimatedImageInitWithAnimatedCoder {
@ -389,7 +430,6 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun
imageView.animates = NO;
#endif
expect(imageView.player.frameBuffer.count).equal(0);
expect(imageView.currentFrameIndex).equal(0);
[imageView removeFromSuperview];
[expectation fulfill];
@ -556,6 +596,12 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun
[self waitForExpectationsWithCommonTimeout];
}
- (void)test30AnimatedImageCoderPriority {
[SDImageCodersManager.sharedManager addCoder:SDImageAPNGTestCoder.sharedCoder];
[SDAnimatedImage imageWithData:[self testAPNGPData]];
expect(SDImageAPNGTestCoder.isCalled).equal(YES);
}
#pragma mark - Helper
- (UIWindow *)window {
if (!_window) {

View File

@ -484,6 +484,25 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
[self waitForExpectationsWithCommonTimeout];
}
- (void)test43CustomDefaultCacheDirectory {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *testDirectory = [paths.firstObject stringByAppendingPathComponent:@"CustomDefaultCacheDirectory"];
NSString *defaultDirectory = [paths.firstObject stringByAppendingPathComponent:@"com.hackemist.SDImageCache"];
NSString *namespace = @"Test";
// Default cache path
expect(SDImageCache.defaultDiskCacheDirectory).equal(defaultDirectory);
SDImageCache *cache1 = [[SDImageCache alloc] initWithNamespace:namespace];
expect(cache1.diskCachePath).equal([defaultDirectory stringByAppendingPathComponent:namespace]);
// Custom cache path
SDImageCache.defaultDiskCacheDirectory = testDirectory;
SDImageCache *cache2 = [[SDImageCache alloc] initWithNamespace:namespace];
expect(cache2.diskCachePath).equal([testDirectory stringByAppendingPathComponent:namespace]);
// Check reset
SDImageCache.defaultDiskCacheDirectory = nil;
expect(SDImageCache.defaultDiskCacheDirectory).equal(defaultDirectory);
}
#pragma mark - SDMemoryCache & SDDiskCache
- (void)test42CustomMemoryCache {
SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];

View File

@ -74,6 +74,7 @@
}
- (void)test07ThatDecodeAndScaleDownImageDoesNotScaleSmallerImage {
// check when user use the larget bytes than image pixels byets, we do not scale up the image (defaults 60MB means 3965x3965 pixels)
NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image];
@ -83,6 +84,17 @@
expect(decodedImage.size.height).to.equal(image.size.height);
}
- (void)test07ThatDecodeAndScaleDownImageScaleSmallerBytes {
// Check when user provide too small bytes, we scale it down to 1x1, but not return the force decoded original size image
NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:1];
expect(decodedImage).toNot.beNil();
expect(decodedImage).toNot.equal(image);
expect(decodedImage.size.width).to.equal(1);
expect(decodedImage.size.height).to.equal(1);
}
- (void)test08ThatEncodeAlphaImageToJPGWithBackgroundColor {
NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
@ -203,7 +215,7 @@
}
- (void)test16ThatHEICAnimatedWorks {
if (@available(iOS 11, tvOS 11, macOS 10.13, *)) {
if (@available(iOS 13, tvOS 13, macOS 10.15, *)) {
NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"heic"];
#if SD_UIKIT
BOOL isAnimatedImage = YES;

View File

@ -253,8 +253,8 @@
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIViewTransitionWork {
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView transition does not work"];
- (void)testUIViewTransitionFromNetworkWork {
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView transition from network does not work"];
// Attach a window, or CALayer will not submit drawing
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
@ -296,6 +296,50 @@
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIViewTransitionFromDiskWork {
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView transition from disk does not work"];
// Attach a window, or CALayer will not submit drawing
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
imageView.sd_imageTransition = SDWebImageTransition.fadeTransition;
imageView.sd_imageTransition.duration = 1;
#if SD_UIKIT
[self.window addSubview:imageView];
#else
imageView.wantsLayer = YES;
[self.window.contentView addSubview:imageView];
#endif
NSData *imageData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
UIImage *placeholder = [[UIImage alloc] initWithData:imageData];
// Ensure the image is cached in disk but not memory
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
[SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
[SDImageCache.sharedImageCache storeImageDataToDisk:imageData forKey:kTestJPEGURL];
NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
__weak typeof(imageView) wimageView = imageView;
[imageView sd_setImageWithURL:originalImageURL
placeholderImage:placeholder
options:SDWebImageFromCacheOnly // Ensure we queired from disk cache
completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
[SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
__strong typeof(wimageView) simageView = imageView;
// Delay to let CALayer commit the transition in next runloop
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
// Check current view contains layer animation
NSArray *animationKeys = simageView.layer.animationKeys;
expect(animationKeys.count).beGreaterThan(0);
[expectation fulfill];
});
}];
[self waitForExpectationsWithCommonTimeout];
}
- (void)testUIViewIndicatorWork {
XCTestExpectation *expectation = [self expectationWithDescription:@"UIView indicator does not work"];

View File

@ -280,6 +280,25 @@
}];
}
- (void)test13ThatScaleDownLargeImageEXIFOrientationImage {
XCTestExpectation *expectation = [self expectationWithDescription:@"SDWebImageScaleDownLargeImages works on EXIF orientation image"];
NSURL *originalImageURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg"];
[SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
expect(image).notTo.beNil();
#if SD_UIKIT
UIImageOrientation orientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:kCGImagePropertyOrientationUpMirrored];
expect(image.imageOrientation).equal(orientation);
#endif
if (finished) {
[expectation fulfill];
} else {
expect(image.sd_isIncremental).beTruthy();
}
}];
[self waitForExpectationsWithCommonTimeout];
}
- (void)test14ThatCustomCacheAndLoaderWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom Cache and Loader during manger query"];
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/100x100.png"];

View File

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