源码阅读:SDWebImage(六)——SDWebImageCoderHelper

本文介绍SDWebImage中动图处理和图像方向处理的方法实现,包括将动图转换为帧数组及反向操作,以及处理图像的EXIF方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

该文章阅读的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值