New file |
| | |
| | | /* |
| | | * 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_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer 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 |