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

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

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

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

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

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

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

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

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

3.大圖拆分為小圖,大小也會(huì)減少。

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

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

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

二、webp格式的原理

2.1 來(lái)源

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

2.1 原理分析 —— 有損模式

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

*宏塊(MacroBlocking)

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


image

*預(yù)測(cè)

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


image

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

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

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

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

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

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

image

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

*JPGify it

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

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

DCT矩陣后量化

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


image

這有主要有兩點(diǎn)不同:

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

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

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

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

三、webp在Android中支持的情況

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

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

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

Android 4.0之前的兼容可以通過官方的源碼庫(kù)(需要手動(dòng)轉(zhuǎn)為so文件),也可以使用前人封裝好的庫(kù):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以上簡(jiǎn)單的使用:

直接將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);

}

}

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

四、webp在IOS中支持的情況

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

1.IOS本地中使用webp圖片

本地使用webp圖片,可以使用SDWebImage里面有個(gè)webP 框架,可以將webp-->NSData-->UIImage最后變?yōu)榭勺R(shí)別的圖片格式直接給控件調(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)頁(yè)上如果用的是 webP 圖片,iOS 直接解析的話,會(huì)顯示?圖片。

思路 1:

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

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

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

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

注意點(diǎn):

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

B. 圖片顯示成功前應(yīng)該保持網(wǎng)頁(yè)布局不調(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)換第一個(gè),將圖片下載下來(lái),并且轉(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);

}

});

}];

//這一步,將這個(gè) 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ù)量,加快請(qǐng)求整體速度,現(xiàn)在的問題就在于webView不識(shí)別webP這種格式而已,再想想 iOS內(nèi)的所有網(wǎng)絡(luò)請(qǐng)求/響應(yīng)我們都可以控制,那我們獲取到數(shù)據(jù)時(shí),轉(zhuǎn)化成它識(shí)別的給它就可以了,而可以幫我們?cè)谀玫綌?shù)據(jù)時(shí)進(jìn)行某些操作,操作完以后又正常進(jìn)行網(wǎng)絡(luò)流程的。這就需要用到NSURLProtocol。

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

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

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

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

*返回?cái)?shù)據(jù)攔截

*干你想干的。。。

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


image

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


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請(qǐng)求的圖片

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];

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

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];

}

注意點(diǎn):

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

一定要注意標(biāo)記請(qǐng)求,不然你會(huì)無(wú)限的循環(huán)下去。。。因?yàn)橐坏┠阈枰幚磉@個(gè)請(qǐng)求,那么系統(tǒng)會(huì)創(chuàng)建你這個(gè)protocol的實(shí)例,然后你自己又開啟了connection進(jìn)行請(qǐng)求的話,又會(huì)觸發(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;這兩個(gè)方法進(jìn)行標(biāo)記和區(qū)分。

五、總結(jié)

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

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

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

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

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