/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * 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 #import "SDAnimatedImageRep.h" @implementation SDWebImageCoderHelper + (UIImage *)animatedImageWithFrames:(NSArray *)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 *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 *)framesFromAnimatedImage:(UIImage *)animatedImage { if (!animatedImage) { return nil; } NSMutableArray *frames = [NSMutableArray array]; NSUInteger frameCount = 0; #if SD_UIKIT || SD_WATCH NSArray *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