原始碼閱讀:SDWebImage(六)——SDWebImageCoderHelper

堯少羽發表於2018-06-01

該文章閱讀的SDWebImage的版本為4.3.3。

這個類提供了四個方法,這四個方法可分為兩類,一類是動圖處理,一類是影像方向處理。

1.私有函式

先來看一下這個類裡的兩個函式

/**
 這個函式是計算兩個整數a和b的最大公約數
 */
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;
}
複製程式碼

2.動圖相關方法

/**
 將元素為SDWebImageFrame物件的陣列轉換為動圖
 */
+ (UIImage * _Nullable)animatedImageWithFrames:(NSArray<SDWebImageFrame *> * _Nullable)frames;
複製程式碼
+ (UIImage *)animatedImageWithFrames:(NSArray<SDWebImageFrame *> *)frames {
    // 如果陣列中沒有元素就不是動圖就直接返回nil
    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++) {
        // 遍歷傳入的SDWebImageFrame物件陣列,獲取每一幀的展示時間
        durations[i] = frames[i].duration * 1000;
    }
    // 計算所有幀展示時長的最大公約數
    NSUInteger const gcd = gcdArray(frameCount, durations);
    // 生成臨時變數儲存總時長
    __block NSUInteger totalDuration = 0;
    // 生成臨時變數儲存動圖陣列
    NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
    // 遍歷傳入的SDWebImageFrame物件陣列
    [frames enumerateObjectsUsingBlock:^(SDWebImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
        // 獲取SDWebImageFrame物件儲存的每一幀的影像
        UIImage *image = frame.image;
        // 獲取SDWebImageFrame物件儲存的每一幀的展示時間
        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;
}
複製程式碼

/**
 將動圖轉換為元素為SDWebImageFrame物件的陣列,是上面那個方法的逆方法
 */
+ (NSArray<SDWebImageFrame *> * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage;
複製程式碼
+ (NSArray<SDWebImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
    // 如果沒傳參就不繼續執行了,直接返回空
    if (!animatedImage) {
        return nil;
    }
    
    // 生成臨時變數儲存SDWebImageFrame物件和數量
    NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
    NSUInteger frameCount = 0;
    
#if SD_UIKIT || SD_WATCH
    // 獲取動圖的幀圖片陣列
    NSArray<UIImage *> *animatedImages = animatedImage.images;
    // 獲取動圖的幀圖片數量
    frameCount = animatedImages.count;
    // 如果幀圖片的數量為0就不繼續執行了,直接返回空
    if (frameCount == 0) {
        return nil;
    }
    
    // 計算每一幀的平均展示時間
    NSTimeInterval avgDuration = animatedImage.duration / frameCount;
    // 如果這個動圖沒有展示時間就預設每一幀展示100毫秒
    if (avgDuration == 0) {
        avgDuration = 0.1; 
    }
    
    // 記錄不同幀圖片的數量
    __block NSUInteger index = 0;
    // 記錄一幀圖片重複次數
    __block NSUInteger repeatCount = 1;
    // 記錄當前遍歷到的圖片之前的圖片
    __block UIImage *previousImage = animatedImages.firstObject;
    [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
        // 第一張圖片不處理
        if (idx == 0) {
            return;
        }
        if ([image isEqual:previousImage]) {
            // 如果這一幀的圖片和之前一幀圖片相同就新增重複次數
            repeatCount++;
        } else {
            // 如果兩幀圖片不相同,就生成SDWebImageFrame物件
            SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
            // 陣列記錄物件
            [frames addObject:frame];
            // 重複次數設定為一次
            repeatCount = 1;
            // 記錄不同的幀數自增
            index++;
        }
        // 記錄當前圖片,用於下次遍歷使用
        previousImage = image;
        // 如果是最後一張照片就直接新增
        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;
}
複製程式碼

3.影像方向處理相關方法

/**
 將EXIF影像方向轉換為iOS版本的方向
 */
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation;
複製程式碼
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
    // CGImagePropertyOrientation在iOS8上才可用,這裡是為了保持相容性
    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;
}
複製程式碼

/**
 將iOS版本的方向轉換為EXIF影像方向
 */
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation;
複製程式碼
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
    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;
}
複製程式碼

原始碼閱讀系列:SDWebImage

原始碼閱讀:SDWebImage(一)——從使用入手

原始碼閱讀:SDWebImage(二)——SDWebImageCompat

原始碼閱讀:SDWebImage(三)——NSData+ImageContentType

原始碼閱讀:SDWebImage(四)——SDWebImageCoder

原始碼閱讀:SDWebImage(五)——SDWebImageFrame

原始碼閱讀:SDWebImage(六)——SDWebImageCoderHelper

原始碼閱讀:SDWebImage(七)——SDWebImageImageIOCoder

原始碼閱讀:SDWebImage(八)——SDWebImageGIFCoder

原始碼閱讀:SDWebImage(九)——SDWebImageCodersManager

原始碼閱讀:SDWebImage(十)——SDImageCacheConfig

原始碼閱讀:SDWebImage(十一)——SDImageCache

原始碼閱讀:SDWebImage(十二)——SDWebImageDownloaderOperation

原始碼閱讀:SDWebImage(十三)——SDWebImageDownloader

原始碼閱讀:SDWebImage(十四)——SDWebImageManager

原始碼閱讀:SDWebImage(十五)——SDWebImagePrefetcher

原始碼閱讀:SDWebImage(十六)——SDWebImageTransition

原始碼閱讀:SDWebImage(十七)——UIView+WebCacheOperation

原始碼閱讀:SDWebImage(十八)——UIView+WebCache

原始碼閱讀:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat

原始碼閱讀:SDWebImage(二十)——UIButton+WebCache

原始碼閱讀:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache

相關文章