app圖片優(yōu)化-webp格式圖片原理及在Android、IOS中的應(yīng)用

參考:http://www.itdecent.cn/p/ff8444fbc773

http://www.itdecent.cn/p/555859783f63

http://blog.csdn.net/lmj623565791/article/details/53240600

http://www.itdecent.cn/p/ed7562a34af1

http://www.itdecent.cn/p/0244e431fb3c

一、app體積優(yōu)化的幾種方式

1.使用TinyPng來優(yōu)化png格式圖片大小,就是使用方法壓縮png圖片。

2.不包含透明像素的圖片,改為JPEG格式,這樣可以減少圖片的大小。

3.大圖拆分為小圖,大小也會減少。

4.使用九宮格圖片,也就是.9.png的圖片,android和ios都適用,對于比較大的圖片和使用范圍,可是形成無損拉伸效果。

5.使用IconFont,通過字體文件來構(gòu)建純色圖,不占圖片體積,通過加載字庫文件,來顯示純色圖片。阿里的字庫IOS接入第三發(fā)庫:github上的開源庫IconFont和阿里的FontAwesomeKit(這個是個很重要的趨勢,以后將特意寫篇文章來說IconFont)

6.本章將講解的webp格式圖片。谷歌2011年發(fā)布WebP,一直對它做了很多升級和改變,2014年WebP開始支持動圖。webp格式是圖片壓縮更加極致,比常用的jpg png格式小24%-35%,webp格式已經(jīng)支持大部分瀏覽器,Android 原生。

二、webp格式的原理

2.1 來源

WebP文件格式來源于VP8視頻編解碼(你可能更知道WebM)。VP8編解碼器的其中一個強大特性是幀內(nèi)預(yù)測壓縮,或者說,視頻的每一幀都被壓縮,后續(xù)幀與幀之間的差異也會被壓縮。這就是WebP的由來:WebM文件里單個被壓縮的幀。更精確的說WebP的核心來則WebM。

2.1 原理分析 —— 有損模式

WebP的有損模式是建立在與一張靜止的JPG競爭的基礎(chǔ)上,因此,你會注意到在對文件處理上有一些驚人相識。

*宏塊(MacroBlocking)

編碼器的第一個階段是將圖片分割成不同"宏塊"。典型的宏塊包括一個16x16的亮度像素塊,和兩個8x8的色度像素塊。這個階段非常像JPEG格式里轉(zhuǎn)換顏色空間,對色度通道降低采樣,以及細(xì)分圖片。


image

*預(yù)測

宏塊里每個4x4的子塊都有一個預(yù)測模型。(又名過濾)。在PNG里過濾用得非常多,它對每一行都做同樣的事,而WebP過濾的是每一塊。它是這樣處理的,在一個塊周圍定義兩組像素:有一行在它上面為A,在它左邊那一列為L。


image

利用A和L,編碼器會將它們放在一個4x4的測試像素塊填滿,并確定哪一個生成了最接近原始塊的值。這些用不同方法填滿的塊叫做"預(yù)測塊"。

Horiz prediction(水平預(yù)測)——將塊的每列使用左列(L)數(shù)據(jù)的副本進(jìn)行填充。

Vertical Prediction(垂直預(yù)測)——將塊的每行使用上列(A)數(shù)據(jù)的副本進(jìn)行填充。

DC Prediction(DC 預(yù)測)——將塊使用 A 上列的像素與 L 左列的像素的平均值進(jìn)行填充。

True Motion (TrueMotion 預(yù)測)——一種超級先進(jìn)的模式,我暫時不講。

值得注意的是,4x4的亮度塊還有另外6種模式,但你現(xiàn)在只需知道這些就好;)

image

基本流程是我們找到這個快最佳的預(yù)測塊,并導(dǎo)出過濾結(jié)果(剩余誤差),然后送到下個階段。

*JPGify it

WebP編碼的最后階段看起來非常像我們的老朋友JPG:

對塊里剩余的值執(zhí)行DCT過濾

DCT矩陣后量化

轉(zhuǎn)成量化矩陣后重新排序,然后送到一個靜態(tài)壓縮器里。


image

這有主要有兩點不同:

在DCT階段輸入的數(shù)據(jù)不是原始的數(shù)據(jù)塊本身,而是預(yù)測后的數(shù)據(jù)

WebP用得靜態(tài)壓縮器是算術(shù)壓縮器,它和JPG用的霍夫曼編碼器類似。

從最后的結(jié)果看WebP感覺有點像加強版的JPG。WebP的預(yù)測階段相比JPG是最大的優(yōu)勢,它減少了特殊顏色,使得在以后的處理階段能更有效的壓縮圖片數(shù)據(jù)。

WebP只是比JPG所有處理過程多了一個預(yù)測模式,在數(shù)據(jù)壓縮方面就把JPG干倒。

三、webp在Android中支持的情況

Webp在app中的使用一般包括兩個方面:

1.對與服務(wù)端交互過程中使用webp圖片

從Android 4.0 開始就對webp圖片進(jìn)行支持,直接使用就可以了。但是要注意:4.2.1+對webp格式的decode、encode是完全支持的(包含半透明的webp圖);對于4.0+ 到 4.2.1 ,只支持完全不透明的decode、encode的webp圖;4.0 以下,應(yīng)該是默認(rèn)不支持webp了。

Android 4.0之前的兼容可以通過官方的源碼庫(需要手動轉(zhuǎn)為so文件),也可以使用前人封裝好的庫:webp-android-backport和webp-android。

示例代碼:


InputStreamis= getAssets().open("weixin.webp");

Bitmapbitmap=null;if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {

bitmap= WebPDecoder.getInstance().decodeWebP(streamToBytes(is));

}else{

bitmap= BitmapFactory.decodeStream(is);

}

imageView.setImageBitmap(bitmap);privatestaticbyte[]streamToBytes(InputStreamis) {

ByteArrayOutputStream os =newByteArrayOutputStream(1024);byte[] buffer =newbyte[1024];intlen;try{while((len =is.read(buffer)) >=0) {

os.write(buffer,0, len);

}

}catch(java.io.IOException e) {

}returnos.toByteArray();

}

2.應(yīng)用中的資源文件

4.0以上簡單的使用:

直接將png轉(zhuǎn)化為webp,放到res/drawable目錄

image

3.在4.0以下支持


public classMainActivityextendsAppCompatActivity {

private static final int[]LL=new int[]

{//

android.R.attr.src,//

};

@Override

protected voidonCreate(Bundle savedInstanceState) {

if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){

LayoutInflaterCompat.setFactory(LayoutInflater.from(this),newLayoutInflaterFactory() {

@Override

publicView onCreateView(View parent, String name, Context context, AttributeSet attrs) {

AppCompatDelegate delegate = getDelegate();

View view = delegate.createView(parent, name, context, attrs);

if(viewinstanceofImageView) {

ImageView imageView = (ImageView) view;

TypedArray a = context.obtainStyledAttributes(attrs,LL);

intwebpSourceResourceID = a.getResourceId(0,0);

if(webpSourceResourceID ==0) {

returnview;

}

TypedValue value = new TypedValue();getResources().getValue(webpSourceResourceID, value, true);String resname = value.string.toString().substring(13,

value.string.toString().length());if (resname.endsWith(".webp")) {

InputStream rawImageStream = getResources().openRawResource(webpSourceResourceID);

byte[] data = streamToBytes(rawImageStream);

finalBitmap webpBitmap = WebPDecoder.getInstance().decodeWebP(data);

imageView.setImageBitmap(webpBitmap);

}

a.recycle();

}

returnview;

}

});

}

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

}

寫一個基類,所有的Activity都繼承此基類。大致邏輯為:對于4.2以下的版本,我們設(shè)置一個LayoutInflaterFactory,當(dāng)創(chuàng)建ImageView的時候,因為AppCompatActivity,ImageView的創(chuàng)建是由上述代碼中的delegate指向的對象完成的,我們通過傳入attrs,取出用戶聲明的src屬性,經(jīng)過一系列操作轉(zhuǎn)化為bitmap,最好設(shè)置到創(chuàng)建好的ImageView上。

四、webp在IOS中支持的情況

這里先說的一點xcode目前無法像Android Studio一樣支持webp作為資源,所以IOS目前無法將webp作為資源直接在IDE中支持,只能作為網(wǎng)絡(luò)圖片和H5圖片顯示在IOS程序中。

1.IOS本地中使用webp圖片

本地使用webp圖片,可以使用SDWebImage里面有個webP 框架,可以將webp-->NSData-->UIImage最后變?yōu)榭勺R別的圖片格式直接給控件調(diào)用。

但是需要額外需要‘webp’包

$pod 'SDWebImage/WebP'


image

#ifdef SD_WEBP

#import

// Fix for issue #416 Undefined symbols for architecture armv7 since WebP introduction when deploying to device

void WebPInitPremultiplyNEON(void);

void WebPInitUpsamplersNEON(void);

void VP8DspInitNEON(void);

@interface UIImage (WebP)

+ (UIImage *)sd_imageWithWebPData:(NSData *)data;

@end

#endif

2.IOS 網(wǎng)絡(luò)或webview使用webp格式圖片

webView 等網(wǎng)頁上如果用的是 webP 圖片,iOS 直接解析的話,會顯示?圖片。

思路 1:

A. 在網(wǎng)頁加載出后截取到HTML及內(nèi)部的JS后,調(diào)用JS預(yù)先準(zhǔn)備好的方法獲取需要轉(zhuǎn)碼的webP格式圖片下載地址(其實一個一個的遍歷也行).

B. 在App 本地開啟線程下載圖片,下載完成后,將圖片轉(zhuǎn)碼由 webP—> png—>Base64(因為實驗出直接用 png/jpg 的話 沒用)

C. 將 Base64及原圖片下載地址一一對應(yīng)調(diào)用JS準(zhǔn)備好的方法進(jìn)行替換

D. 將下載后的圖片進(jìn)行緩存,并進(jìn)行管理

注意點:

A. 圖片在最終顯示成功前會顯示成?,此處為了用戶體驗應(yīng)該采用占位圖片

B. 圖片顯示成功前應(yīng)該保持網(wǎng)頁布局不調(diào)整,需要由 JS 預(yù)先設(shè)置好布局

C. 圖片在本地的緩存需要管理


//在 `webView` 加載完 `HTML` 后,解析源碼,執(zhí)行相應(yīng)的 `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 *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

{

//    [self sd_cancelCurrentImageLoad];

//    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

if (url) {

//        __weak __typeof(self)wself = self;

id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

//            if (!wself) return;

dispatch_main_sync_safe(^{

//                if (!wself) return;

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 之前的下載操作全部取消,然后將這次的操作放進(jìn)去

//        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

} 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:

采用webP的目的就是減少網(wǎng)絡(luò)傳輸數(shù)據(jù)量,加快請求整體速度,現(xiàn)在的問題就在于webView不識別webP這種格式而已,再想想 iOS內(nèi)的所有網(wǎng)絡(luò)請求/響應(yīng)我們都可以控制,那我們獲取到數(shù)據(jù)時,轉(zhuǎn)化成它識別的給它就可以了,而可以幫我們在拿到數(shù)據(jù)時進(jìn)行某些操作,操作完以后又正常進(jìn)行網(wǎng)絡(luò)流程的。這就需要用到NSURLProtocol。

NSURLProtocol是 iOS里面的URL Loading System的一部分,但是從它的名字來看,你絕對不會想到它會是一個對象,可是它偏偏是個對象。。。而且還是抽象對象(可是OC里面沒有抽象這一說)。平常我們做網(wǎng)絡(luò)相關(guān)的東西基本很少碰它,但是它的功能卻強大得要死。

*可以攔截UIWebView,基于系統(tǒng)的NSUIConnection或者NSUISession進(jìn)行封裝的網(wǎng)絡(luò)請求。

*忽略網(wǎng)絡(luò)請求,直接返回自定義的Response

*修改request(請求地址,認(rèn)證信息等等)

*返回數(shù)據(jù)攔截

*干你想干的。。。

對URL Loading System不清楚的,可以看看下面這張圖,看看里面有哪些類:


image

思路很簡單,就是攔截請求URL帶.png.jpg.gif的請求,首先去緩存里面取,有的話直接返回,沒有的去請求,并保存本地。


static NSString * const hasInitKey = @"JYCustomWebViewProtocolKey";

@interface JYCustomWebViewProtocol ()

@property (nonatomic, strong) NSMutableData *responseData;

@property (nonatomic, strong) NSURLConnection *connection;

@end

@implementation JYCustomWebViewProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

if ([request.URL.scheme isEqualToString:@"http"]) {

NSString *str = request.URL.path;

//只處理http請求的圖片

if (([str hasSuffix:@".png"] || [str hasSuffix:@".jpg"] || [str hasSuffix:@".gif"])

&& ![NSURLProtocol propertyForKey:hasInitKey inRequest:request]) {

return YES;

}

}

return NO;

}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {

NSMutableURLRequest *mutableReqeust = [request mutableCopy];

//這邊可用干你想干的事情。。更改地址,提取里面的請求內(nèi)容,或者設(shè)置里面的請求頭。。

return mutableReqeust;

}

- (void)startLoading

{

NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];

//做下標(biāo)記,防止遞歸調(diào)用

[NSURLProtocol setProperty:@YES forKey:hasInitKey inRequest:mutableReqeust];

//查看本地是否已經(jīng)緩存了圖片

NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];

NSData *data = [[SDImageCache sharedImageCache] diskImageDataBySearchingAllPathsForKey:key];

if (data) {

NSURLResponse *response = [[NSURLResponse alloc] initWithURL:mutableReqeust.URL

MIMEType:[NSData sd_contentTypeForImageData:data]

expectedContentLength:data.length

textEncodingName:nil];

[self.client URLProtocol:self

didReceiveResponse:response

cacheStoragePolicy:NSURLCacheStorageNotAllowed];

[self.client URLProtocol:self didLoadData:data];

[self.client URLProtocolDidFinishLoading:self];

}

else {

self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];

}

}

- (void)stopLoading

{

[self.connection cancel];

}

#pragma mark- NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

[self.client URLProtocol:self didFailWithError:error];

}

#pragma mark - NSURLConnectionDataDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

{

self.responseData = [[NSMutableData alloc] init];

[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];

}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

[self.responseData appendData:data];

[self.client URLProtocol:self didLoadData:data];

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

{

UIImage *cacheImage = [UIImage sd_imageWithData:self.responseData];

//利用SDWebImage提供的緩存進(jìn)行保存圖片

[[SDImageCache sharedImageCache] storeImage:cacheImage

recalculateFromImage:NO

imageData:self.responseData

forKey:[[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]

toDisk:YES];

[self.client URLProtocolDidFinishLoading:self];

}

注意點:

每次只能只有一個protocol進(jìn)行處理,如果有多個自定義protocol,系統(tǒng)將采取你registerClass的倒序進(jìn)行調(diào)用,一旦你需要對這個請求進(jìn)行處理,那么接下來的所有相關(guān)操作都需要這個protocol進(jìn)行管理。

一定要注意標(biāo)記請求,不然你會無限的循環(huán)下去。。。因為一旦你需要處理這個請求,那么系統(tǒng)會創(chuàng)建你這個protocol的實例,然后你自己又開啟了connection進(jìn)行請求的話,又會觸發(fā)URL Loading system的回調(diào)。系統(tǒng)給我們提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;這兩個方法進(jìn)行標(biāo)記和區(qū)分。

五、總結(jié)

工具:jpeg/png可以轉(zhuǎn)換成為webp格式,官方轉(zhuǎn)換工具:webp轉(zhuǎn)換工具

在網(wǎng)頁端已經(jīng)大規(guī)模使用webp格式進(jìn)行傳輸、顯示了,但是在app端還有很多人在用png和jpg,webp是一個很好的可以減小app大小的圖片格式,在Android 端4.0之后才支持作為資源存在,在ios端無法作為資源存在,但是無論Android 和IOS端在webview 和網(wǎng)絡(luò)途徑上有方式來實現(xiàn)webp格式解析,對于網(wǎng)絡(luò)大圖需求多的app,webp格式是很好的,建議所有Android app換webp格式,IOS app看需求使用網(wǎng)絡(luò)顯示webp格式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容