前言
webp格式
,谷歌開發(fā)的一種旨在加快圖片加載速度的圖片格式潘拨。圖片壓縮體積大約只有JPEG的2/3喻括,并能節(jié)省大量的服務器寬帶資源和數(shù)據(jù)空間。是一種同時提供了有損壓縮與無損壓縮(可逆壓縮)的圖片檔案格式,衍生自影像編碼格式VP8矫膨,被認為是WebM多媒體格式的姊妹項目狱杰,是由Google以BSD授權(quán)條款釋出瘦材。VP8編解碼器的其中一個強大特性是幀內(nèi)預測壓縮,或者說仿畸,視頻的每一幀都被壓縮食棕,后續(xù)幀與幀之間的差異也會被壓縮朗和。這就是WebP的由來:WebM文件里單個被壓縮的幀。更精確的說WebP的核心來則WebM簿晓。
webp
最初在2010年釋出眶拉,目標是減少檔案大小,達到和JPEG格式相同的圖片品質(zhì)憔儿,希望能夠減少圖片檔在網(wǎng)路上的傳送時間镀层。
原理
一、有損壓縮
1.宏塊(MacroBlocking)
圖片編碼第一個階段的任務是把圖片分割成不同的宏塊
皿曲。常見的就是包括一個‘16×16’的亮度像素塊和兩個‘8×8’的色度像素塊唱逢,如下圖:
亮度像素塊(luma)工作原理:指的是像素摳像,把一幅含有RGB通道的圖像轉(zhuǎn)換為單通道的黑白圖像屋休,這幅黑白圖像就代表著這幅圖像的亮部和暗部區(qū)域蹂安。
色度像素塊(chroma)工作原理:指的是RGB空間,就是把 R焙矛、G垛吗、B 放到數(shù)學里面的立體坐標系上,然后可以把對應的RGB 表示的圖像放到這個坐標系上叠艳,就可以通過這個坐標系來觀察和分析整幅圖像的紅綠藍顏色分布了奶陈,因為引入了立體坐標系,所以可以使用研究立體幾何的方法來研究和處理圖像了附较。
2.預測
宏塊里每個4x4的子塊都有一個預測模型吃粒。(又名過濾)。在PNG里過濾用得非常多拒课,它對每一行都做同樣的事徐勃,而WebP過濾的是每一塊。它是這樣處理的早像,在一個塊周圍定義兩組像素:有一行在它上面為A僻肖,在它左邊那一列為L。如圖:利用 A 和 L卢鹦,編碼器會將他們放在一個4x4的測試像素塊填滿臀脏,并確定哪一個生成了最接近原始塊的值。這些用不同方法填滿的塊叫做"預測塊"冀自。
WebP
編碼器四種幀內(nèi)預測模式:
(1)Horiz prediction
(水平預測):將塊的每列使用左列L數(shù)據(jù)的副本進行填充揉稚。
(2)Vertical Prediction
(垂直預測):將塊的每行使用上列A數(shù)據(jù)的副本進行填充。
(3)DC Prediction
(DC 預測):將塊使用 A 上列的像素與 L 左列的像素的平均值作為宏塊唯一的值進行填充凡纳。
(4)True Motion
(TrueMotion 預測):除了行 A 和列 L 之外窃植,用宏塊上方和左側(cè)的像素P、A(從P開始)中像素塊之間的水平差異以列 L 為基準拓展每一行荐糜。
3.處理 DCT
當圖片處理到此處時巷怜,還剩下小的殘差葛超,通過 FDCT (正向離散余弦變換),讓變換后的數(shù)據(jù)低頻部分分布在數(shù)據(jù)塊左上方延塑,而高頻部分集中于右下方實現(xiàn)更高效的壓縮绣张。在DCT階段輸入的數(shù)據(jù)不是原始的數(shù)據(jù)塊本身,而是預測后的數(shù)據(jù)关带。WebP的預測階段相比JPG是最大的優(yōu)勢侥涵,它減少了特殊顏色,使得在以后的處理階段能更有效的壓縮圖片數(shù)據(jù)宋雏。WebP只是比JPG所有處理過程多了一個預測模式芜飘,在數(shù)據(jù)壓縮方面就比JPG優(yōu)秀很多。
4.有損壓縮圖片與 jpg 體積比較
總結(jié):當WebP
將 JPG
壓縮到相當于原圖 90% 質(zhì)量時磨总,圖片體積減少了 50% 左右嗦明。當WebP
將 JPG
壓縮到相當于原圖 80% 質(zhì)量時,圖片體積則減少了 60%~80%蚪燕。
有損 WebP
壓縮性能優(yōu)于JPG
的原因主要是其預測編碼技術(shù)先進娶牌,并且宏塊自適應量化也帶來了壓縮效率的提升。
二馆纳、無損壓縮
WebP
無損壓縮采用了預測變換诗良、顏色變換、減去綠色變換鲁驶、彩色緩存編碼鉴裹、LZ77 反向參考等不同技術(shù)來處理圖像,之后對變換圖像數(shù)據(jù)和參數(shù)進行熵編碼灵嫌。
1.預測變換
預測空間變換
通過利用相鄰像素的數(shù)據(jù)相關(guān)性減少熵[shāng]
壹罚。在預測變換中葛作,對已解碼的像素預測當前像素值寿羞,并且僅對差值(實際預測)進行編碼。預測變換有 13 種不同的模式赂蠢,使用較多的是左绪穆、上、左上以及右上的像素預測模式虱岂,其余為左玖院、上、左上和右上組合的平均值預測模式第岖。
2.顏色變換
借助顏色變換去除每個像素的 R难菌,G 和 B 值。彩色變換時保持綠色(G)值原樣蔑滓,根據(jù)綠色(G)值變換紅色(R)值郊酒,再根據(jù)綠色值轉(zhuǎn)換藍色(B)值遇绞,最后根據(jù)紅色(R)值進行轉(zhuǎn)換。
如果與預測變換的情況一樣燎窘,就需要將圖像劃分為宏塊摹闽,并且對于宏塊中的所有像素使用相同的變換模式。變換模式分為 3 種:green_to_red褐健,green_to_blue和red_to_blue付鹿。
3.減去綠色變換
減去綠色變換
從每個像素的紅色、藍色值中減去綠色值蚜迅。當此變換存在時舵匾,解碼器需要將綠色值添加到紅色和藍色。
4.彩色緩存編碼
無損 WebP 壓縮使用已經(jīng)看到的圖像片段來重構(gòu)新的像素谁不。如果沒有找到對應的匹配值纽匙,可以使用本地調(diào)色板,同時本地調(diào)色板也會不斷更新最近使用的顏色拍谐。
5.無損壓縮圖片與PNG體積比較
總結(jié):WebP 無損對 PNG 圖片的優(yōu)化到達了 20%~40% 烛缔。
三、WebP 與主流的圖片格式功能對比
四轩拨、iOS 里面原生控件對 webp 的支持
1.SDWebImage 可以直接支持
$ pod 'SDWebImage/WebP'
2.遇到的問題及解決辦法
問題:
查看SDWebImage
的 .podspec
pod 配置文件践瓷,可知道 SDWebImage
支持 webp
格式圖片是依賴于其 Webp
子庫,而子庫進一步依賴于 libwebp
庫亡蓉。libwebp 是來自谷歌的被墻的晕翠,源地址為 https://chromium.googlesource.com/webm/libwebp
解決辦法:
添加終端翻墻:
~ git config --global http.proxy 'socks5://127.0.0.1:1086' `socks5://127.0.0.1:1086要查看你翻墻軟件里面的配置`
~ git config --global https.proxy 'socks5://127.0.0.1:1086'
然后 pod install
另外提供刪除終端翻墻
~ git config --global --unset http.proxy
~ git config --global --unset https.proxy
3.原生控件使用
(1)使用方式
直接使用 SDWebImage
的sd_setImageWithURL
系列方法即可加載 webp 圖片。
(2)原理
pod之后看SDWebImageCodersManager
和SDWebImageWebPCoder
SDWebImageCodersManager
類里面
初始化:
- (instancetype)init {
if (self = [super init]) {
// initialize with default coders
NSMutableArray<id<SDWebImageCoder>> *mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder]] mutableCopy];
#ifdef SD_WEBP
[mutableCoders addObject:[SDWebImageWebPCoder sharedCoder]];
#endif
_coders = [mutableCoders copy];
_codersLock = dispatch_semaphore_create(1);
}
return self;
}
注:在`SDWebImageCodersManager`初始化的地方砍濒,判斷如果當前環(huán)境支持 webp就會加上`[SDWebImageWebPCoder sharedCoder]`
這個`SDWebImageWebPCoder `是內(nèi)置的加載 webp 圖片或者webp動畫的編碼器
把 data 解碼成 image
- (UIImage *)decodedImageWithData:(NSData *)data {
LOCK(self.codersLock);
NSArray<id<SDWebImageCoder>> *coders = self.coders;
UNLOCK(self.codersLock);
for (id<SDWebImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canDecodeFromData:data]) {
return [coder decodedImageWithData:data];
}
}
return nil;
}
注:判斷如果 webp 的 coder 可以解碼此 data 的話就進入 code 里面進行解碼
SDWebImageWebPCoder
類里面
解碼成 image
- (UIImage *)decodedImageWithData:(NSData *)data {
if (!data) {
return nil;
}
WebPData webpData;
WebPDataInit(&webpData);//初始化一個有缺省值的 webp_data
webpData.bytes = data.bytes;
webpData.size = data.length;
WebPDemuxer *demuxer = WebPDemux(&webpData);//解析“data”給出的完整WebP文件淋肾,成功解析后返回WebPDemuxer對象,否則返回NULL爸邢。WebPDemuxer包含圖的寬高樊卓,colorSpace(解碼之后圖片的像素格式)等屬性
if (!demuxer) {
return nil;
}
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);//獲取 webpFeatureFlags 的位操作組合
CGColorSpaceRef colorSpace = [self sd_colorSpaceWithDemuxer:demuxer];//獲取解碼之后圖片的像素格式
if (!(flags & ANIMATION_FLAG)) {
// for static single webp image
UIImage *staticImage = [self sd_rawWebpImageWithData:webpData colorSpace:colorSpace];
WebPDemuxDelete(demuxer);
CGColorSpaceRelease(colorSpace);
staticImage.sd_imageFormat = SDImageFormatWebP;
return staticImage;
}
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);//WEBP_FF_LOOP_COUNT ->用于動畫文件
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
CGBitmapInfo bitmapInfo;//指定bitmap是否包含alpha通道,像素中alpha通道的相對位置杠河,像素組件是整形還是浮點型等信息的字符串碌尔。
// `CGBitmapContextCreate` does not support RGB888 on iOS. Where `CGImageCreate` supports.
if (!(flags & ALPHA_FLAG)) {
// RGBX8888
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
} else {
// RGBA8888
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
}
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);//繪制圖片上下文,第一個參數(shù)創(chuàng)建BitmapContext的內(nèi)存空間券敌,第二個圖片的寬度唾戚,第三個圖片的高度,第四個內(nèi)存中像素的每個組件的位數(shù).例如待诅,對于32位像素格式和RGB 顏色空間叹坦,應該將這個值設為8.,第五個是每一行在內(nèi)存所占的比特數(shù)卑雁,第六個是colorSpace
if (!canvas) {
WebPDemuxDelete(demuxer);
CGColorSpaceRelease(colorSpace);
return nil;
}
// for animated webp image
WebPIterator iter;
if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demuxer);
CGContextRelease(canvas);
CGColorSpaceRelease(colorSpace);
return nil;
}
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
do {
@autoreleasepool {
UIImage *image = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace];
if (!image) {
continue;
}
int duration = iter.duration;
if (duration <= 10) {
// WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms
// Some animated WebP images also created without duration, we should keep compatibility
duration = 100;
}
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration / 1000.f];
[frames addObject:frame];
}
} while (WebPDemuxNextFrame(&iter));
WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demuxer);
CGContextRelease(canvas);
CGColorSpaceRelease(colorSpace);
UIImage *animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
animatedImage.sd_imageFormat = SDImageFormatWebP;
return animatedImage;
}
注:整個解碼功能的實現(xiàn)是依賴于 libwebp 庫募书。
4.WebView 使用
在以 Native 方式開發(fā)的 App 中也會大量使用的 UIWebView 來展示一些簡單頁面轧钓,然而 Safari 及 UIWebView 當前并不支持 WebP 格式。若是想在 UIWebView 中也把圖片顯示出來锐膜,一個解決思路就是:攔截替換毕箍。攔截 WebP 圖片然后轉(zhuǎn)換為 jpg 或者 png 再交給 UIWebView 進行渲染和展示。
(1)使用方式:
使用MagicWebViewWebP.framework導入到項目中道盏。
引用頭文件:#import <MagicWebViewWebP/MagicWebViewWebPManager.h>
在 webview 加載之前而柑,注冊MagicURLProtocol
_web = [[UIWebView alloc]initWithFrame:CGRectMake(0, 200, ScreenWidth, 300)];
[self.view addSubview:_web];
[[MagicWebViewWebPManager shareManager] registerMagicURLProtocolWebView:_web];
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURLURLWithString:@"http://isparta.github.io/compare-webp/index.html#12345"]];
[_web loadRequest:req];
dealloc中銷毀MagicURLProtocol
// 銷毀
-(void)dealloc{
[[MagicWebViewWebPManager shareManager] unregisterMagicURLProtocolWebView: _web];
}
注:若有特殊需求,需要在web頁面退出時銷毀MagicURLProtocol荷逞,否則會攔截整個app的網(wǎng)絡請求媒咳。
(2)原理:攔截替換
方法1
:
A. 在網(wǎng)頁加載出后截取到HTML及內(nèi)部的JS后,調(diào)用JS預先準備好的方法獲取需要轉(zhuǎn)碼的webP格式圖片下載地址(其實一個一個的遍歷也行).
B. 在App 本地開啟線程下載圖片,下載完成后,將圖片經(jīng)過webP—> png—>Base64轉(zhuǎn)碼(因為實驗出直接用 png/jpg 的話 沒用)
C. 將 Base64及原圖片下載地址一一對應調(diào)用JS準備好的方法進行替換
D. 將下載后的圖片進行緩存,并進行管理
注:
A. 圖片在最終顯示成功前會顯示成?,此處為了用戶體驗應該采用占位圖片
B. 圖片顯示成功前應該保持網(wǎng)頁布局不調(diào)整,需要由 JS 預先設置好布局
C. 圖片在本地的緩存需要管理
代碼如下:
//在 `webView` 加載完 `HTML` 后,解析源碼,執(zhí)行相應的 `JS` 方法
-(void)webViewDidFinishLoad:(UIWebView *)webView{
//獲取`HTML`代碼
NSString *lJs = @"document.documentElement.innerHTML";
NSString *str = [webView stringByEvaluatingJavaScriptFromString:lJs];
//執(zhí)行約定好的方法,獲取需要下載的 webp 圖片
NSString *imgs = [self.webView stringByEvaluatingJavaScriptFromString:@"YongChe.getAllWebPImg();"];
NSArray *array = [NSJSONSerialization JSONObjectWithData:[imgs dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
//此處,做示范,只轉(zhuǎn)換第一個,將圖片下載下來,并且轉(zhuǎn)為 PNG 后,再轉(zhuǎn)成 Base64,傳給 JS 腳本執(zhí)行
NSString *imgUrl = array.firstObject;
__weak typeof (self) weakSelf = self;
[SDWebImageCustomeDownLoad downloadWithURL:[NSURL URLWithString:imgUrl] progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSString *imgBase = [UIImagePNGRepresentation(image) base64EncodedStringWithOptions:0];
NSString *base = [NSString stringWithFormat:@"data:image/png;base64,%@",imgBase];
NSString *js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','%@')",imageURL,base];
[weakSelf.webView stringByEvaluatingJavaScriptFromString:js];
}];
}
+ (void)downloadWithURL:(NSURL *)url progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
if (url) {
id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
dispatch_main_sync_safe(^{
if (image && completedBlock){
completedBlock(image, error, cacheType, url);
return;
}else if (image) { //沒有回調(diào),但是圖片下載完成了
} else { //image 下載失敗
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//這一步,將這個 View 之前的下載操作全部取消,然后將這次的操作放進去
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
方法2
(推薦):
NSURLProtocol
可以攔截的網(wǎng)絡請求包括NSURLSession,NSURLConnection以及UIWebVIew种远。
步驟:
注冊—>攔截—>轉(zhuǎn)發(fā)—>回調(diào)—>結(jié)束
參考MagicWebViewWebP.framework的實現(xiàn)方式
注:NSURLProtocol 的全局性質(zhì)涩澡,影響范圍大,這種方式存在潛在的風險坠敷,需要嚴格的過濾和限制 WebP 請求的攔截妙同。NSURLProtocol 作用的疊加性質(zhì),也無法保證與其它第三方代碼的兼容膝迎。每次只能只有一個protocol進行處理粥帚,如果有多個自定義protocol,系統(tǒng)將采取你registerClass的倒序進行調(diào)用限次,一旦你需要對這個請求進行處理芒涡,那么接下來的所有相關(guān)操作都需要這個protocol進行管理。
參考文章
app圖片優(yōu)化-webp格式圖片原理及在Android卖漫、IOS中的應用
MagicWebViewWebP
WebP 極限壓縮及ios實現(xiàn)
都說 WebP 厲害费尽,究竟厲害在哪里?