SDWebImage源码阅读--基础知识

一、如何判断图片类型?

当文件通过二进制流数据进行传输时,需要制定规范,用来表明其数据类型。数据类型及其对应的文件头如下:

数据类型 文件头
JPEG (jpg) FFD8FFE1
PNG (png) 89504E47
GIF (gif) 47494638
TIFF tif;tiff 0x4D4D002A
RAR Archive (rar) 52617221
WebP 524946462A73010057454250

SDWebImage通过获取文件头的第一个字节的数据,然后比较其 ASIC 码,进而得出其数据类型。具体实现在 NSData+ImageContentType 中:

/**
 根据图片NSData获取图片的类型

 @param data NSData数据
 @return 图片数据类型
 */
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    uint8_t c;
    // 获取图片数据的第一个字节数据
    [data getBytes:&c length:1];
    // 根据字母的ASC码比较
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}

二、Core Foundation 对象的内存管理

底层的 Core Foundation 对象大对数以 XxxCreateWithXxx 方式创建,例如:

CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8);

对于这种类型对象的内存管理,要相应的使用 CFRetain 和 CFRealease 方法,我们可以直观的认为,这两种方法与 OC 对象的 retain 和 realease 方法等价。
在 ARC 下,Core Foundation 对象转化成 OC 对象时,原来的 Core Foundation 对象该怎样进行内存管理?

  • __bridge只做类型转换,但是不修改对象(内存)管理权;
  • __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;
  • __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

三、如何获取 GIF 图片的第一张图片

UIImage+GIF

/**
 根据gif图片的data生成对应的gif的UIImage对象。而且只会取GIF图片的第一张UIImage。

 @param data gif图片的data对象
 @return 生成的image对象。这里只获取gif图片的第一张图像,如果要实现gif完整图像,使用FLAnimatedImageView
 */
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
    if (!data) {
        return nil;
    }
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    // 获取GIF图片包含的UIImage数量
    size_t count = CGImageSourceGetCount(source);
    UIImage *staticImage;
    // 如果只有一张UIImage
    if (count <= 1) {
        staticImage = [[UIImage alloc] initWithData:data];
    } else {
        // we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
        // this here is only code to allow drawing animated images as static ones
        CGFloat scale = 1;
        scale = [UIScreen mainScreen].scale;
        // 获取第一张UIImage对象
        CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
        // 获取gif图片的第一张图片
        UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
        // 用第一张图片生成一个新的gif图片
        staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
        CGImageRelease(CGImage);
    }
    CFRelease(source);
    return staticImage;
}

四、图片为什么需要解压缩?

  • 不管是 JPEG 还是 PNG 图片,都是一种压缩的位图图形格式。只不过 PNG 图片是无损压缩,并且支持 alpha 通道,而 JPEG 图片则是有损压缩,可以指定 0-100% 的压缩比。
  • 压缩的图形格式不能够被直接使用,必须解压成未压缩的位图格式。
  • 位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点
  • 将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。

当未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩。因此,也就有了业内的解决方案,在子线程提前对图片进行强制解压缩:

/**
 解压缩图片
 @param image UIImage对象
 @return 返回解压缩以后的图片
 */
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
    //图片是否能够加压缩
    if (![UIImage shouldDecodeImage:image]) {
        return image;
    }
    
    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
    /*
     *解压缩操作放入一个自动释放池里面。一遍自动释放所有的变量。
     */
    @autoreleasepool{
        
        CGImageRef imageRef = image.CGImage;
        //获取图片的色彩空间
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
        
        BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                      imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                      imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace) {
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
            CFAutorelease(colorspaceRef);
        }
        //宽度和高度
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        //图片占用的字节数
        size_t bytesPerRow = kBytesPerPixel * width;

        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        // 创建一个位图上下文
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     kBitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        if (context == NULL) {
            return image;
        }
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        // 将原始位图绘制到上下文中
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        // 创建一张新的解压缩后的没有alpha通道的位图
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        // 得到解压缩以后的图片
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}

你可能感兴趣的:(SDWebImage源码阅读--基础知识)