寫在開頭
本文只是介紹悔据,通過安卓原生的方式將一張?jiān)紙D片縮放到合適的大小庄敛,嚴(yán)格來說是縮放圖片,而非壓縮圖片的技術(shù)
并且由于縮放后的圖片占空間還是較大科汗,并且算法耗時(shí)較長藻烤,所以對(duì)于我的使用場(chǎng)景(壓縮上傳圖片)不是很好用,但是拿來做圖片墻的話還行
若想壓縮上傳圖片的話,還是推薦使用現(xiàn)成的庫怖亭,下面先推薦兩個(gè)圖片壓縮庫涎显,傳送門:
AiYaCompressHelper
Luban
圖片壓縮
生產(chǎn)環(huán)境用戶反映,上傳身份證經(jīng)常會(huì)失敗兴猩,而且很費(fèi)時(shí)間期吓,檢查代碼發(fā)現(xiàn)前輩的代碼寫的漿糊一般,凈出現(xiàn)一些w倾芝,ww讨勤,www的變量名,我知道他們都指代寬width晨另,但原諒后輩腦子笨潭千,實(shí)在記不住誰是誰,只能刪掉重來借尿。
壓縮圖片的思路刨晴,無非就是以下幾步:
- 通過inSampleSize減少取樣點(diǎn),先將圖片大概壓縮一下
- 通過Matrix垛玻,對(duì)圖片大小進(jìn)行精確調(diào)整
- 改變編碼格式割捅,將圖片轉(zhuǎn)存為PNG或者JPG
減少采樣點(diǎn)
減少采樣點(diǎn)是BitmapFactory中提供的一個(gè)方法,主要是用到了inSampleSize參數(shù)帚桩。若inSampleSize為1時(shí),采樣后的圖片就是原始大小的圖片嘹黔,若inSampleSize為2账嚎,則采樣后的圖片的寬高為原圖的1/2,像素面積為原圖的1/4儡蔓,占空間也為1/4郭蕉;若inSampleSize為4,則采樣后的圖片的寬高為原圖的1/4喂江,像素面積為原圖的1/16召锈,占空間也為1/16以此類推。
但是inSampleSize參數(shù)不能為浮點(diǎn)數(shù)获询,以及小于1的數(shù)涨岁,小于1時(shí)作為1來處理,即不能將圖片放大吉嚣,因?yàn)樵砩喜辉试S梢薪。
這里有一個(gè)特殊情況,官方文檔中指出尝哆,inSampleSize參數(shù)取值應(yīng)該為2的冪秉撇,即1、2、4琐馆、8等规阀,若給的值不為2的冪,則會(huì)取一個(gè)比給的值小的最大的2的冪來代替瘦麸。例如inSampleSize參數(shù)取值為10姥敛,則會(huì)用8來代替。但這個(gè)結(jié)論并非在所有系統(tǒng)上成立瞎暑,因此此處應(yīng)該嚴(yán)格控制彤敛,否則會(huì)得到意想不到的結(jié)果。
獲取采樣率的步驟遵循以下流程:
- BitmapFactory.Options.inJustDecodeBounds = true了赌,若此時(shí)加載圖片墨榄,只會(huì)加載圖片的寬高信息
- 加載圖片,然后從BitmapFactory.Options中取出寬高信息
- 根據(jù)目標(biāo)大小計(jì)算采樣率
- 可選步驟勿她,BitmapFactory.Options.inPreferredConfig = Config.RGB_565袄秩,將圖像設(shè)為565模式,此時(shí)圖像深度為2字節(jié)逢并。安卓中默認(rèn)模式為RGB_8888之剧,圖像深度為4字節(jié),但大部分情況不需要圖像透明屬性
- BitmapFactory.Options.inJustDecodeBounds = false砍聊,重新加載圖片
需要注意的是背稼,降低采樣點(diǎn)加載圖片耗時(shí)較長,一次處理大概需要200-400ms玻蝌,大量圖片請(qǐng)使用線程池蟹肘。
Matrix變換
由于修改采樣點(diǎn)無法將圖片縮放到一個(gè)準(zhǔn)確的大小,所以還需要Matrix做后續(xù)處理俯树。因?yàn)閳D片在內(nèi)存中存儲(chǔ)就是一個(gè)個(gè)像素點(diǎn)帘腹,Matrix可以對(duì)每個(gè)像素點(diǎn)進(jìn)行相應(yīng)的變換,即可完成對(duì)圖像的變換许饿。
需要注意的是阳欲,為什么有了Matrix變換,還需要用更改采樣點(diǎn)的方式做一次預(yù)處理陋率?因?yàn)镸atrix變換需要將圖片加載進(jìn)內(nèi)存操作球化,而現(xiàn)在手機(jī)相機(jī),拍一張圖像大約16M像素點(diǎn)翘贮,若使用RGB_8888模式加載赊窥,一張圖片大概需要60M的內(nèi)存,雖然現(xiàn)在手機(jī)內(nèi)存夠大狸页,但一個(gè)應(yīng)用程序可用內(nèi)存也就幾百M(fèi)锨能,加載大量圖片還是會(huì)OOM扯再。所以應(yīng)該先減少采樣點(diǎn)加載圖片,做好預(yù)處理之后再用Matrix微調(diào)址遇。
順便說一下Matrix熄阻,Matrix基本上就是一個(gè)用來操作圖片的類,但是不止是縮放倔约,還有其他功能秃殉,比如反轉(zhuǎn),位移浸剩,傾斜等
setTranslate(float dx,float dy):位移操作
setSkew(float kx,float ky):傾斜操作钾军,kx、ky為X绢要、Y方向上的比例
setSkew(float kx,float ky,float px,float py):傾斜操作吏恭,以px、py為軸心進(jìn)行傾斜重罪,kx樱哼、ky為X、Y方向上的傾斜比例
setRotate(float degrees):旋轉(zhuǎn)操作剿配,軸心為(0,0)
setRotate(float degrees,float px,float py):旋轉(zhuǎn)操作搅幅,軸心為(px,py)
setScale(float sx,float sy):縮放操作,sx呼胚、sy為X茄唐、Y方向上的縮放比例。
setScale(float sx,float sy,float px,float py):縮放操作砸讳,以(px,py)為軸心進(jìn)行縮放琢融,sx、sy為X簿寂、Y方向上的縮放比例
不過這不是本文的重點(diǎn),不再詳述宿亡。
完整代碼貼出:
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
/**
* Created by ZhangXuan
* 圖像縮放工具類
*/
public class ImageScalingUtil {
/**
* 通過降低取樣點(diǎn)壓縮圖片常遂,不推薦直接使用<br/>
* 壓縮后圖像使用RGB_565模式,即每個(gè)像素占位2字節(jié)挽荠,限定寬高壓縮<br/>
* 由于inSampleSize壓縮比這個(gè)參數(shù)在不同手機(jī)表現(xiàn)不同克胳,有的手機(jī)可以取任意整數(shù),有的手機(jī)只能取2的冪數(shù)圈匆,則取2的冪數(shù)保證所有手機(jī)表現(xiàn)一致漠另。<br/>
* 需注意,由于inSampleSize的特性跃赚,若限定寬為1000x1000笆搓,實(shí)際圖片寬為1010x600性湿,則該圖片會(huì)被壓縮為505x300,圖片會(huì)較小
*
* @param imgPath 原圖片路徑
* @param reqWidth 最大寬度
* @param reqHeight 最大高度
* @return 壓縮后的bitmap
*/
public static Bitmap reducingBitmapSampleFromPath(String imgPath, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;// 讀取大小不讀取內(nèi)容
options.inPreferredConfig = Config.RGB_565;// 設(shè)置圖片每個(gè)像素占2字節(jié)满败,沒有透明度
BitmapFactory.decodeFile(imgPath, options);// options讀取圖片
double outWidth = options.outWidth;
double outHeight = options.outHeight;// 獲取到當(dāng)前圖片寬高
int inSampleSize = 1;
/*
先計(jì)算原圖片寬高比ratio=width/height肤频,再計(jì)算限定的范圍的寬高比比reqRatio,
若reqRatio > ratio算墨,則說明限定的范圍更加細(xì)長宵荒,則以高為標(biāo)準(zhǔn)計(jì)算inSampleSize
否則,則說明限定范圍更加粗矮净嘀,則以寬為計(jì)算標(biāo)準(zhǔn)
*/
double ratio = outWidth / outHeight;
double reqRatio = reqWidth / reqHeight;
if (reqRatio > ratio)
while (outHeight / inSampleSize > reqHeight) inSampleSize *= 2;
else
while (outWidth / inSampleSize > reqWidth) inSampleSize *= 2;
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(imgPath, options);
}
/**
* 通過降低取樣點(diǎn)壓縮圖片报咳,不推薦直接使用<br/>
* 壓縮后圖像使用RGB_565模式,即每個(gè)像素占位2字節(jié)挖藏,限定大小壓縮<br/>
* 由于inSampleSize壓縮比這個(gè)參數(shù)在不同手機(jī)表現(xiàn)不同暑刃,有的手機(jī)可以取任意整數(shù),有的手機(jī)只能取2的冪數(shù)熬苍,則取2的冪數(shù)保證所有手機(jī)表現(xiàn)一致稍走。<br/>
* 需注意,由于inSampleSize的特性柴底,若限定大小為500k婿脸,而原圖為501k,則壓縮后的圖片為125.25k柄驻,圖片會(huì)較小
*
* @param imgPath 原圖片路徑
* @param reqSize 目標(biāo)文件大小狐树,單位為kb
* @return 壓縮后的bitmap
*/
public static Bitmap reducingBitmapSampleFromPath(String imgPath, int reqSize) {
long area = reqSize * 1024 / 2;// 每個(gè)像素占2字節(jié),將需求大小轉(zhuǎn)為像素面積
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;// 讀取大小不讀取內(nèi)容
options.inPreferredConfig = Config.RGB_565;// 設(shè)置圖片每個(gè)像素占2字節(jié)鸿脓,沒有透明度
BitmapFactory.decodeFile(imgPath, options);// options讀取圖片
double outWidth = options.outWidth;
double outHeight = options.outHeight;// 獲取到當(dāng)前圖片寬高
int inSampleSize = 1;
while ((outHeight / inSampleSize) * (outWidth / inSampleSize) > area)
inSampleSize *= 2;
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(imgPath, options);
}
/**
* 壓縮圖片<br/>
* 通過設(shè)定壓縮后的寬高的最大像素抑钟,將圖片等比例縮小<br/>
* 先通過降低取樣點(diǎn),將圖片壓縮到比目標(biāo)寬高稍大一點(diǎn)野哭,然后再通過Matrix將圖片精確調(diào)整到目標(biāo)大小<br/>
* 壓縮后圖像使用RGB_565模式在塔,即每個(gè)像素占位2字節(jié),限定大小壓縮<br/>
* 若被壓縮圖片本身就小于限定大小拨黔,則不改變其大小蛔溃,只更改圖像顏色模式為RGB_565<br/>
* 由于inSampleSize壓縮比這個(gè)參數(shù)在不同手機(jī)表現(xiàn)不同,有的手機(jī)可以取任意整數(shù)篱蝇,有的手機(jī)只能取2的冪數(shù)贺待,則通過混合壓縮的方式保證壓縮的結(jié)果一致<br/>
*
* @param imgPath 原圖片路徑
* @param reqWidth 最大寬度
* @param reqHeight 最大高度
* @return 壓縮后的bitmap
*/
public static Bitmap compressBitmapFromPath(String imgPath, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;// 讀取大小不讀取內(nèi)容
options.inPreferredConfig = Config.RGB_565;// 設(shè)置圖片每個(gè)像素占2字節(jié),沒有透明度
BitmapFactory.decodeFile(imgPath, options);// options讀取圖片
double outWidth = options.outWidth;
double outHeight = options.outHeight;// 獲取到當(dāng)前圖片寬高
int inSampleSize = 1;
/*
先計(jì)算原圖片寬高比ratio=width/height零截,再計(jì)算限定的范圍的寬高比比reqRatio麸塞,
若reqRatio > ratio,則說明限定的范圍更加細(xì)長涧衙,則以高為標(biāo)準(zhǔn)計(jì)算inSampleSize
否則哪工,則說明限定范圍更加粗矮奥此,則以寬為計(jì)算標(biāo)準(zhǔn)
*/
double ratio = outWidth / outHeight;
double reqRatio = reqWidth / reqHeight;
if (reqRatio > ratio)
while (outHeight / inSampleSize > reqHeight) inSampleSize *= 2;
else
while (outWidth / inSampleSize > reqWidth) inSampleSize *= 2;
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
if (1 == inSampleSize) {
// inSampleSize == 1,就說明原圖比要求的尺寸小或者相等正勒,那么不用繼續(xù)壓縮得院,直接返回。
return BitmapFactory.decodeFile(imgPath, options);
}
/*
否則的話章贞,先將圖片通過減少采樣點(diǎn)的方式祥绞,以一個(gè)比限定范圍稍大的尺寸讀入內(nèi)存,
防止因?yàn)閳D片太大而OOM鸭限,以及太大的圖片加載時(shí)間過長
然后繼續(xù)進(jìn)行壓縮的步驟
*/
options.inSampleSize = inSampleSize / 2;
Bitmap baseBitmap = BitmapFactory.decodeFile(imgPath, options);
/*
使用之前計(jì)算過的寬高比蜕径,
若reqRatio > ratio,則說明限定的范圍更加細(xì)長败京,則以高為標(biāo)準(zhǔn)計(jì)算壓縮比
否則兜喻,則說明限定范圍更加粗矮,則以寬為計(jì)算標(biāo)準(zhǔn)
*/
float compressRatio = 1;
if (reqRatio > ratio)
compressRatio = reqHeight * 1.0f / baseBitmap.getHeight();
else
compressRatio = reqWidth * 1.0f / baseBitmap.getWidth();
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() * compressRatio),
(int) (baseBitmap.getHeight() * compressRatio),
baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix對(duì)象
Matrix matrix = new Matrix();
// 根據(jù)傳入的參數(shù)設(shè)置縮放比例
matrix.setScale(compressRatio, compressRatio);
Paint paint = new Paint();
// 消除鋸齒
paint.setAntiAlias(true);
// 根據(jù)縮放比例赡麦,把圖片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
return afterBitmap;
}
/**
* 壓縮圖片<br/>
* 通過設(shè)定壓縮后的大小朴皆,將圖片等比例縮小<br/>
* 先通過降低取樣點(diǎn),將圖片壓縮到比目標(biāo)寬高稍大一點(diǎn)泛粹,然后再通過Matrix將圖片精確調(diào)整到目標(biāo)大小<br/>
* 壓縮后圖像使用RGB_565模式遂铡,即每個(gè)像素占位2字節(jié)<br/>
* 若被壓縮圖片本身就小于限定大小,則不改變其大小晶姊,只更改圖像顏色模式為RGB_565<br/>
* 由于inSampleSize壓縮比這個(gè)參數(shù)在不同手機(jī)表現(xiàn)不同扒接,有的手機(jī)可以取任意整數(shù),有的手機(jī)只能取2的冪數(shù)们衙,則通過混合壓縮的方式保證壓縮的結(jié)果一致<br/>
*
* @param imgPath 原圖片路徑
* @param reqSize 壓縮后文件大小钾怔,單位為kb
* @return 壓縮后的bitmap
*/
public static Bitmap compressBitmapFromPath(String imgPath, int reqSize) {
long area = reqSize * 1024 / 2;// 每個(gè)像素占2字節(jié),將需求大小轉(zhuǎn)為像素面積
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;// 讀取大小不讀取內(nèi)容
options.inPreferredConfig = Config.RGB_565;// 設(shè)置圖片每個(gè)像素占2字節(jié)蒙挑,沒有透明度
BitmapFactory.decodeFile(imgPath, options);// options讀取圖片
double outWidth = options.outWidth;
double outHeight = options.outHeight;// 獲取到當(dāng)前圖片寬高
int inSampleSize = 1;
while ((outHeight / inSampleSize) * (outWidth / inSampleSize) > area)
inSampleSize *= 2;
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
if (1 == inSampleSize) {
// inSampleSize == 1宗侦,就說明原圖比要求的尺寸小或者相等,那么不用繼續(xù)壓縮忆蚀,直接返回凝垛。
return BitmapFactory.decodeFile(imgPath, options);
}
/*
否則的話,先將圖片通過減少采樣點(diǎn)的方式蜓谋,以一個(gè)比限定范圍稍大的尺寸讀入內(nèi)存,
防止因?yàn)閳D片太大而OOM炭分,以及太大的圖片加載時(shí)間過長
然后繼續(xù)進(jìn)行壓縮的步驟
*/
options.inSampleSize = inSampleSize / 2;
Bitmap baseBitmap = BitmapFactory.decodeFile(imgPath, options);
/*
目標(biāo)大小的面積與現(xiàn)在圖片大小的面積的比的平方根桃焕,就是縮放比
java Math.sqrt() 函數(shù)不能開小數(shù),而且先計(jì)算除法捧毛,再計(jì)算開放观堂,再對(duì)結(jié)果求反誤差很大让网,所以做兩次開方計(jì)算
*/
float compressRatio = 1;
compressRatio = (float) (Math.sqrt(area) / Math.sqrt(baseBitmap.getWidth() * baseBitmap.getHeight()));
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() * compressRatio),
(int) (baseBitmap.getHeight() * compressRatio),
baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix對(duì)象
Matrix matrix = new Matrix();
// 根據(jù)傳入的參數(shù)設(shè)置縮放比例
matrix.setScale(compressRatio, compressRatio);
Paint paint = new Paint();
// 消除鋸齒
paint.setAntiAlias(true);
// 根據(jù)縮放比例,把圖片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
return afterBitmap;
}
/**
* 將一張圖片 以PNG的格式 轉(zhuǎn)換成 base64 編碼
*
* @param bitmap
* @return
*/
public static String savePNGAndToBase64(Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] pngByte = baos.toByteArray();// 轉(zhuǎn)為byte數(shù)組
return Base64.encodeToString(pngByte, Base64.DEFAULT);
}
/**
* 將一張圖片 以JPEG的格式 轉(zhuǎn)換成 base64 編碼
*
* @param bitmap
* @return
*/
public static String saveJPEGAndToBase64(Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] pngByte = baos.toByteArray();// 轉(zhuǎn)為byte數(shù)組
return Base64.encodeToString(pngByte, Base64.DEFAULT);
}
}