123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDWebImageCoderHelper.h"
- #import "SDWebImageFrame.h"
- #import "UIImage+MultiFormat.h"
- #import "NSImage+WebCache.h"
- #import <ImageIO/ImageIO.h>
- #import "SDAnimatedImageRep.h"
- @implementation SDWebImageCoderHelper
- + (UIImage *)animatedImageWithFrames:(NSArray<SDWebImageFrame *> *)frames {
- NSUInteger frameCount = frames.count;
- if (frameCount == 0) {
- return nil;
- }
-
- UIImage *animatedImage;
-
- #if SD_UIKIT || SD_WATCH
- NSUInteger durations[frameCount];
- for (size_t i = 0; i < frameCount; i++) {
- durations[i] = frames[i].duration * 1000;
- }
- NSUInteger const gcd = gcdArray(frameCount, durations);
- __block NSUInteger totalDuration = 0;
- NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
- [frames enumerateObjectsUsingBlock:^(SDWebImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
- UIImage *image = frame.image;
- NSUInteger duration = frame.duration * 1000;
- totalDuration += duration;
- NSUInteger repeatCount;
- if (gcd) {
- repeatCount = duration / gcd;
- } else {
- repeatCount = 1;
- }
- for (size_t i = 0; i < repeatCount; ++i) {
- [animatedImages addObject:image];
- }
- }];
-
- animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f];
-
- #else
-
- NSMutableData *imageData = [NSMutableData data];
- CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
- // Create an image destination. GIF does not support EXIF image orientation
- CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
- if (!imageDestination) {
- // Handle failure.
- return nil;
- }
-
- for (size_t i = 0; i < frameCount; i++) {
- @autoreleasepool {
- SDWebImageFrame *frame = frames[i];
- float frameDuration = frame.duration;
- CGImageRef frameImageRef = frame.image.CGImage;
- NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
- CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
- }
- }
- // Finalize the destination.
- if (CGImageDestinationFinalize(imageDestination) == NO) {
- // Handle failure.
- CFRelease(imageDestination);
- return nil;
- }
- CFRelease(imageDestination);
- SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
- animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
- [animatedImage addRepresentation:imageRep];
- #endif
-
- return animatedImage;
- }
- + (NSArray<SDWebImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
- if (!animatedImage) {
- return nil;
- }
-
- NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
- NSUInteger frameCount = 0;
-
- #if SD_UIKIT || SD_WATCH
- NSArray<UIImage *> *animatedImages = animatedImage.images;
- frameCount = animatedImages.count;
- if (frameCount == 0) {
- return nil;
- }
-
- NSTimeInterval avgDuration = animatedImage.duration / frameCount;
- if (avgDuration == 0) {
- 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 UIImage *previousImage = animatedImages.firstObject;
- [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
- // ignore first
- if (idx == 0) {
- return;
- }
- if ([image isEqual:previousImage]) {
- repeatCount++;
- } else {
- SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
- [frames addObject:frame];
- repeatCount = 1;
- index++;
- }
- previousImage = image;
- // last one
- if (idx == frameCount - 1) {
- SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
- [frames addObject:frame];
- }
- }];
-
- #else
-
- NSBitmapImageRep *bitmapRep;
- for (NSImageRep *imageRep in animatedImage.representations) {
- if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
- bitmapRep = (NSBitmapImageRep *)imageRep;
- break;
- }
- }
- if (bitmapRep) {
- frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
- }
-
- if (frameCount == 0) {
- return nil;
- }
-
- for (size_t i = 0; i < frameCount; i++) {
- @autoreleasepool {
- // NSBitmapImageRep need to manually change frame. "Good taste" API
- [bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
- float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
- NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapRep.CGImage size:CGSizeZero];
- SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:frameImage duration:frameDuration];
- [frames addObject:frame];
- }
- }
- #endif
-
- return frames;
- }
- #if SD_UIKIT || SD_WATCH
- // Convert an EXIF image orientation to an iOS one.
- + (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
- // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
- UIImageOrientation imageOrientation = UIImageOrientationUp;
- switch (exifOrientation) {
- case 1:
- imageOrientation = UIImageOrientationUp;
- break;
- case 3:
- imageOrientation = UIImageOrientationDown;
- break;
- case 8:
- imageOrientation = UIImageOrientationLeft;
- break;
- case 6:
- imageOrientation = UIImageOrientationRight;
- break;
- case 2:
- imageOrientation = UIImageOrientationUpMirrored;
- break;
- case 4:
- imageOrientation = UIImageOrientationDownMirrored;
- break;
- case 5:
- imageOrientation = UIImageOrientationLeftMirrored;
- break;
- case 7:
- imageOrientation = UIImageOrientationRightMirrored;
- break;
- default:
- break;
- }
- return imageOrientation;
- }
- // Convert an iOS orientation to an EXIF image orientation.
- + (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
- // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
- NSInteger exifOrientation = 1;
- switch (imageOrientation) {
- case UIImageOrientationUp:
- exifOrientation = 1;
- break;
- case UIImageOrientationDown:
- exifOrientation = 3;
- break;
- case UIImageOrientationLeft:
- exifOrientation = 8;
- break;
- case UIImageOrientationRight:
- exifOrientation = 6;
- break;
- case UIImageOrientationUpMirrored:
- exifOrientation = 2;
- break;
- case UIImageOrientationDownMirrored:
- exifOrientation = 4;
- break;
- case UIImageOrientationLeftMirrored:
- exifOrientation = 5;
- break;
- case UIImageOrientationRightMirrored:
- exifOrientation = 7;
- break;
- default:
- break;
- }
- return exifOrientation;
- }
- #endif
- #pragma mark - Helper Fuction
- #if SD_UIKIT || SD_WATCH
- static NSUInteger gcd(NSUInteger a, NSUInteger b) {
- NSUInteger c;
- while (a != 0) {
- c = a;
- a = b % a;
- b = c;
- }
- return b;
- }
- static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
- if (count == 0) {
- return 0;
- }
- NSUInteger result = values[0];
- for (size_t i = 1; i < count; ++i) {
- result = gcd(values[i], result);
- }
- return result;
- }
- #endif
- @end
|