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並返回
題外話: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:`方法來直接創建出一個可正常顯示的圖片。