Merge pull request #3243 from dreampiggy/convenience_api_frame_count
Added `sd_imageFrameCount` convenient API for UIAinmatedImage/NSBitmapImageRep
This commit is contained in:
commit
1e7f7174b6
|
@ -314,6 +314,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)sd_imageFrameCount {
|
||||||
|
return self.animatedImageFrameCount;
|
||||||
|
}
|
||||||
|
|
||||||
- (SDImageFormat)sd_imageFormat {
|
- (SDImageFormat)sd_imageFormat {
|
||||||
return self.animatedImageFormat;
|
return self.animatedImageFormat;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
|
avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
__block NSUInteger index = 0;
|
|
||||||
__block NSUInteger repeatCount = 1;
|
__block NSUInteger repeatCount = 1;
|
||||||
__block UIImage *previousImage = animatedImages.firstObject;
|
__block UIImage *previousImage = animatedImages.firstObject;
|
||||||
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||||
|
@ -149,15 +148,12 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
|
SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
|
||||||
[frames addObject:frame];
|
[frames addObject:frame];
|
||||||
repeatCount = 1;
|
repeatCount = 1;
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
previousImage = image;
|
previousImage = image;
|
||||||
// last one
|
|
||||||
if (idx == frameCount - 1) {
|
|
||||||
SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
|
|
||||||
[frames addObject:frame];
|
|
||||||
}
|
|
||||||
}];
|
}];
|
||||||
|
// last one
|
||||||
|
SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
|
||||||
|
[frames addObject:frame];
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) {
|
||||||
frameCount = 1;
|
frameCount = 1;
|
||||||
#elif SD_UIKIT || SD_WATCH
|
#elif SD_UIKIT || SD_WATCH
|
||||||
// Filter the same frame in `_UIAnimatedImage`.
|
// Filter the same frame in `_UIAnimatedImage`.
|
||||||
frameCount = image.images.count > 0 ? [NSSet setWithArray:image.images].count : 1;
|
frameCount = image.images.count > 1 ? [NSSet setWithArray:image.images].count : 1;
|
||||||
#endif
|
#endif
|
||||||
NSUInteger cost = bytesPerFrame * frameCount;
|
NSUInteger cost = bytesPerFrame * frameCount;
|
||||||
return cost;
|
return cost;
|
||||||
|
|
|
@ -20,12 +20,23 @@
|
||||||
* For animated image format, 0 means infinite looping.
|
* For animated image format, 0 means infinite looping.
|
||||||
* Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
|
* Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
|
||||||
* AppKit:
|
* AppKit:
|
||||||
* NSImage currently only support animated via GIF imageRep unlike UIImage.
|
* NSImage currently only support animated via `NSBitmapImageRep`(GIF) or `SDAnimatedImageRep`(APNG/GIF/WebP) unlike UIImage.
|
||||||
* The getter of this property will get the loop count from GIF imageRep
|
* The getter of this property will get the loop count from animated imageRep
|
||||||
* The setter of this property will set the loop count from GIF imageRep
|
* The setter of this property will set the loop count from animated imageRep
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;
|
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UIKit:
|
||||||
|
* Returns the `images`'s count by unapply the patch for the different frame durations. Which matches the real visible frame count when displaying on UIImageView.
|
||||||
|
* See more in `SDImageCoderHelper.animatedImageWithFrames`.
|
||||||
|
* Returns 1 for static image.
|
||||||
|
* AppKit:
|
||||||
|
* Returns the underlaying `NSBitmapImageRep` or `SDAnimatedImageRep` frame count.
|
||||||
|
* Returns 1 for static image.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign, readonly) NSUInteger sd_imageFrameCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UIKit:
|
* UIKit:
|
||||||
* Check the `images` array property.
|
* Check the `images` array property.
|
||||||
|
|
|
@ -29,6 +29,32 @@
|
||||||
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)sd_imageFrameCount {
|
||||||
|
NSArray<UIImage *> *animatedImages = self.images;
|
||||||
|
if (!animatedImages || animatedImages.count <= 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageFrameCount));
|
||||||
|
if ([value isKindOfClass:[NSNumber class]]) {
|
||||||
|
return [value unsignedIntegerValue];
|
||||||
|
}
|
||||||
|
__block NSUInteger frameCount = 1;
|
||||||
|
__block UIImage *previousImage = animatedImages.firstObject;
|
||||||
|
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||||
|
// ignore first
|
||||||
|
if (idx == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (![image isEqual:previousImage]) {
|
||||||
|
frameCount++;
|
||||||
|
}
|
||||||
|
previousImage = image;
|
||||||
|
}];
|
||||||
|
objc_setAssociatedObject(self, @selector(sd_imageFrameCount), @(frameCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||||
|
|
||||||
|
return frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)sd_isAnimated {
|
- (BOOL)sd_isAnimated {
|
||||||
return (self.images != nil);
|
return (self.images != nil);
|
||||||
}
|
}
|
||||||
|
@ -87,6 +113,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)sd_imageFrameCount {
|
||||||
|
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
|
||||||
|
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
|
||||||
|
NSBitmapImageRep *bitmapImageRep;
|
||||||
|
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
|
||||||
|
bitmapImageRep = (NSBitmapImageRep *)imageRep;
|
||||||
|
}
|
||||||
|
if (bitmapImageRep) {
|
||||||
|
return [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)sd_isAnimated {
|
- (BOOL)sd_isAnimated {
|
||||||
BOOL isAnimated = NO;
|
BOOL isAnimated = NO;
|
||||||
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
|
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
|
||||||
|
|
|
@ -770,7 +770,10 @@ static BOOL _isCalled;
|
||||||
[[SDImageCodersManager sharedManager] addCoder:[SDImageAWebPCoder sharedCoder]];
|
[[SDImageCodersManager sharedManager] addCoder:[SDImageAWebPCoder sharedCoder]];
|
||||||
UIImage *image = [UIImage sd_imageWithData:[NSData dataWithContentsOfFile:[self testMemotyCostImagePath]]];
|
UIImage *image = [UIImage sd_imageWithData:[NSData dataWithContentsOfFile:[self testMemotyCostImagePath]]];
|
||||||
NSUInteger cost = [image sd_memoryCost];
|
NSUInteger cost = [image sd_memoryCost];
|
||||||
|
#if SD_UIKIT
|
||||||
expect(image.images.count).equal(5333);
|
expect(image.images.count).equal(5333);
|
||||||
|
#endif
|
||||||
|
expect(image.sd_imageFrameCount).equal(16);
|
||||||
expect(image.scale).equal(1);
|
expect(image.scale).equal(1);
|
||||||
expect(cost).equal(16 * image.size.width * image.size.height * 4);
|
expect(cost).equal(16 * image.size.width * image.size.height * 4);
|
||||||
[[SDImageCodersManager sharedManager] removeCoder:[SDImageAWebPCoder sharedCoder]];
|
[[SDImageCodersManager sharedManager] removeCoder:[SDImageAWebPCoder sharedCoder]];
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
image = [UIImage sd_imageWithGIFData:data];
|
image = [UIImage sd_imageWithGIFData:data];
|
||||||
expect(image).notTo.beNil();
|
expect(image).notTo.beNil();
|
||||||
expect(image.sd_isAnimated).beTruthy();
|
expect(image.sd_isAnimated).beTruthy();
|
||||||
|
expect(image.sd_imageFrameCount).equal(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Helper
|
#pragma mark - Helper
|
||||||
|
|
|
@ -410,9 +410,7 @@ withLocalImageURL:(NSURL *)imageUrl
|
||||||
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
|
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
|
||||||
expect(outputImage.size).to.equal(inputImage.size);
|
expect(outputImage.size).to.equal(inputImage.size);
|
||||||
expect(outputImage.scale).to.equal(inputImage.scale);
|
expect(outputImage.scale).to.equal(inputImage.scale);
|
||||||
#if SD_UIKIT
|
expect(outputImage.sd_imageLoopCount).to.equal(inputImage.sd_imageLoopCount);
|
||||||
expect(outputImage.images.count).to.equal(inputImage.images.count);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// check max pixel size encoding with scratch
|
// check max pixel size encoding with scratch
|
||||||
CGFloat maxWidth = 50;
|
CGFloat maxWidth = 50;
|
||||||
|
@ -429,13 +427,11 @@ withLocalImageURL:(NSURL *)imageUrl
|
||||||
// Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
|
// Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
|
||||||
expect(ABS(outputMaxImage.size.width - maxPixelSize.width)).beLessThanOrEqualTo(1);
|
expect(ABS(outputMaxImage.size.width - maxPixelSize.width)).beLessThanOrEqualTo(1);
|
||||||
expect(ABS(outputMaxImage.size.height - maxPixelSize.height)).beLessThanOrEqualTo(1);
|
expect(ABS(outputMaxImage.size.height - maxPixelSize.height)).beLessThanOrEqualTo(1);
|
||||||
#if SD_UIKIT
|
expect(outputMaxImage.sd_imageLoopCount).to.equal(inputImage.sd_imageLoopCount);
|
||||||
expect(outputMaxImage.images.count).to.equal(inputImage.images.count);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)thumbnailImagesFromImageSource:(CGImageSourceRef)source API_AVAILABLE(ios(11.0), tvos(11.0), macos(13.0)) {
|
- (NSArray *)thumbnailImagesFromImageSource:(CGImageSourceRef)source API_AVAILABLE(ios(11.0), tvos(11.0), macos(10.13)) {
|
||||||
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
|
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
|
||||||
NSDictionary *fileProperties = properties[(__bridge NSString *)kCGImagePropertyFileContentsDictionary];
|
NSDictionary *fileProperties = properties[(__bridge NSString *)kCGImagePropertyFileContentsDictionary];
|
||||||
NSArray *imagesProperties = fileProperties[(__bridge NSString *)kCGImagePropertyImages];
|
NSArray *imagesProperties = fileProperties[(__bridge NSString *)kCGImagePropertyImages];
|
||||||
|
|
Loading…
Reference in New Issue