網(wǎng)站首頁 編程語言 正文
前言
在Android中我們經(jīng)常會遇到圖片壓縮的場景,比如給服務(wù)端上傳圖片,包括個人信息的用戶頭像,有時候人臉識別也需要捕獲圖片等等。這種情況下,我們都需要對圖片做一定的處理,比如大小,尺寸等的壓縮。
常見的圖片壓縮方法
- 質(zhì)量壓縮
- 尺寸壓縮
- libjpeg
質(zhì)量壓縮
首先我們要介紹一個api--Bitmap.compress()
@WorkerThread
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
StrictMode.noteSlowCall("Compression of a bitmap is slow");
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
compress()是系統(tǒng)的API,也是質(zhì)量和尺寸壓縮常用的方法。
public boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream);這個方法有三個參數(shù):
- Bitmap.CompressFormat format圖像的壓縮格式;
- int quality圖像壓縮率,O-100。0壓縮100%,100意味著不壓縮;
- OutputStream stream 寫入壓縮數(shù)據(jù)的輸出流;
返回值:如果成功地把壓縮數(shù)據(jù)寫入輸出流,則返回true。
偽代碼
val baos= ByteArrayoutputstream ()
try {
var quality = 50
do {
quality -= 10
baos.reset()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
} while (baos.toByteArray().size / 1024 > 100)
fos.write(baos.toByteArray(o))
}catch (ex : Throwable) {
ex.printStackTrace ()} finally {
fos.apply i this: FileOutputStream
flush ()
close ()
}
尺寸壓縮
先來看看一個屬性O(shè)ptions
- 屬性 inJustDecodeBounds,如果該值為true,那么將不返回實際的 bitmap,也不給其分配內(nèi)存空間這樣就避免內(nèi)存溢出了。
- 允許我們查詢圖片的信息,這其中就包括圖片大小信息,options.outHeight(圖片原始高度)和option.outWidth(圖片原始寬度)。
兩次decode,傳入不同的options配置:
部分偽代碼
val reqWidth = 500
val reqHeight = 300
val bitmap = decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight)
val fos = Fileoutputstream(
File(applicationContext.filesDir,
child: "$ {system.currentTimeMillis() }_scale.jpg")
)
try {
val quality = 50
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos)
catch(ex: Throwable) {
ex.printstackTrace() finally {
fos.apply {
flush()
close()
}
}
}
}
}
private fun decodeSampledBitmapFromFile(imageFile: File,reqWidth: Int,reqHeight: Int): Bitmap
{
return BitmapFactory.Options().run {
inJustDecodeBounds = true
//先獲取原始圖片的寬高,不會將Bitmap加載到內(nèi)存中,返回null
BitmapFactory.decodeFile(imageFile.absolutePath, opts: this)
inSamplesize = calculateInSampleSize(options: this, reqWidth,reqHeight)
inJustDecodeBounds - false
BitmapFactory.decodeFile(imageFile.absolutePath, opts : this)
}
}
private fun calculateInSampleSize(context: BitmapFactory, reqWidth: Int, reqHeight: Int): Int {
//解構(gòu)語法,獲取原始圖片的寬高
val (height: Int, width: Int) = options.run { outHeight to outwidth }
//計算最大的inSampleSize值,該值為2的冪次方,并同時保持這兩個值高度和寬度大于請求的高度和寬度。
//原始圖片的寬高要大于要求的寬高
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfwidth: Int = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfwidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
inSampleSize都是2的倍數(shù) .
BitmapFactory 給我們提供了一個解析圖片大小的參數(shù)類 BitmapFactory.Options ,把這個類的對象的 inJustDecodeBounds 參數(shù)設(shè)置為 true,這樣解析出來的 Bitmap 雖然是個 null,但是 options 中可以得到圖片的寬和高以及圖片的類型。得到了圖片實際的寬和高之后我們就可以進行壓縮設(shè)置了,主要是計算圖片的采樣率。
- 第一次采樣已經(jīng)結(jié)束,我們已經(jīng)成功的計算出了sampleSize的大小
- 第二次采樣時我需要將圖片加載出來顯示,不能只加載圖片的框架,因此inJustDecodeBounds屬性要設(shè)置為false
libjpeg
- libjpeg是一個完全用C語言編寫的庫,包含了被廣泛使用的JPEG解碼、JPEG編碼和其他的JPEG功能的實現(xiàn)。
- libjpeg-turbo圖像編解碼器,使用了SIMD指令來加速x86、x86-64、ARM和 PowerPC系統(tǒng)上的JPEG壓縮和解壓縮,libjpeg-turbo 的速度通常是libjpeg 的2-6倍。
- 可以使用采用哈夫曼
- 微信采用的方式
圖片壓縮流程
其實最重要的是把ARGB轉(zhuǎn)換為RBG,也就是把每個像素4個字節(jié),轉(zhuǎn)換為每個像素3個字節(jié)。
導(dǎo)入對應(yīng)的so庫文件即可編寫C的代碼 jpeg.so 和 jpeg-turbo.so
編寫這部分的代碼需要NDK的環(huán)境和C語言的基礎(chǔ)
偽代碼
int generateCompressJPEG(BYTE *data, int w, int h, int quality, const char *outfileName, jboolean optimize) {
//結(jié)構(gòu)體相當(dāng)于java的類
struct jpeg_compress_struct jcs;
//當(dāng)讀完整個文件的時候回回調(diào)
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
//setjmp是一個系統(tǒng)級函數(shù),是一個回調(diào)
if (setjmp(jem.setjmp_buffer)) {
return 0;
}
//初始化jsc結(jié)構(gòu)體
jpeg_create_compress(&jcs);
//打開輸出文件 wb可寫 rb可讀
FILE *f = fopen(outfileName, "wb");
if (f == NULL) {
return 0;
}
//設(shè)置結(jié)構(gòu)體的文件路徑,以及寬高
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;
jcs.image_height = h;
//TRUE=arithmetic coding, FALSE=Huffman
jcs.arith_code = false;
int nComponent = 3;
// 顏色的組成rgb,三個 of color components in input image
jcs.input_components = nComponent;
// 設(shè)置顏色空間為rgb
jcs.in_color_space = JCS_RGB;
jpeg_set_defaults(&jcs);
// 是否采用哈夫曼
jcs.optimize_coding = optimize;
//設(shè)置質(zhì)量
jpeg_set_quality(&jcs, quality, true);
//開始壓縮
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
row_stride = jcs.image_width * nComponent;
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];
jpeg_write_scanlines(&jcs, row_pointer, 1);
}
// 壓縮結(jié)束
jpeg_finish_compress(&jcs);
// 銷毀回收內(nèi)存
jpeg_destroy_compress(&jcs);
//關(guān)閉文件
fclose(f);
return 1;
}
for (int i = 0; i < bitmapInfo.height; ++i) {
for (int j= 0; j < bitmapInfo.width; ++j){
if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888){
//0x2312faff ->588446463
color = *(int *) (pixelsColor);
// 從color值中讀取RGBA的值/ /ABGR
b = (color >> 16)& 0xFE;
g = (color >> 8)& OxFF;
r = (color >> 0) & OxFF;
*data = r;
* (data + 1) =g;
*(data + 2) = b;
data += 3;
//移動步長4個字節(jié)
pixelsColor +- 4 ;
}else {
return -2;
}
// 是否采用哈夫曼
jcs.optimize_coding = optimize;
至此,三種圖片壓縮的方法已經(jīng)介紹完畢了。
總結(jié)
經(jīng)過圖片壓縮實踐,質(zhì)量壓縮和libjpeg最后的圖片的大小一樣,效果也和原圖差不多。
其實,經(jīng)過我翻查原碼發(fā)現(xiàn),新版本的Bitmap.compress() 會調(diào)用
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
private static native boolean nativeCompress(long nativeBitmap, int format,
int quality, OutputStream stream,
byte[] tempStorage);
其實最后也會調(diào)用到nativeCompress的壓縮,也會采用哈夫曼算法,提高壓縮效率。
既然這樣,那么這里為什么還要介紹libjpeg的方法呢?
- 兼容低版本,早起的compress沒有采用哈夫曼算法
- 大廠的跨平臺算法
原文鏈接:https://juejin.cn/post/7087388674735734797
相關(guān)推薦
- 2022-08-31 Postgresql數(shù)據(jù)庫character?varying和character的區(qū)別說明_Post
- 2022-03-04 如何在uni-app中選擇一個合適的UI組件庫
- 2022-05-08 一文教你向Pandas?DataFrame添加行_python
- 2023-07-04 spring boot security之前后端分離配置
- 2023-10-14 C/C++--跨平臺--預(yù)定義宏 WIN32、_WIN32、_WIN64
- 2022-03-13 C語言實現(xiàn)求解最小公倍數(shù)的算法示例_C 語言
- 2023-01-30 python多進程程序打包成exe的問題_python
- 2023-06-04 Pandas通過index選擇并獲取行和列_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支