源码阅读:SDWebImage(七)——SDWebImageImageIOCoder

该文章阅读的SDWebImage的版本为4.3.3。

这个是编解码器类,提供了对PNG、JPEG、TIFF类型图片的编解码,还支持逐行解码。

1.公共方法

+ (nonnull instancetype)sharedCoder;
复制代码

这个类除了实现SDWebImageProgressiveCoder协议中方法外,对外暴露的只有一个获取单例对象的方法。

2.静态变量

定义每像素字节数为4

static const size_t kBytesPerPixel = 4;
复制代码

定义每位字节数为8

static const size_t kBitsPerComponent = 8;
复制代码

定义解码图像的最大大小为60MB

static const CGFloat kDestImageSizeMB = 60.0f;
复制代码

定义用于解码图像的最大大小为20MB

static const CGFloat kSourceImageTileSizeMB = 20.0f;
复制代码

定义1MB是1024*1024Bytes

static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
复制代码

定义1MB的像素数是1MB的Bytes数量除以1像素的Bytes数

static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
复制代码

定义解码完的图像的最大像素数是解码完的图像最大大小乘以1MB的像素数

static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
复制代码

定义用于解码的图像的最大像素数是解码的图像的最大大小乘以1MB的像素数

static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
复制代码

定义重叠像素大小为2像素

static const CGFloat kDestSeemOverlap = 2.0f;
复制代码

3.私有变量

定义了两个变量来保存图像的宽和高

size_t _width, _height;
复制代码

定义变量记录图像方向

UIImageOrientation _orientation;
复制代码

定义变量记录图像源

CGImageSourceRef _imageSource;
复制代码

4.生命周期方法

- (void)dealloc {
    // 释放并置空图像源变量
    if (_imageSource) {
        CFRelease(_imageSource);
        _imageSource = NULL;
    }
}
复制代码
+ (instancetype)sharedCoder {
    // 实例化SDWebImageImageIOCoder对象
    static SDWebImageImageIOCoder *coder;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coder = [[SDWebImageImageIOCoder alloc] init];
    });
    return coder;
}
复制代码

5.私有方法

/**
 判断图像是否需要解码
 */
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
    // 如果没传图像就直接返回NO
    if (image == nil) {
        return NO;
    }
    
    // 如果是动图也直接返回NO
    if (image.images != nil) {
        return NO;
    }
    
    // 获取到图像的位图图像
    CGImageRef imageRef = image.CGImage;
    
    // 判断图像是否有通明度
    BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
    // 如果有透明度也直接返回NO
    if (hasAlpha) {
        return NO;
    }
    
    return YES;
}
复制代码
/**
 判断是否支持HEIC类型图像的解码
 */
+ (BOOL)canDecodeFromHEICFormat {
    // 1.先获取进程信息对象
    // 2.通过进程信息对象获取操作系统版本
    // 3.如果当前操作系统版本是iOS 11+就支持HEIC类型图像的解码
    static BOOL canDecode = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
#if TARGET_OS_SIMULATOR || SD_WATCH
        canDecode = NO;
#elif SD_MAC
        NSProcessInfo *processInfo = [NSProcessInfo processInfo];
        if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
            // macOS 10.13+
            canDecode = processInfo.operatingSystemVersion.minorVersion >= 13;
        } else {
            canDecode = NO;
        }
#elif SD_UIKIT
        NSProcessInfo *processInfo = [NSProcessInfo processInfo];
        if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
            // iOS 11+ && tvOS 11+
            canDecode = processInfo.operatingSystemVersion.majorVersion >= 11;
        } else {
            canDecode = NO;
        }
#endif
#pragma clang diagnostic pop
    });
    return canDecode;
}
复制代码
/**
 判断是否支持HEIC类型图像的编码
 */
+ (BOOL)canEncodeToHEICFormat {
    // 如果能创建CGImageDestinationRef对象就支持HEIC类型图像的编码
    static BOOL canEncode = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableData *imageData = [NSMutableData data];
        CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];
        
        // Create an image destination.
        CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
        if (!imageDestination) {
            // Can not encode to HEIC
            canEncode = NO;
        } else {
            // Can encode to HEIC
            CFRelease(imageDestination);
            canEncode = YES;
        }
    });
    return canEncode;
}
复制代码
/**
 通过图像数据获取图像的EXIF方向
 */
+ (UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
    // 定义临时变量保存方向,默认向上
    UIImageOrientation result = UIImageOrientationUp;
    // 获取图像源对象
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    // 如果能获取到图像源对象就进一步判断,否则就返回向上
    if (imageSource) {
        // 获取图像源中图像的属性字典
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
        if (properties) {
            // 如果获取成功就继续获取图像方向
            CFTypeRef val;
            NSInteger exifOrientation;
            val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
            if (val) {
                // 如果获取到方向就转换成UIImageOrientation类型数据返回
                CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation);
                result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
            } // 如果没获取到方向就返回向上
            CFRelease((CFTypeRef) properties);
        } else {
            // 如果获取失败就返回向上
        }
        CFRelease(imageSource);
    }
    return result;
}
复制代码
/**
 判断是否缩小图片
 */
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
    BOOL shouldScaleDown = YES;
    
    // 获取到图像的位图图像
    CGImageRef sourceImageRef = image.CGImage;
    // 获取到位图图像的宽和高
    CGSize sourceResolution = CGSizeZero;
    sourceResolution.width = CGImageGetWidth(sourceImageRef);
    sourceResolution.height = CGImageGetHeight(sourceImageRef);
    // 计算图像的总像素数
    float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
    // 用最大像素数除以总像素数以获取图像的压缩比
    float imageScale = kDestTotalPixels / sourceTotalPixels;
    // 如果压缩比小于1,也就是总像素数大于最大像素数,就返回YES
    // 否则就返回NO
    if (imageScale < 1) {
        shouldScaleDown = YES;
    } else {
        shouldScaleDown = NO;
    }
    
    return shouldScaleDown;
}
复制代码
/**
 获取图像的色彩空间
 */
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
    // 获取图像色彩空间模型
    CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
    // 获取图像色彩空间
    CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
    
    // 如果色彩空间模型是未知、单色彩、CMYK、索引类型的,就统一返回RGB类型。
    // 其他的就返回原本获取到的类型
    BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                  imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                  imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                  imageColorSpaceModel == kCGColorSpaceModelIndexed);
    if (unsupportedColorSpace) {
        colorspaceRef = SDCGColorSpaceGetDeviceRGB();
    }
    return colorspaceRef;
}
复制代码
/**
 解压图片对象
 */
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
    // 如果不需要解码就不继续进行了了,直接返回
    if (![[self class] shouldDecodeImage:image]) {
        return image;
    }
    
    // 建立自动释放池,以帮助系统在收到内存警告时释放内存。
    @autoreleasepool{
        
        // 获取到图片对象的位图图像
        CGImageRef imageRef = image.CGImage;
        // 获取图片对象的色彩空间
        CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:imageRef];
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        
        // 创建不带透明度的位图图形上下文
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     kBitsPerComponent,
                                                     0,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        // 如果创建失败就直接返回图片对象
        if (context == NULL) {
            return image;
        }
        
        // 绘制图像到上下文中
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        // 生成位图图像
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        // 根据位图图像生成图片对象
        UIImage *imageWithoutAlpha = [[UIImage alloc] initWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];
        // 释放掉上下文
        CGContextRelease(context);
        // 释放掉位图图像
        CGImageRelease(imageRefWithoutAlpha);
        
        // 返回生成的图片对象
        return imageWithoutAlpha;
    }
}
复制代码
/**
 解压并缩小图片对象
 */
- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
    // 如果不需要解码就不继续进行了,直接返回
    if (![[self class] shouldDecodeImage:image]) {
        return image;
    }
    
    // 如果不需要缩小就调用只解压的方法
    if (![[self class] shouldScaleDownImage:image]) {
        return [self sd_decompressedImageWithImage:image];
    }
    
    // 创建图像上下文
    CGContextRef destContext;
    
    // 建立自动释放池,以帮助系统在收到内存警告时释放内存。
    @autoreleasepool {
        // 获取到图片对象的位图图像
        CGImageRef sourceImageRef = image.CGImage;
        
        // 获取图像的总像素数
        CGSize sourceResolution = CGSizeZero;
        sourceResolution.width = CGImageGetWidth(sourceImageRef);
        sourceResolution.height = CGImageGetHeight(sourceImageRef);
        float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
        
        // 计算缩小比例
        float imageScale = kDestTotalPixels / sourceTotalPixels;
        // 计算缩小后的尺寸
        CGSize destResolution = CGSizeZero;
        destResolution.width = (int)(sourceResolution.width*imageScale);
        destResolution.height = (int)(sourceResolution.height*imageScale);
        
        // 获取色彩空间
        CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:sourceImageRef];
        
        // 创建图像上下文
        destContext = CGBitmapContextCreate(NULL,
                                            destResolution.width,
                                            destResolution.height,
                                            kBitsPerComponent,
                                            0,
                                            colorspaceRef,
                                            kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        // 如果创建失败就直接返回图片对象
        if (destContext == NULL) {
            return image;
        }
        // 设置图像上下文的绘图质量
        CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
        
        // 创建临时变量保存来源块大小
        CGRect sourceTile = CGRectZero;
        // 来源块的宽度就是原图的宽度
        sourceTile.size.width = sourceResolution.width;
        // 计算来源块的高度
        sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
        // 设置来源块的横坐标
        sourceTile.origin.x = 0.0f;
        
        // 创建历史变量保存目标块大小
        CGRect destTile;
        // 目标块的宽是缩放后的宽
        destTile.size.width = destResolution.width;
        // 目标块的宽是来源块的高乘以缩放比
        destTile.size.height = sourceTile.size.height * imageScale;
        // 设置目标块的横坐标
        destTile.origin.x = 0.0f;
        // 计算来源块与目标块的重复区域
        float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
        // 生成变量保存来源块图像位图
        CGImageRef sourceTileImageRef;
        // 计算需要绘制的次数
        int iterations = (int)( sourceResolution.height / sourceTile.size.height );
        // 计算剩余像素的高度
        int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
        if(remainder) {
            // 如果有剩余像素就将绘制次数加1
            iterations++;
        }
        // 创建变量保存来源块的高度,用来计算纵坐标的移动
        float sourceTileHeightMinusOverlap = sourceTile.size.height;
        // 来源块高度加上要重复覆盖的高度
        sourceTile.size.height += sourceSeemOverlap;
        // 目标块高度加上重叠的像素数
        destTile.size.height += kDestSeemOverlap;
        // 开启循环绘制图像
        for( int y = 0; y < iterations; ++y ) {
            // 建立自动释放池,以帮助系统在收到内存警告时释放内存。
            @autoreleasepool {
                // 计算来源块的纵坐标:来源块的高度乘以当前循环次数,然后加上重复覆盖的高度
                sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
                // 计算目标块的纵坐标:目标图像的高度减去要绘制的来源块的高度乘以压缩比,再减去重叠高度
                // 这个地方,来源块的纵坐标是递增的,目标块的纵坐标是递减的,这是因为为UIKit的坐标系和CGContext是镜像关系
                destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
                // 按照计算好的尺寸绘制来源块的位图
                sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
                // 如果是最后一块要绘制,并且这一块是剩余的像素
                if( y == iterations - 1 && remainder ) {
                    // 因为剩余像素的高度是不固定的,所以重新计算目标块的纵坐标
                    float dify = destTile.size.height;
                    destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
                    dify -= destTile.size.height;
                    destTile.origin.y += dify;
                }
                // 将来源块位图按照计算好的尺寸绘制到目标图像上下文中
                CGContextDrawImage( destContext, destTile, sourceTileImageRef );
                // 释放来源块位图
                CGImageRelease( sourceTileImageRef );
            }
        }
        
        // 根据目标图像上下文生成目标图像位图
        CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
        // 释放目标图像上下文
        CGContextRelease(destContext);
        // 如果生成位图失败就直接返回图片对象
        if (destImageRef == NULL) {
            return image;
        }
        // 生成目标图片对象
        UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
        // 释放目标图像位图
        CGImageRelease(destImageRef);
        // 如果生成图片对象失败就直接返回图片对象
        if (destImage == nil) {
            return image;
        }
        // 返回目标图片对象
        return destImage;
    }
}
复制代码

6.SDWebImageProgressiveCoder协议方法实现

  • 解码
- (BOOL)canDecodeFromData:(nullable NSData *)data {
    // 获取图像类型
    switch ([NSData sd_imageFormatForImageData:data]) {
        case SDImageFormatWebP:
            // 如果是WebP格式就返回NO
            return NO;
        case SDImageFormatHEIC:
            // 如果是HEIC格式就检查当前系统是否支持
            return [[self class] canDecodeFromHEICFormat];
        default:
            // 其他类型就返回YES
            return YES;
    }
}
复制代码
- (UIImage *)decodedImageWithData:(NSData *)data {
    // 如果没传数据就返回空
    if (!data) {
        return nil;
    }
    
    // 生成图片对象
    UIImage *image = [[UIImage alloc] initWithData:data];
    
#if SD_MAC
    return image;
#else
    // 如果生成失败就返回空
    if (!image) {
        return nil;
    }
    
    // 获取到图像的方向
    UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
    // 如果方向不为上,就根据原图片方向生成图片对象
    if (orientation != UIImageOrientationUp) {
        image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:orientation];
    }
    
    return image;
#endif
}
复制代码
- (UIImage *)decompressedImageWithImage:(UIImage *)image
                                   data:(NSData *__autoreleasing  _Nullable *)data
                                options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
#if SD_MAC
    return image;
#endif
#if SD_UIKIT || SD_WATCH
    // 创建变量保存,默认是不缩小
    BOOL shouldScaleDown = NO;
    // 如果传入选项字典参数
    if (optionsDict != nil) {
        // 创建变量保存value值
        NSNumber *scaleDownLargeImagesOption = nil;
        // 如果SDWebImageCoderScaleDownLargeImagesKey对应的value值是NSNumber类型的,就用变量保存
        if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
            scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
        }
        // 如果获取到的value值不为空,就转换成BOOL类型,并保存
        if (scaleDownLargeImagesOption != nil) {
            shouldScaleDown = [scaleDownLargeImagesOption boolValue];
        }
    }
    if (!shouldScaleDown) {
        // 如果不需要缩小就直接调用解压方法
        return [self sd_decompressedImageWithImage:image];
    } else {
        // 如果需要缩小就调用解压并缩小方法
        UIImage *scaledDownImage = [self sd_decompressedAndScaledDownImageWithImage:image];
        
        if (scaledDownImage && !CGSizeEqualToSize(scaledDownImage.size, image.size)) {
            // 如果处理成功,还要处理数据指针,调用压缩方法
            SDImageFormat format = [NSData sd_imageFormatForImageData:*data];
            NSData *imageData = [self encodedDataWithImage:scaledDownImage format:format];
            if (imageData) {
                // 通过参数回传处理后的图像数据
                *data = imageData;
            }
        }
        // 返回处理后的图片
        return scaledDownImage;
    }
#endif
}
复制代码
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
    // 获取图像类型
    switch ([NSData sd_imageFormatForImageData:data]) {
        case SDImageFormatWebP:
            // 如果是WebP格式就返回NO
            return NO;
        case SDImageFormatHEIC:
            // 如果是HEIC格式就检查当前系统是否支持
            return [[self class] canDecodeFromHEICFormat];
        default:
            // 其他类型就返回YES
            return YES;
    }
}
复制代码
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
    // 创建一个增量图像源
    if (!_imageSource) {
        _imageSource = CGImageSourceCreateIncremental(NULL);
    }
    UIImage *image;
    
    // 更新数据源
    CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
    
    // 获取到图像的宽、高和方向
    if (_width + _height == 0) {
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
        if (properties) {
            NSInteger orientationValue = 1;
            CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
            val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
            val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
            if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
            CFRelease(properties);
            
            // When we draw to Core Graphics, we lose orientation information,
            // which means the image below born of initWithCGIImage will be
            // oriented incorrectly sometimes. (Unlike the image born of initWithData
            // in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
            _orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue];
#endif
        }
    }
    
    if (_width + _height > 0) {
        // 创建位图对象
        CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
        
#if SD_UIKIT || SD_WATCH
        // iOS变形图像的解决方法
        if (partialImageRef) {
            // 获取图像的高
            const size_t partialHeight = CGImageGetHeight(partialImageRef);
            // 生成RGB色彩空间
            CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
            // 创建位图图形上下文
            CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
            if (bmContext) {
                // 将图像绘制到位图图形上下文中
                CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
                // 位图对象的引用计数-1
                CGImageRelease(partialImageRef);
                // 通过上下文获取图片
                partialImageRef = CGBitmapContextCreateImage(bmContext);
                // 位图对象的引用计数-1
                CGContextRelease(bmContext);
            }
            else {
                // 位图对象的引用计数-1
                CGImageRelease(partialImageRef);
                // 位图对象置空
                partialImageRef = nil;
            }
        }
#endif
        // 根据位图生成图片独享
        if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
            image = [[UIImage alloc] initWithCGImage:partialImageRef scale:1 orientation:_orientation];
#elif SD_MAC
            image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif      // 位图对象的引用计数-1
            CGImageRelease(partialImageRef);
        }
    }
    
    // 如果加载完成就释放掉位图对象,并置空
    if (finished) {
        if (_imageSource) {
            CFRelease(_imageSource);
            _imageSource = NULL;
        }
    }
    
    // 返回图片对象
    return image;
}
复制代码
  • 编码
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
    // 获取图像类型
    switch (format) {
        case SDImageFormatWebP:
            // 如果是WebP格式就返回NO
            return NO;
        case SDImageFormatHEIC:
            // 如果是HEIC格式就检查当前系统是否支持
            return [[self class] canEncodeToHEICFormat];
        default:
            // 其他类型就返回YES
            return YES;
    }
}
复制代码
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
    // 如果图片对象为空就直接返回空
    if (!image) {
        return nil;
    }
    
    // 如果没有传图片类型
    if (format == SDImageFormatUndefined) {
        // 有透明度的就是PNG类型,否则就是JPEG类型
        BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
        if (hasAlpha) {
            format = SDImageFormatPNG;
        } else {
            format = SDImageFormatJPEG;
        }
    }
    
    // 创建变量保存图片编码后的数据
    NSMutableData *imageData = [NSMutableData data];
    // 获取CFStringRef格式的图片类型
    CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
    
    // 创建CGImageDestinationRef对象
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
    // 如果创建失败就直接返回空
    if (!imageDestination) {
        // Handle failure.
        return nil;
    }
    
    // 创建字典对象保存编码参数
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
    // 获取图片的方向
    NSInteger exifOrientation = [SDWebImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
    // 设置方向参数
    [properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
#endif
    
    // 将图像位图对象添加到CGImageDestinationRef对象中
    CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
    
    // 如果编码失败就返回空
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        // Handle failure.
        imageData = nil;
    }
    
    // 释放掉imageDestination对象
    CFRelease(imageDestination);
    
    // 返回编码后的数据
    return [imageData copy];
}

复制代码

7.总结

这个类实现了SDWebImageProgressiveCoder协议中的方法,提供了解码、编码、解压和缩小的功能,下面我们就梳理一下各个功能的逻辑。

7.1.解码

这个功能实现比较简单,主要是利用了UIImage类的initWithData:initWithCGImage: scale: orientation:这两个对象方法:

  • 首先判断是否有图像数据;
  • 接着利用图像数据生成图片对象;
  • 然后判断图片对象是否生成成功;
  • 再获取图像方向,如果方向不是向上,就根据方向生成图片对象;
  • 最后返回生成的图片对象

7.2.编码

这个功能涉及到了对ImageIO库中CGImageDestinationRef的使用,但是我们通篇看下来,就是三个步骤:

  1. 把冰箱门打开:利用CGImageDestinationCreateWithData()函数创建图像目的地;

  2. 把大象放进去:使用CGImageDestinationAddImage()函数将图片对象添加到图像目的地中;

  3. 把冰箱门关上:调用CGImageDestinationFinalize()函数将图片对象写入到数据对象中;

具体的逻辑是:

  • 首先判断是否有图片对象;
  • 接着判断图片对象的格式;
  • 然后创建图像目的地对象;
  • 接着配置编码的属性,即图像的方向;
  • 然后将图片对象和属性添加到图像目的地对象中编码;
  • 最后返回编码后的数据;

7.3.解压

图片为什么要解压?具体可以看一下这篇文章:谈谈 iOS 中图片的解压缩。解压的步骤也是分为三步:

  1. 使用CGBitmapContextCreate()函数创建位图图像上下文;
  2. 利用CGContextDrawImage()函数将图片绘制到位图图像上下文中;
  3. 调用CGBitmapContextCreateImage()函数从位图图像上下文中获取位图图像;

具体逻辑是:

  • 首先判断是否应该解码,如果图片对象不存在、是动图、有透明度就不解码;
  • 接下来创建一个自动释放池,以便在内存不足时清除缓存;
  • 然后创建一个位图图像上下文;
  • 接着就把图像绘制到位图图像上下文中;
  • 再从位图图像上下文中获取绘制好的位图对象;
  • 最后利用位图对象生成图片对象并返回;

7.4.解压并缩小

具体逻辑:

  • 首先判断是否应该解码,如果图片对象不存在、是动图、有透明度就不解码;
  • 然后判断是否应该缩小,如果图像的像素数量不超过指定数量就只解压不缩小;
  • 接下来创建一个自动释放池,以便在内存不足时清除缓存;
  • 接着创建一个位图图像上下文;
  • 然后创建一个循环,依次从原图像中获取一片宽度和原图相等,高度是指定数值,横坐标为0,纵坐标递增的图像,绘制到位图图像上下文的指定位置和大小,直至获取完原图。
  • 再从位图图像上下文中获取绘制好的位图对象;
  • 最后利用位图对象生成图片对象并返回;

其中缩小的功能实现的思想是,将原图分片缩小。每次获取原图中的一片进行缩小,直至全部缩小完成。

源码阅读系列: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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值