本章开始将介绍SDWebImage库在原有类的基础上增加的拓展方法来完善图片下载功能,涉及的类有NSData+ImageContentType,UIImage+MultiFormat,UIImage+GIF。首先解释一下涉及到的一些基本概念和方法:
•什么是文件头
文件头是指位于文件数据流开头的一段承担一定任务的数据,一般都在开头的部分。文件有很多类型,所以这时候我们就需要利用文件头携带的信息去进行判断。
例如,用十六进制编辑器查看一张gif格式的图片的文件头如下(部分数据):
<47494638 39619001 2c01f672 007b4f27 866952a6 8839a58e 55b59d7d cbb439c3 af49e8dc 1ee9de2d faec1feb e026ece1...965a6ec9 65975e7e 09669862 8e496699 66d21408 003b>
47494638即为gif图片格式的文件头标识。
•如何根据文件头判断图片格式
图片格式 文件头
JPEG (jpg) FFD8FFE1
PNG (png) 89504E47
GIF (gif) 47494638
TIFF(tiff) 49492A00或4D4D002A
WebP 524946462A73010057454250
重点来解释一下webp这种特殊格式,它是由12个字节组成的文件头,把这些字节通过ASCII编码,我们会得到如下内容:
•Image I/O 基础
Image I/O framework提供不透明数据类型(opaque data types),从CGImageSourceRef获取图片数据,将图片数据写入到CGImageDestinationRef。它提供一个范围很广的图片格式,包含web格式,动态图,原始相机数据。是MAC平台最快速图片编码和解码操作,能够加载多张图片的功能,支持图片元数据,并且能有效的缓存图片数据。
一个Image Sources抽象出来了图片数据,它不止包含一个图像,缩略图,还有各个图像的特征和图片文件。这些都是通过CGImageSource实现。当从Image Sources中创建图片时,可以提供一个index和dictionary(利用键值对)来创建一个缩略图或者是允许缓存。在创建图片的时候,也需提供一个index值来索引图片,因为Image Sources中可能是多张图片,如果参数时0,那么只有一个图片。可以通过CGImageSourceGetCount来获得图片在Image Sources中的数量。
当图片从网络中获取的时候,可能由于过大,数据缓慢,这时候就需要渐进式加载图片来显示。主要通过CFData对象来实现:
1.创建一个CFData去添加image data.
2.创建一个渐进式图片资源,通过 CGImageSourceCreateIncremental
3.获取图片数据到CFData中
4.调用CGImageSourceUpdateData函数,传递CFData和一个bool值,去描述这个数据是否包含全部图片数据或者只是部分数据。无论什么情况,这个data包含已经积累的全部图片文件。
如果已经有足够的图片数据,可以通过函数绘制CGImageSourceCreateImageAtIndex部分图片,然后记得要Release掉它。检查是否已经有全部的图片数据通过使用CGImageSourceGetStatusAtIndex函数。如果图片是完整的,函数返回值为kCGImageStatusComplete。否则继续3,4步骤,直到获得全部数据。Release掉渐进式增长的image source。
一.SDWebImage 源码解读之NSData+ImageContentType
先贴下源码:
现在来看源码的话,就简单易懂了。
1.获取二进制数据开头一个字节长度的数据(只需比对文件头的第一个字节数据即可)
[data getBytes:&c length:1];
2.根据文件头判断图片格式
如果第一个字节为FF,就可以判断该文件是一张格式为jpeg的图片,依此论推。
3.针对webp这种特殊格式,先判断data的字节长度是否> 12,如果不大于12,说明不是webp格式;
如果> 12,进而将十六进制转为文本字符串,如果该字符串开头是RIFF,并且结尾是WEBP,就判定该文件类型为webp格式的图片。
总结:从该类来看,SDWebImage判断图片的类型的方法还是比较容易理解,主要考察的是对数据流的文件头知识的了解,对于我来说,之前这块没有进行资料了解的情况下会有点看不懂,理解之后就能够理解这样做的原因。
二.SDWebImage 源码解读之UIImage+GIF
该类中拓展了三个方法,都是针对gif格式图片的加载做完善。
+ (UIImage *)sd_animatedGIFNamed:(NSString *)name;//根据名字获取本地的gif图片(该方法比较简单,自行查看即可,下面不进行涉及)
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;//根据图片二进制数据获取图片对象
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;//根据给定的大小缩放gif图片
现在先来看看关键方法的代码,上面三个方法都是基于此方法进行的:
该方法是通过Image I/O framework提供的一些API接口去获取gif图片中指定帧的图片基本数据,从这些数据中返回当前帧图片的动画时间。CGImageSource是一个负责读取图片数据的类,外部接口通过Image I/O framework提供的一些API接口可以将图片的数据转化成CGImageSource。CGImageSourceCopyPropertiesAtIndex(source, index, nil)方法是获取gif图片数据中指定帧的图片数据的API接口,该接口会返回一个CFDictionaryRef类型的图片数据,通过(__bridge NSDictionary *)的方法就能转化成我们熟悉的NSDictionary类型。在该代码段中,获取到帧图片的gifProperties属性数据之后,根据kCGImagePropertyGIFUnclampedDelayTime和kCGImagePropertyGIFDelayTime去获取帧图片的动画时间,之后对这个动画时间做一个最小值的处理,默认都为0.1s。
现在讲讲第二个方法,根据图片二进制数据获取图片对象:
在该方法中,使用CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL)方法,将图片的二进制数据转化成CGImageSourceRef source 类型,这样便可以使用Image I/O framework提供的一些API接口,CGImageSourceGetCount(source)该方法是用于获取图片中的帧数,如果帧数小于等于1,则直接将图片数据进行转化成UIImage类型不进行操作,如果大于1之后,会对gif图片中所有的帧图片进行遍历,保存到图片数组images中,并统计所有帧动画的总时长,如果最后的总时长为0的情况下就设置默认每帧0.1s的总时长,之后利用UIImage的[UIImage animatedImageWithImages:images duration:duration]方法,将所有的帧动画重新组合成一个UIImage对象。下面讲讲第三个方法:
上边的方法能够实现把图片的尺寸修剪为size,剪裁的前提是根据给出的大小与原图片的宽跟高进行对比,求出宽比,高比中的最大值,然后计算出最大比之后的图片大小,也就是scaledSize的值,同时计算出对应的裁剪开始位置thumbnailPoint,利用UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);方法按比例对每一帧的图片进行绘制。
三.SDWebImage 源码解读之UIImage+MultiFormat
该类中拓展了一个方法,+ (UIImage *)sd_imageWithData:(NSData *)data;,该方法的作用是用来将图片的二进制数据转化成iOS中能够直接使用的UIImage对象,支持多种图片格式进行转化,比UIImage+GIF.h类具有更好的拓展性。先看看该功能的实现思路。
该方法通过调用NSData+ImageContentType.h类中的sd_contentTypeForImageData方法去获取图片的类型,如果是gif类型的图片就是用UIImage+GIF.h中的+ (UIImage *)sd_animatedGIFWithData:(NSData *)data方法将二进制数据转化成图片。如果是webo格式的图片会调用sd_imageWithWebPData进行转化(该方法在SDWebImage库中并没有找到对应实现不知道作者是在哪里进行了实现操作),其他类型的则会使用+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation;方法进行转化,下面的那个方法是利用Image I/O framework库中提供的API方法在获取到的图片数据信息中找出图片的转向,进行绘制。这个属性时为了防止进行剪切或者缩放后图片颠倒或者旋转的问题,保持原图片的转向。