前言
webp格式
,谷歌開發的一種旨在加快圖片加載速度的圖片格式。圖片壓縮體積大約只有JPEG的2/3,并能節省大量的服務器寬帶資源和數據空間。是一種同時提供了有損壓縮與無損壓縮(可逆壓縮)的圖片檔案格式,衍生自影像編碼格式VP8,被認為是WebM多媒體格式的姊妹項目,是由Google以BSD授權條款釋出。VP8編解碼器的其中一個強大特性是幀內預測壓縮,或者說,視頻的每一幀都被壓縮,后續幀與幀之間的差異也會被壓縮。這就是WebP的由來:WebM文件里單個被壓縮的幀。更精確的說WebP的核心來則WebM。
webp
最初在2010年釋出,目標是減少檔案大小,達到和JPEG格式相同的圖片品質,希望能夠減少圖片檔在網路上的傳送時間。
原理
一、有損壓縮
1.宏塊(MacroBlocking)
圖片編碼第一個階段的任務是把圖片分割成不同的宏塊
。常見的就是包括一個‘16×16’的亮度像素塊和兩個‘8×8’的色度像素塊,如下圖:
亮度像素塊(luma)工作原理:指的是像素摳像,把一幅含有RGB通道的圖像轉換為單通道的黑白圖像,這幅黑白圖像就代表著這幅圖像的亮部和暗部區域。
色度像素塊(chroma)工作原理:指的是RGB空間,就是把 R、G、B 放到數學里面的立體坐標系上,然后可以把對應的RGB 表示的圖像放到這個坐標系上,就可以通過這個坐標系來觀察和分析整幅圖像的紅綠藍顏色分布了,因為引入了立體坐標系,所以可以使用研究立體幾何的方法來研究和處理圖像了。
2.預測
宏塊里每個4x4的子塊都有一個預測模型。(又名過濾)。在PNG里過濾用得非常多,它對每一行都做同樣的事,而WebP過濾的是每一塊。它是這樣處理的,在一個塊周圍定義兩組像素:有一行在它上面為A,在它左邊那一列為L。如圖:利用 A 和 L,編碼器會將他們放在一個4x4的測試像素塊填滿,并確定哪一個生成了最接近原始塊的值。這些用不同方法填滿的塊叫做"預測塊"。
WebP
編碼器四種幀內預測模式:
(1)Horiz prediction
(水平預測):將塊的每列使用左列L數據的副本進行填充。
(2)Vertical Prediction
(垂直預測):將塊的每行使用上列A數據的副本進行填充。
(3)DC Prediction
(DC 預測):將塊使用 A 上列的像素與 L 左列的像素的平均值作為宏塊唯一的值進行填充。
(4)True Motion
(TrueMotion 預測):除了行 A 和列 L 之外,用宏塊上方和左側的像素P、A(從P開始)中像素塊之間的水平差異以列 L 為基準拓展每一行。
3.處理 DCT
當圖片處理到此處時,還剩下小的殘差,通過 FDCT (正向離散余弦變換),讓變換后的數據低頻部分分布在數據塊左上方,而高頻部分集中于右下方實現更高效的壓縮。在DCT階段輸入的數據不是原始的數據塊本身,而是預測后的數據。WebP的預測階段相比JPG是最大的優勢,它減少了特殊顏色,使得在以后的處理階段能更有效的壓縮圖片數據。WebP只是比JPG所有處理過程多了一個預測模式,在數據壓縮方面就比JPG優秀很多。
4.有損壓縮圖片與 jpg 體積比較
總結:當WebP
將 JPG
壓縮到相當于原圖 90% 質量時,圖片體積減少了 50% 左右。當WebP
將 JPG
壓縮到相當于原圖 80% 質量時,圖片體積則減少了 60%~80%。
有損 WebP
壓縮性能優于JPG
的原因主要是其預測編碼技術先進,并且宏塊自適應量化也帶來了壓縮效率的提升。
二、無損壓縮
WebP
無損壓縮采用了預測變換、顏色變換、減去綠色變換、彩色緩存編碼、LZ77 反向參考等不同技術來處理圖像,之后對變換圖像數據和參數進行熵編碼。
1.預測變換
預測空間變換
通過利用相鄰像素的數據相關性減少熵[shāng]
。在預測變換中,對已解碼的像素預測當前像素值,并且僅對差值(實際預測)進行編碼。預測變換有 13 種不同的模式,使用較多的是左、上、左上以及右上的像素預測模式,其余為左、上、左上和右上組合的平均值預測模式。
2.顏色變換
借助顏色變換去除每個像素的 R,G 和 B 值。彩色變換時保持綠色(G)值原樣,根據綠色(G)值變換紅色(R)值,再根據綠色值轉換藍色(B)值,最后根據紅色(R)值進行轉換。
如果與預測變換的情況一樣,就需要將圖像劃分為宏塊,并且對于宏塊中的所有像素使用相同的變換模式。變換模式分為 3 種:green_to_red,green_to_blue和red_to_blue。
3.減去綠色變換
減去綠色變換
從每個像素的紅色、藍色值中減去綠色值。當此變換存在時,解碼器需要將綠色值添加到紅色和藍色。
4.彩色緩存編碼
無損 WebP 壓縮使用已經看到的圖像片段來重構新的像素。如果沒有找到對應的匹配值,可以使用本地調色板,同時本地調色板也會不斷更新最近使用的顏色。
5.無損壓縮圖片與PNG體積比較
總結:WebP 無損對 PNG 圖片的優化到達了 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`初始化的地方,判斷如果當前環境支持 webp就會加上`[SDWebImageWebPCoder sharedCoder]`
這個`SDWebImageWebPCoder `是內置的加載 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);//繪制圖片上下文,第一個參數創建BitmapContext的內存空間,第二個圖片的寬度,第三個圖片的高度,第四個內存中像素的每個組件的位數.例如,對于32位像素格式和RGB 顏色空間,應該將這個值設為8.,第五個是每一行在內存所占的比特數,第六個是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;
}
注:整個解碼功能的實現是依賴于 libwebp 庫。
4.WebView 使用
在以 Native 方式開發的 App 中也會大量使用的 UIWebView 來展示一些簡單頁面,然而 Safari 及 UIWebView 當前并不支持 WebP 格式。若是想在 UIWebView 中也把圖片顯示出來,一個解決思路就是:攔截替換。攔截 WebP 圖片然后轉換為 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的網絡請求。
(2)原理:攔截替換
方法1
:
A. 在網頁加載出后截取到HTML及內部的JS后,調用JS預先準備好的方法獲取需要轉碼的webP格式圖片下載地址(其實一個一個的遍歷也行).
B. 在App 本地開啟線程下載圖片,下載完成后,將圖片經過webP—> png—>Base64轉碼(因為實驗出直接用 png/jpg 的話 沒用)
C. 將 Base64及原圖片下載地址一一對應調用JS準備好的方法進行替換
D. 將下載后的圖片進行緩存,并進行管理
注:
A. 圖片在最終顯示成功前會顯示成?,此處為了用戶體驗應該采用占位圖片
B. 圖片顯示成功前應該保持網頁布局不調整,需要由 JS 預先設置好布局
C. 圖片在本地的緩存需要管理
代碼如下:
//在 `webView` 加載完 `HTML` 后,解析源碼,執行相應的 `JS` 方法
-(void)webViewDidFinishLoad:(UIWebView *)webView{
//獲取`HTML`代碼
NSString *lJs = @"document.documentElement.innerHTML";
NSString *str = [webView stringByEvaluatingJavaScriptFromString:lJs];
//執行約定好的方法,獲取需要下載的 webp 圖片
NSString *imgs = [self.webView stringByEvaluatingJavaScriptFromString:@"YongChe.getAllWebPImg();"];
NSArray *array = [NSJSONSerialization JSONObjectWithData:[imgs dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
//此處,做示范,只轉換第一個,將圖片下載下來,并且轉為 PNG 后,再轉成 Base64,傳給 JS 腳本執行
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) { //沒有回調,但是圖片下載完成了
} 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
可以攔截的網絡請求包括NSURLSession,NSURLConnection以及UIWebVIew。
步驟:
注冊—>攔截—>轉發—>回調—>結束
參考MagicWebViewWebP.framework的實現方式
注:NSURLProtocol 的全局性質,影響范圍大,這種方式存在潛在的風險,需要嚴格的過濾和限制 WebP 請求的攔截。NSURLProtocol 作用的疊加性質,也無法保證與其它第三方代碼的兼容。每次只能只有一個protocol進行處理,如果有多個自定義protocol,系統將采取你registerClass的倒序進行調用,一旦你需要對這個請求進行處理,那么接下來的所有相關操作都需要這個protocol進行管理。
參考文章
app圖片優化-webp格式圖片原理及在Android、IOS中的應用
MagicWebViewWebP
WebP 極限壓縮及ios實現
都說 WebP 厲害,究竟厲害在哪里?