@@ -25,7 +25,6 @@
Loading
25 25
static const size_t kBitsPerComponent = 8;
26 26
27 27
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
28 -
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
29 28
/*
30 29
 * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
31 30
 * Suggested value for iPad1 and iPhone 3GS: 60.
@@ -379,8 +378,8 @@
Loading
379 378
        // see kDestImageSizeMB, and how it relates to destTotalPixels.
380 379
        CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
381 380
        CGSize destResolution = CGSizeZero;
382 -
        destResolution.width = (int)(sourceResolution.width * imageScale);
383 -
        destResolution.height = (int)(sourceResolution.height * imageScale);
381 +
        destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale));
382 +
        destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale));
384 383
        
385 384
        // device color space
386 385
        CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
@@ -419,7 +418,7 @@
Loading
419 418
        // The source tile height is dynamic. Since we specified the size
420 419
        // of the source tile in MB, see how many rows of pixels high it
421 420
        // can be given the input image width.
422 -
        sourceTile.size.height = (int)(tileTotalPixels / sourceTile.size.width );
421 +
        sourceTile.size.height = MAX(1, (int)(tileTotalPixels / sourceTile.size.width));
423 422
        sourceTile.origin.x = 0.0f;
424 423
        // The output tile is the same proportions as the input tile, but
425 424
        // scaled to image scale.
@@ -485,7 +484,7 @@
Loading
485 484
}
486 485
487 486
+ (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
488 -
    if (defaultScaleDownLimitBytes < kBytesPerMB) {
487 +
    if (defaultScaleDownLimitBytes < kBytesPerPixel) {
489 488
        return;
490 489
    }
491 490
    kDestImageLimitBytes = defaultScaleDownLimitBytes;
@@ -596,13 +595,10 @@
Loading
596 595
    }
597 596
    CGFloat destTotalPixels;
598 597
    if (bytes == 0) {
599 -
        bytes = kDestImageLimitBytes;
598 +
        bytes = [self defaultScaleDownLimitBytes];
600 599
    }
600 +
    bytes = MAX(bytes, kBytesPerPixel);
601 601
    destTotalPixels = bytes / kBytesPerPixel;
602 -
    if (destTotalPixels <= kPixelsPerMB) {
603 -
        // Too small to scale down
604 -
        return NO;
605 -
    }
606 602
    float imageScale = destTotalPixels / sourceTotalPixels;
607 603
    if (imageScale < 1) {
608 604
        shouldScaleDown = YES;

@@ -89,6 +89,7 @@
Loading
89 89
90 90
+ (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration {
91 91
    SDWebImageTransition *transition = [SDWebImageTransition new];
92 +
    transition.duration = duration;
92 93
#if SD_UIKIT
93 94
    transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction;
94 95
#else
@@ -103,6 +104,7 @@
Loading
103 104
104 105
+ (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration {
105 106
    SDWebImageTransition *transition = [SDWebImageTransition new];
107 +
    transition.duration = duration;
106 108
#if SD_UIKIT
107 109
    transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction;
108 110
#else
@@ -117,6 +119,7 @@
Loading
117 119
118 120
+ (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration {
119 121
    SDWebImageTransition *transition = [SDWebImageTransition new];
122 +
    transition.duration = duration;
120 123
#if SD_UIKIT
121 124
    transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction;
122 125
#else
@@ -131,6 +134,7 @@
Loading
131 134
132 135
+ (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration {
133 136
    SDWebImageTransition *transition = [SDWebImageTransition new];
137 +
    transition.duration = duration;
134 138
#if SD_UIKIT
135 139
    transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction;
136 140
#else
@@ -145,6 +149,7 @@
Loading
145 149
146 150
+ (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration {
147 151
    SDWebImageTransition *transition = [SDWebImageTransition new];
152 +
    transition.duration = duration;
148 153
#if SD_UIKIT
149 154
    transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction;
150 155
#else
@@ -159,6 +164,7 @@
Loading
159 164
160 165
+ (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration {
161 166
    SDWebImageTransition *transition = [SDWebImageTransition new];
167 +
    transition.duration = duration;
162 168
#if SD_UIKIT
163 169
    transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction;
164 170
#else
@@ -173,6 +179,7 @@
Loading
173 179
174 180
+ (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration {
175 181
    SDWebImageTransition *transition = [SDWebImageTransition new];
182 +
    transition.duration = duration;
176 183
#if SD_UIKIT
177 184
    transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction;
178 185
#else

@@ -212,7 +212,8 @@
Loading
212 212
        decodingOptions = [NSMutableDictionary dictionary];
213 213
    }
214 214
    CGImageRef imageRef;
215 -
    if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
215 +
    BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
216 +
    if (createFullImage) {
216 217
        if (isVector) {
217 218
            if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
218 219
                // Provide the default pixel count for vector images, simply just use the screen size
@@ -251,8 +252,8 @@
Loading
251 252
    if (!imageRef) {
252 253
        return nil;
253 254
    }
254 -
    
255 -
    if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
255 +
    // Thumbnail image post-process
256 +
    if (!createFullImage) {
256 257
        if (preserveAspectRatio) {
257 258
            // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
258 259
            exifOrientation = kCGImagePropertyOrientationUp;

@@ -15,6 +15,8 @@
Loading
15 15
#import "UIImage+Metadata.h"
16 16
#import "UIImage+ExtendedCacheData.h"
17 17
18 +
static NSString * _defaultDiskCacheDirectory;
19 +
18 20
@interface SDImageCache ()
19 21
20 22
#pragma mark - Properties
@@ -40,6 +42,17 @@
Loading
40 42
    return instance;
41 43
}
42 44
45 +
+ (NSString *)defaultDiskCacheDirectory {
46 +
    if (!_defaultDiskCacheDirectory) {
47 +
        _defaultDiskCacheDirectory = [[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"];
48 +
    }
49 +
    return _defaultDiskCacheDirectory;
50 +
}
51 +
52 +
+ (void)setDefaultDiskCacheDirectory:(NSString *)defaultDiskCacheDirectory {
53 +
    _defaultDiskCacheDirectory = [defaultDiskCacheDirectory copy];
54 +
}
55 +
43 56
- (instancetype)init {
44 57
    return [self initWithNamespace:@"default"];
45 58
}
@@ -72,12 +85,11 @@
Loading
72 85
        _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
73 86
        
74 87
        // Init the disk cache
75 -
        if (directory != nil) {
76 -
            _diskCachePath = [directory stringByAppendingPathComponent:ns];
77 -
        } else {
78 -
            NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
79 -
            _diskCachePath = path;
88 +
        if (!directory) {
89 +
            // Use default disk cache directory
90 +
            directory = [self.class defaultDiskCacheDirectory];
80 91
        }
92 +
        _diskCachePath = [directory stringByAppendingPathComponent:ns];
81 93
        
82 94
        NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
83 95
        _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
@@ -121,7 +133,7 @@
Loading
121 133
    return [self.diskCache cachePathForKey:key];
122 134
}
123 135
124 -
- (nullable NSString *)userCacheDirectory {
136 +
+ (nullable NSString *)userCacheDirectory {
125 137
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
126 138
    return paths.firstObject;
127 139
}
@@ -131,9 +143,9 @@
Loading
131 143
        static dispatch_once_t onceToken;
132 144
        dispatch_once(&onceToken, ^{
133 145
            // ~/Library/Caches/com.hackemist.SDImageCache/default/
134 -
            NSString *newDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
146 +
            NSString *newDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
135 147
            // ~/Library/Caches/default/com.hackemist.SDWebImageCache.default/
136 -
            NSString *oldDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
148 +
            NSString *oldDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
137 149
            dispatch_async(self.ioQueue, ^{
138 150
                [((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
139 151
            });
@@ -269,7 +281,7 @@
Loading
269 281
    });
270 282
}
271 283
272 -
// Make sure to call form io queue by caller
284 +
// Make sure to call from io queue by caller
273 285
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
274 286
    if (!imageData || !key) {
275 287
        return;
@@ -304,7 +316,7 @@
Loading
304 316
    return exists;
305 317
}
306 318
307 -
// Make sure to call form io queue by caller
319 +
// Make sure to call from io queue by caller
308 320
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
309 321
    if (!key) {
310 322
        return NO;
@@ -516,14 +528,10 @@
Loading
516 528
        @autoreleasepool {
517 529
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
518 530
            UIImage *diskImage;
519 -
            SDImageCacheType cacheType = SDImageCacheTypeNone;
520 531
            if (image) {
521 532
                // the image is from in-memory cache, but need image data
522 533
                diskImage = image;
523 -
                cacheType = SDImageCacheTypeMemory;
524 534
            } else if (diskData) {
525 -
                cacheType = SDImageCacheTypeDisk;
526 -
                
527 535
                BOOL shouldCacheToMomery = YES;
528 536
                if (context[SDWebImageContextStoreCacheType]) {
529 537
                    SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
@@ -541,10 +549,10 @@
Loading
541 549
            
542 550
            if (doneBlock) {
543 551
                if (shouldQueryDiskSync) {
544 -
                    doneBlock(diskImage, diskData, cacheType);
552 +
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
545 553
                } else {
546 554
                    dispatch_async(dispatch_get_main_queue(), ^{
547 -
                        doneBlock(diskImage, diskData, cacheType);
555 +
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
548 556
                    });
549 557
                }
550 558
            }

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

@@ -174,7 +174,29 @@
Loading
174 174
#if SD_UIKIT || SD_MAC
175 175
            // check whether we should use the image transition
176 176
            SDWebImageTransition *transition = nil;
177 -
            if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
177 +
            BOOL shouldUseTransition = NO;
178 +
            if (options & SDWebImageForceTransition) {
179 +
                // Always
180 +
                shouldUseTransition = YES;
181 +
            } else if (cacheType == SDImageCacheTypeNone) {
182 +
                // From network
183 +
                shouldUseTransition = YES;
184 +
            } else {
185 +
                // From disk (and, user don't use sync query)
186 +
                if (cacheType == SDImageCacheTypeMemory) {
187 +
                    shouldUseTransition = NO;
188 +
                } else if (cacheType == SDImageCacheTypeDisk) {
189 +
                    if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
190 +
                        shouldUseTransition = NO;
191 +
                    } else {
192 +
                        shouldUseTransition = YES;
193 +
                    }
194 +
                } else {
195 +
                    // Not valid cache type, fallback
196 +
                    shouldUseTransition = NO;
197 +
                }
198 +
            }
199 +
            if (finished && shouldUseTransition) {
178 200
                transition = self.sd_imageTransition;
179 201
            }
180 202
#endif

@@ -25,9 +25,7 @@
Loading
25 25
    
26 26
    NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
27 27
    [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
28 -
        if (result.range.location >= 3) {
29 -
            scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
30 -
        }
28 +
        scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
31 29
    }];
32 30
    
33 31
    return scale;
@@ -128,7 +126,7 @@
Loading
128 126
    }
129 127
    data = [data copy]; // avoid mutable data
130 128
    id<SDAnimatedImageCoder> animatedCoder = nil;
131 -
    for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
129 +
    for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
132 130
        if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
133 131
            if ([coder canDecodeFromData:data]) {
134 132
                if (!options) {
@@ -209,7 +207,7 @@
Loading
209 207
        }
210 208
        CGFloat scale = self.scale;
211 209
        id<SDAnimatedImageCoder> animatedCoder = nil;
212 -
        for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
210 +
        for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
213 211
            if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
214 212
                if ([coder canDecodeFromData:animatedImageData]) {
215 213
                    animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];

@@ -160,14 +160,14 @@
Loading
160 160
    }
161 161
}
162 162
163 -
- (void)resetCurrentFrameIndex {
164 -
    self.currentFrame = nil;
165 -
    self.currentFrameIndex = 0;
166 -
    self.currentLoopCount = 0;
167 -
    self.currentTime = 0;
168 -
    self.bufferMiss = NO;
169 -
    self.needsDisplayWhenImageBecomesAvailable = NO;
170 -
    [self handleFrameChange];
163 +
- (void)resetCurrentFrameStatus {
164 +
    // These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback.
165 +
    _currentFrame = nil;
166 +
    _currentFrameIndex = 0;
167 +
    _currentLoopCount = 0;
168 +
    _currentTime = 0;
169 +
    _bufferMiss = NO;
170 +
    _needsDisplayWhenImageBecomesAvailable = NO;
171 171
}
172 172
173 173
- (void)clearFrameBuffer {
@@ -191,7 +191,8 @@
Loading
191 191
    [_fetchQueue cancelAllOperations];
192 192
    // Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
193 193
    [_displayLink stop];
194 -
    [self resetCurrentFrameIndex];
194 +
    // We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
195 +
    [self resetCurrentFrameStatus];
195 196
}
196 197
197 198
- (void)pausePlaying {
Files Coverage
SDWebImage 83.52%
SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.m 65.51%
Project Totals (70 files) 83.45%
3294.1
default=
TRAVIS_OS_NAME=osx
ios
3294.1
default=
TRAVIS_OS_NAME=osx
macos
1
coverage:
2
  ignore:
3
    - "Examples"
4
    - "Vendors"
5
    - "Tests"
6
  status:
7
    project:
8
      default: off
9
      ios:
10
        flags: ios
11
      macos:
12
        flags: macos
13
      tvos:
14
        flags: tvos
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading