淺談android中載入高清大圖及圖片壓縮方式(二)
這一講就是本系列的第二篇,一起來聊下關於android中載入高清大圖的問題,我們都知道如果我們直接載入原圖的話,一個是非常慢,需要等待一定時間,如果沒有在一定的時間內給使用者響應的話,將會極大影響使用者的體驗。另一個是如果你的手機記憶體小的話,可能會直接崩潰。這也就是直接載入高清原圖問題。遇到這些問題很容易想到的一點就是圖片壓縮,本篇文章也就是講述圖片壓縮方式來實現載入高清大圖的效果。但是現在問題就來了,通過上篇部落格我們知道,手機的解析度有很多,如何保證我同一張圖片在不同解析度的手機上能適當的壓縮比例顯示出來呢???有的人就直接壓縮小點,那麼就可以在不同的多個解析度的手機上顯示出來。但是我們都知道壓縮的越小,失真率就越高。很容易理解,大部分我們使用的都是點陣圖,點陣圖有一個特點就是有很多個畫素點組成的畫素矩陣,當我們壓縮圖片時就相當於畫素點,本來顯示這張圖片需要720*1280個畫素點,現在壓縮成320*480個畫素點,這麼少的畫素點不足以顯示出原來的效果,最後明顯造成圖片顯示不清楚。所以找到一個合適的壓縮比例就顯得尤為重要了。
那我們如何做呢?實現的大致思路如下:我們所謂的壓縮實際上就是去設定BItmap中的一個inSampleSize(取樣率)屬性,通過它實現圖片的壓縮。怎麼樣去給inSampleSize屬性設定一個合適的值呢??首先因為螢幕的解析度多樣化,然後我的圖片要根據不同解析度來得到不同inSampleSize,這樣才會合適顯示在我的手機螢幕上。所以需要獲得螢幕的高度和寬度,然後再去拿到螢幕高度和寬度。然後用圖片的寬度,高度分別去除以螢幕的高度和寬度,最後就得到高度比例和寬度比例。
到這裡就會出現兩種方法來實現壓縮:一種比較繁瑣,另一種更直接。
首先,說第一種壓縮方式吧。
由於圖片大多數都是點陣圖顯示,即具體個數的畫素點來顯示的,在不同解析度的手機螢幕顯示圖片說白就是在不同畫素點的總數的螢幕中顯示,很容易理解,當我有個很大的圖片,所謂很很大的圖片 就是總的畫素點數很多,並且在低分辨(總的畫素點少顯示)肯定有問題,只能顯示部分,所以需要 根據當前的手機解析度的大小,來適當壓縮圖片的大小比例,然後來顯示在相應解析度的螢幕上當我通過某個方式拿到一張圖片會有如下幾種情況: 圖片寬度(ImWidth),圖片高度(ImHeight),螢幕寬度(Width),螢幕高度(Height)
1、若圖片的寬度大於圖片高度(即橫向圖片),且寬度大於螢幕寬度:Size=ImWidth/Width
2、若圖片的高度大於圖片寬度(即縱向圖片),且高度大於螢幕高度:Size=ImHeight/Height;
3、就是根據一個圖片壓縮比例演算法公式:取圖片寬度壓縮倍數和圖片高度的壓縮倍數的平均值:Size=(ImWidth/Width+ImHeight/Height)/2;
最後將我們在不同情況下得到的size賦給我們的inSampleSize。
然後,說第二種,第二種就更直接暴力,直接給出一個公式:
inSampleSize=Math.sqrt(widthScaleSize*widthScaleSize+heightScaleSize*heightScaleSize);
這個公式有點像數學上的勾股定理,但是自己想想挺有道理,它這樣取這麼一個inSampleSize,其實類似就是去對角線的壓縮比。
最後再來說一種方式,這個叫圖片質量的壓縮,就是在我們壓縮圖片過程,如何儘量保證我們的圖片的質量呢??主要是通過Bitmap的compress來實現
質量的壓縮的。不妨我們來看下原始碼是怎麼介紹的吧。
/**
* Write a compressed version of the bitmap to the specified outputstream.
* If this returns true, the bitmap can be reconstructed by passing a
* corresponding inputstream to BitmapFactory.decodeStream(). Note: not
* all Formats support all bitmap configs directly, so it is possible that
* the returned bitmap from BitmapFactory could be in a different bitdepth,
* and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque
* pixels).
*
* @param format The format of the compressed image
* @param quality Hint to the compressor, 0-100. 0 meaning compress for
* small size, 100 meaning compress for max quality. Some
* formats, like PNG which is lossless, will ignore the
* quality setting
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos)
它大致的意思是這樣的就是將一個Bitmap物件儲存在一個確定的輸出流中,並且compress會返回一個boolean型別的值,如果返回為true
就會通過一個與之相對應的輸入流來重建一個BItmap物件,然後標註了:並不是所有的格式都直接支援這種方式,這樣就會造成出來不同尺寸大小BItmap可能
會失去原有圖片畫素的透明度。力例如JPEG格式圖片僅僅支援不透明畫素點。還需要注意:就是裡面quality引數的介紹:它是這樣說的quatily取值範圍為:0到100
0代表質量最低,100則代表質量最高,如果是PNG格式的圖片的話,忽視了質量值的設定,就會造成圖片的失真。
那麼有了以上的瞭解,相信對下面程式碼的理解更加簡單了。
package com.mikyou.loadBigImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.os.Bundle;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView bigIv;
private int Width, Height, ImWidth, ImHeight;//獲取螢幕的高度和寬度以及圖片的高度和寬度
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getScreenWidthAndHeight();
bigIv = (ImageView) findViewById(R.id.big_iv);
}
public void loadBigImage(View view) {
//讀取SD卡的狀態,並且-判斷SD卡是否可用
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//先判斷SD卡的狀態是否可用,mnt目錄shell--->emualted---->0
//如果是可用的狀態,就讀到SD卡的路徑,然後將它加載出來
//如果圖片過大就容易造成圖片載入的延遲甚至會造成記憶體溢位,所以需要對圖片做一定的壓縮處理
BitmapFactory.Options options = new BitmapFactory.Options();
String path = Environment.getExternalStorageDirectory() + "/img_big_1.jpg";
/**
* 思考:如何合理設定inSampleSize來針對不同解析度的手機,從而得到一個更佳的圖片壓縮方案呢??
* 分析:
* 由於圖片大多數都是點陣圖顯示,即具體個數的畫素點來顯示的,在不同解析度的手機螢幕顯示圖片
* 說白就是在不同畫素點的總數的螢幕中顯示,很容易理解,當我有個很大的圖片,所謂很很大的圖片
* 就是總的畫素點數很多,並且在低分辨(總的畫素點少顯示)肯定有問題,只能顯示部分,所以需要
* 根據當前的手機解析度的大小,來適當壓縮圖片的大小比例,然後來顯示在相應解析度的螢幕上
* 當我通過某個方式拿到一張圖片會有如下幾種情況:
* 圖片寬度(ImWidth),圖片高度(ImHeight),螢幕寬度(Width),螢幕高度(Height)
* 一、根據SD卡路徑載入圖片的大小比例壓縮
* 1、若圖片的寬度大於圖片高度(即橫向圖片),且寬度大於螢幕寬度:Size=ImWidth/Width
* 2、若圖片的高度大於圖片寬度(即縱向圖片),且高度大於螢幕高度:Size=ImHeight/Height;
* 3、就是根據一個圖片壓縮比例演算法公式:取圖片寬度壓縮倍數和圖片高度的壓縮倍數的平均值:Size=(ImWidth/Width+ImHeight/Height)/2;
* 二、圖片的質量的壓縮
* 三、根據Bitmap來壓縮圖片大小比例
* */
//bitmap=getImageCompress(bitmap);
// bitmap= getImageByScaleSize(bitmap);
Bitmap bitmap=getImageByScaleSizeByTec(path);
bigIv.setImageBitmap(bitmap);
}
}
/**
* 圖片的質量壓縮:
* 圖片質量的壓縮思想大致如下:
* 先將一張圖片到一個位元組陣列輸出流物件儲存,
* 然後通過不斷壓縮資料,直到圖片大小壓縮到某個具體大小時,然後再把
* 位元組陣列輸出流物件作為一個位元組陣列輸入流引數物件傳入得到一個位元組陣列輸入流
* 最後再將位元組陣列輸入流得到Bitmap物件,最終拿到圖片質量壓縮後的圖片
*/
public Bitmap getImageCompress(Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);//質量壓縮方法,這裡100表示不壓縮,把壓縮後的資料存放到位元組陣列輸出流中。
int options = 100;
while (baos.toByteArray().length / 1024 > 100) { //迴圈判斷如果壓縮後圖片是否大於100kb,大於繼續壓縮
baos.reset();//重置baos即清空baos
options -= 10;//每次都減少10
bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//這裡壓縮options%,把壓縮後的資料存放到baos中
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮後的資料baos存放到ByteArrayInputStream中
Bitmap bitmap2 = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream資料生成圖片
return bitmap2;
}
/**
* 根據SD卡路徑載入圖片的大小比例壓縮
*/
public Bitmap getImageByScaleSize(String path) {
int scaleSize =1;//1就表示不壓縮
BitmapFactory.Options options = new BitmapFactory.Options();
/* options.inJustDecodeBounds=true;//只讀取圖片的資訊,不讀取圖片的具體資料
ImWidth = options.outWidth;
ImHeight = options.outHeight;*/
getImageWidthAndHeight(path);//得到圖片的高度和寬度
if (ImWidth > ImHeight && ImWidth > Width) {
scaleSize = (int)(ImWidth*1.0f / Width+0.5f);//加0.5是為了四捨五入,取一個很好的精度
} else if (ImHeight > ImWidth && ImHeight > Height) {
scaleSize = (int)(ImHeight*1.0f / Height+0.5f);
} else {//其他情況表示,就是當是橫向或者縱向圖片時,它的長度和寬度都大於螢幕
scaleSize = (int)(ImWidth*1.0f / Width + ImHeight*1.0f / Height+0.5f) / 2;
}ba
//設定圖片的取樣率
options.inSampleSize = scaleSize;//針對不同的手機解析度,設定的縮放比也不一樣,這裡的值可能是不一樣的
Bitmap bitmap2 = BitmapFactory.decodeFile(path, options);
return bitmap2;
}
//獲取當前手機螢幕的高度和寬度
private void getScreenWidthAndHeight() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
Width = metrics.widthPixels;
Height = metrics.heightPixels;
}
//得到原圖的高度和寬度
private void getImageWidthAndHeight(String path) {
try {
ExifInterface exifInterfece=new ExifInterface(path);
ImWidth=exifInterfece.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH,0);
ImHeight=exifInterfece.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH,0);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 公式法
* */
public Bitmap getImageByScaleSizeByTec(String path){
int scaleSize =1;//1就表示不壓縮
getImageWidthAndHeight(path);
int WidthScaleSize=(int)(ImWidth*1.0f/Width*1.0f+0.5f);
int HeightScaleSize=(int)(ImHeight*1.0f/Height*1.0f+0.5f);
scaleSize=(int)(Math.sqrt(WidthScaleSize*WidthScaleSize+HeightScaleSize*HeightScaleSize)+0.5f);
BitmapFactory.Options options = new BitmapFactory.Options();
//設定圖片的取樣率
options.inSampleSize = scaleSize;//針對不同的手機解析度,設定的縮放比也不一樣,這裡的值可能是不一樣的
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
}
執行結果: