### NSData+ImageContentType: 根据NSData获取MIME
正如标题`NSData+ImageContentType`的唯一方法`+ (NSString *)sd_contentTypeForImageData:(NSData *)data;`就是根据图片的二进制数据返回其对应的MIME类型。它的具体实现如下:
```objectivec
+ (NSString *)sd_contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
case 0x52:
if ([data length] NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"image/webp";
}

return nil;
}
return nil;

}

实际上每个文件的前几个字节都标识着文件的类型,对于一般的图片文件,通过第一个字节(WebP需要12字节)可以辨识出文件类型。
这个方法的实现思路是这样的:
1.取data的第一个字节的数据,辨识出JPG/JPEG、PNG、GIF、TIFF这几种图片格式,返回其对应的MIME类型。
2.如果第一个字节是数据为`0x52`,需要进一步检测,因为以`0x52`为文件头的文件也可能会是rar等类型(可以在<a href="http://baike.baidu.com/link?url=_PP3WE8Xx_j8lEFuiO-MfBmI-TskdkeF1ZQE6CUUUGtQbPE6RB1SGTal7-oUZJVWK0n9AkpnQpQ2E4YRScA1q_" target='blank'>文件头</a>查看),而webp的前12字节有着固定的数据:
<img src="http://qiniu.storage.mikezh.com/img%2FKVZ%7D50%60FID%5BX1%5DJ%5D%5D4J%5DRRU.jpg" width="400" alt="WebP文件前12字节数据"/>
因此前12字节数据有前缀`RIFF`和后缀`WEBP`的就是WebP格式。
<div style="text-align: right;font-size:9pt;"><a href="#labelTop" name="anchor2_0">回到顶部</a></div>
### UIImage+GIF
在介绍这个分类之前,我们要弄清一个问题,iOS展示gif图的原理:
1.将gif图的每一帧导出为一个UIImage,将所有导出的UIImage放置到一个数组
2.用上面的数组作为构造参数,使用animatedImage开头的方法创建UIImage,此时创建的UIImage的images属性值就是刚才的数组,duration值是它的一次播放时长。
3.将UIImageView的image设置为上面的UIImage时,gif图会自动显示出来。(也就是说关键是那个数组,用尺寸相同的图片创建UIImage组成数组也是可以的) 这个分类下有三个方法:
```objectivec
// 指定在main bundle中gif的文件名,读取文件的二进制,然后调用下面的方法
+ (UIImage *)sd_animatedGIFNamed:(NSString *)name; // 将gif文件的二进制转为animatedImage
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data; // 将self.images数组中的图片按照指定的尺寸缩放,返回一个animatedImage,一次播放的时间是self.duration
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;

可以说共有两个功能:

1.+sd_animatedGIFNamed:+ sd_animatedGIFWithData:将文件(二进制)转为animatedImage。

2.-sd_animatedImageByScalingAndCroppingToSize:负责gif图的缩放。

方法+ sd_animatedGIFWithData:的实现细节是这样的:

+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil;
}
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
// 获取图片数量(如果传入的是gif图的二进制,那么获取的是图片帧数)
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
// 遍历每张图,通过`sd_frameDurationAtIndex:source:`获取每张图需要的播放时间,用duration累加,将图到出为UIImage,依次放到数组imges中
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
continue;
}
duration += [self sd_frameDurationAtIndex:i source:source];
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image);
}
// 如果上面的计算播放时间方法没有成功,就按照下面方法计算
// 计算一次播放的总时间:每张图播放1/10秒 * 图片总数
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
}
CFRelease(source);
return animatedImage;
}

计算每帧需要播放的时间的方法实现为:

+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
// 获取这一帧的属性字典
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
// 从字典中获取这一帧持续的时间
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
} else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
// 许多烦人的广告指定duration为0来让图像尽可能快地闪过。
// 我们遵循Firefox的做法:对于指定duration小于<= 10 ms的帧设置duration值为100ms
// 详见<rdar://problem/7689300>和<http://webkit.org/b/36082>
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
}

+ (UIImage *)sd_animatedGIFNamed:(NSString *)name方法的实现比较简单,对retina的屏幕做了一点适配,只需将文件的name传入即可,不需传入文件后面的@"2x"或者.gif文件后缀。这个方法内部会根据当前屏幕的scale决定时候添加@"2x",然后添加文件后缀,在mainBundle中找到这个文件读取出二进制然后调用方法+ (UIImage *)sd_animatedGIFWithData:(NSData *)data

对gif图进行缩放的方法- sd_animatedImageByScalingAndCroppingToSize:的实现思路为:

1.取较大的缩放比例值,用这个值让宽高等比缩放

2.调整位置,使缩放后的图居中

3.遍历self.images, 将每张图缩放后导出,放到数组中

4.使用上面的数组创建animatedImage并返回

### UIImage+WebP
首先了解一下WebP
>WebP格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。Facebook Ebay等知名网站已经开始测试并使用WebP格式。
>但WebP是一种有损压缩。相较编码JPEG文件,编码同样质量的WebP文件需要占用更多的计算资源。
>桌面版Chrome可打开WebP格式。

题外话:google还开发了音/视频格式WebM,针对Web平台的音视频传输格式

WebM由Google提出,是一个开放、免费的媒体文件格式。WebM 影片格式其实是以 Matroska(即 MKV)容器格式为基础开发的新容器格式,里面包括了VP8影片轨和 Ogg Vorbis 音轨,其中Google将其拥有的VP8视频编码技术以类似BSD授权开源,Ogg Vorbis 本来就是开放格式。 WebM标准的网络视频更加偏向于开源并且是基于HTML5标准的,WebM 项目旨在为对每个人都开放的网络开发高质量、开放的视频格式,其重点是解决视频服务这一核心的网络用户体验。Google 说 WebM 的格式相当有效率,应该可以在 netbook、tablet、手持式装置等上面顺畅地使用。

UIImage+WebP提供了一个WebP图片的二进制数据转为UIImage的方法+ (UIImage *)sd_imageWithWebPData:(NSData *)data;,但是想要使用它,还必须先在项目中导入WebP的解析器libwebp,需要在google code相应的页面clone下来https://developers.google.com/speed/webp/download,但是没翻墙就哭了。这里提供了一个github上的一个mirror:https://github.com/webmproject/libwebp, SD对libwebp的版本也有要求,我下载是用的是0.4.3版本。

下面我们看一下+ (UIImage *)sd_imageWithWebPData:(NSData *)data;方法的实现:

+ (UIImage *)sd_imageWithWebPData:(NSData *)data {
WebPDecoderConfig config;
// 初始化解码器结构体(WebPDecoderConfig类型)变量config
if (!WebPInitDecoderConfig(&config)) {
return nil;
}
// 将WebP图片的二进制数据传递给config
if (WebPGetFeatures(data.bytes, data.length, &config.input) != VP8_STATUS_OK) {
return nil;
} config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
config.options.use_threads = 1; // 将WebP图片数据解码为RGBA值数组,保存在config中
if (WebPDecode(data.bytes, data.length, &config) != VP8_STATUS_OK) {
return nil;
}
// 从config中读取出图片的宽高信息
int width = config.input.width;
int height = config.input.height;
if (config.options.use_scaling) {
width = config.options.scaled_width;
height = config.options.scaled_height;
} // 根据解码后的RGBA值数组创建UIImage.
// 1.创建数据提供者,参数指定了RGBA值数组的开始地址`config.output.u.RGBA.rgba`和长度`config.output.u.RGBA.size`,用于释放数据的回调`FreeImageData`
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0;
size_t components = config.input.has_alpha ? 4 : 3;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
// 2.使用数据提供者和其他信息创建CGImageRef
CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
// 3.将CGImageRef转为UIImage
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef); return image;
}

### UIImage+MultiFormat:根据NSData相应的MIME将NSData转为UIImage
这个分类提供了一个通用的方法,的当不知道图片是什么格式的时候,可以使用这个方法将二进制直接传递过来,这个方法的内部会检测图片的类型,并根据相应的方法创建UIImage。
```objectivec
+ (UIImage *)sd_imageWithData:(NSData *)data {
if (!data) {
return nil;
}

UIImage *image;
NSString *imageContentType = [NSData sd_contentTypeForImageData:data];
if ([imageContentType isEqualToString:@"image/gif"]) {
// 1.如果是gif,使用gif的data转UIImage方法
image = [UIImage sd_animatedGIFWithData:data];
}

ifdef SD_WEBP

else if ([imageContentType isEqualToString:@"image/webp"]) {
// 2.如果是WebP,使用WebP的data转UIImage方法
image = [UIImage sd_imageWithWebPData:data];
}

endif

else {// 3.其他情况
image = [[UIImage alloc] initWithData:data];
// 获取图片的方向
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
// 如果方向不是向上,则使用方向重新创建图片
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
}
}
return image;

}

<div style="text-align: right;font-size:9pt;"><a href="#labelTop" name="anchor5_0">回到顶部</a></div>
需要说明的是:在其他情况的处理上的一些细节,
SD会先获取到图片的原始方向,如果方向不是UIImageOrientationUp,使用UIImage的`-imageWithCGImage:scale:orientation:`方法创建图片,这个方法内部会按照传递的方向值将图片还原为正常的显示效果。 举例来说,如果拍摄时相机摆放角度为`逆时针`旋转90度(对应着的EXIF值为8),拍摄出来的图片显示效果为`顺时针`旋转了90度(这就好比在查看时相机又摆正了,实际上在windows下的图片查看器显示为顺时针旋转了90度,而mac由于会自动处理则正向显示),而如果使用UIImage的`-imageWithCGImage:scale:orientation:`方法创建图片,则会正向显示也就是实际拍摄时的效果。 至于相机摆放的角度如何与EXIF值对应,请参照这篇文章<a href="http://www.cocoachina.com/ios/20150605/12021.html" target='blank'>《如何处理iOS中照片的方向》</a>,注意的就是iphone的初始方向是横屏home键在后侧的情况。 图片的EXIF信息会记录拍摄的角度,SD会从图片数据中读取出EXIF信息,由于EXIF值与方向一一对应(EXIF值-1 = 方向),那么就使用`+ sd_exifOrientationToiOSOrientation:`方法通过传递EXIF值获取到方向值。最后就是通过UIImage的`-imageWithCGImage:scale:orientation:`方法创建图片。 在网上有很多介绍如何获取正向图片的方法,它们的思路大多是这样:根据图片的方向值来逆向旋转图片。殊不知,apple早就为你提供好了`-imageWithCGImage:scale:orientation:`方法来直接创建出一个可正常显示的图片。

通读SDWebImage③--gif和webP的支持、不同格式图片的处理、方向处理的更多相关文章

  1. 利用 LibWebP-NET 解码与编码 WebP 格式图片

    此文以后将会在我的新博客更新,有任何疑问可在我的新博文中提出 https://blog.clso.fun/posts/2019-03-02/vb-net-webp.html WebP 格式是谷歌开发并 ...

  2. CentOS系统php5.6安装ImageMagick处理webp格式图片

    1.先安装webp yum install libwebp 2.编译安装ImageMagick 之前有过yum安装的先卸载 yum remove ImageMagick 我使用的是老版本ImageMa ...

  3. windows下查看webp格式图片

    关于webp 时下webp格式日渐势起,主流浏览器(IE: ???)已经开始支持webp,诸多互联网企业(Facebook 和 ebay,国内的有淘宝.腾讯和美团等)都已经在不遗余力的将webp应用到 ...

  4. Kindle支持哪些格式

    官方产品介绍页面有相关技术参数: Kindle Format 8 (AZW3), Kindle (AZW), TXT,PDF, MOBI, PRC原格式,HTML,DOC,DOCX,JPEG,GIF, ...

  5. 修改stb_image.c以让Duilib直接支持Ico格式的图标显示

    duilib不支持ico格式的图标资源, 但是我要想显示ico格式的图标... 发现网上那些转换ico为bmp或其它格式的都不是一个好办法, 也还是不能让duilib直接显示ico... 昨晚稍微研究 ...

  6. 使用DD_belatedPNG让IE6支持PNG透明图片

    使用DD_belatedPNG让IE6支持PNG透明图片 众所周知IE6不支持透明的PNG图片,而PNG图片在Web设计方面表现力上,具有其它图形格式所达不到的效果,IE6这一致命缺陷极大地限制了We ...

  7. iOS图案锁,支持动画、图片、绘图

    最近忙着搭建一个聊天用的框架,过几天应该会整理写出来吧,原理不难,但是实现后会省很多事.好久没写博客,周末心血来潮写了个图案锁,这东西没什么技术含量,网上一堆,这次这个图案锁顺便联系了怎么打包使用.a ...

  8. NSIS:简单按钮美化插件SkinButton,支持透明PNG图片。

    原文 NSIS:简单按钮美化插件SkinButton,支持透明PNG图片. 征得作者贾可的同意,特发布按钮美化插件SkinButton. 插件说明: 使用GDI+库写的一个简单按钮美化插件,支持透明P ...

  9. c# 播放器 支持所有格式

    原文:c# 播放器 支持所有格式 ---恢复内容开始--- 直接上代码 internal static class LibVlcAPI { internal struct PointerToArray ...

随机推荐

  1. Selenium Xpath Tutorials - Identifying xpath for element with examples to use in selenium

    Xpath in selenium is close to must required. XPath is element locator and you need to provide xpath ...

  2. 使用 bash 创建定时任务

    假设要增加每分钟执行一次的检测任务 (crontab -l; echo "* * * * * python check.py") | crontab 在 centos 6 上, 注 ...

  3. 【webGL】threejs常用的api

    /*** 场景(scene) ***/ var scene = new THREE.Scene(); // 创建场景 scene.add(x); // 插入场景 /*** 相机(camera) *** ...

  4. ASP.NET中的chart控件绑定SQL Server数据库

    网上很多的chart控件的实例都没有绑定数据库,经过一番摸索后,终于实现了chart控件绑定数据库. 首先,在Visual Studio中建立一个网站,新建一个WebForm项目,名称为ChartTe ...

  5. 采用cocos2d-x lua 制作数字滚动效果样例

    require "Cocos2d"require "Cocos2dConstants"local testscene = class("testsce ...

  6. Scrapy 爬虫 使用指南 完全教程

    scrapy note command 全局命令: startproject :在 project_name 文件夹下创建一个名为 project_name 的Scrapy项目. scrapy sta ...

  7. 用markdown简化书写

    引言 markdown是什么, 谁在使用markdown, 以及为什么我应该/不应该使用它? 我能从这篇文章中了解到什么, 怎么开始使用? markdown有两个含义: 一套标记语法(类比html,w ...

  8. 基于netty-socketio的web推送服务

    实时消息的推送,PC端的推送技术可以使用socket建立一个长连接来实现.传统的web服务都是客户端发出请求,服务端给出响应.但是现在直观的要求是允许特定时间内在没有客户端发起请求的情况下服务端主动推 ...

  9. Flume(3)source组件之NetcatSource使用介绍

    一.概述: 本节首先提供一个基于netcat的source+channel(memory)+sink(logger)的数据传输过程.然后剖析一下NetcatSource中的代码执行逻辑. 二.flum ...

  10. SPFA

    SPFA算法用来求单源最短路.可以处理任何有解的情况. 先建一个数组\(dist_x = 起点到x的最短路长度\),当\(x=起点\)时为0,当x和起点不通时为INF(本题中为\(2^31-1\)). ...