對(duì)于一個(gè)應(yīng)用或多或少的都會(huì)使用到圖片,如果圖片過大就會(huì)很吃內(nèi)存,造成oom的。使用在使用圖片的時(shí)候我們可以使用圖片壓縮的方式對(duì)圖片進(jìn)行處理。使圖片即合適又不占用大量的內(nèi)存。我們從以下幾個(gè)方面介紹。
1.圖片的存在方式
- 文件的方式
- 流的方式
- bitmap---存在內(nèi)存中
- 字符串的方式(base64便于加密)
2.android中壓縮的api
android系統(tǒng)中提供了Bitmapfactory類對(duì)圖片進(jìn)行壓縮。
對(duì)與bitmapfactory對(duì)圖片進(jìn)行編碼有如下方式:
-BitmapFactory.decodeResource(...) // resource
- BitmapFactory.decodeByteArray() // 字節(jié)數(shù)組
- BitmapFactory.decodeFile() // 文件
- BitmapFactory.decodeStream() // 流
- BitmapFactory.decodeFileDescriptor() // FileDescriptor
這些函數(shù)都有最終調(diào)用的是decodeStream這個(gè)函數(shù)。然后調(diào)用本地方法,即c/C++進(jìn)行編碼(nativeDecodeStream)
在這些函數(shù)中都可以傳入一個(gè)參數(shù),BitmapFactory.Options對(duì)編碼進(jìn)行不同的操作。
- inDestiy : 密度,指每平方英寸中的像素?cái)?shù),在DisplayMetrics類中屬性density的值為dpi/160
- inScreenDensity : 單位密度下可存在的點(diǎn)
- inTargetDensity : bitmap最終的像素
| density | ScreenDensity | 分辨率 | res |
|---|---|---|---|
| 1 | 160 | 320*533 | mdpi |
| 1.5 | 240 | 480*800 | hdpi |
| 2 | 320 | 720*12803 | xhdpi |
| 3 | 480 | 1080*1920 | xxhdpi |
| 3.5 | 560 | ... | xxxhdpi |
壓縮格式(Bitmap.Config)
| Bitmap.Config | 值 | 描述 | 占用內(nèi)存(字節(jié)) |
|---|---|---|---|
| Bitmap.Config | ARGB_8888 | 表示32位的ARGB位圖 | 4 |
| Bitmap.Config | ARGB_4444 | 表示16位的ARGB位圖 | 2 |
| Bitmap.Config | RGB_565 | 表示16位的RGB位圖 | 2 |
| Bitmap.Config | ALPHA_8 | 表示8位的Alpha位圖 | 1 |
3. Bitmap占用內(nèi)存計(jì)算
-
Bitmap占用內(nèi)存計(jì)算 = 圖片最終顯示出來的寬 * 圖片最終顯示出來的高 * 圖片品質(zhì)(Bitmap.Config的值)
SDcard中A圖片的分辨率為300 X 600,使用ARGB_8888的品質(zhì)加載,那么這張圖片占用的內(nèi)存 = 300 * 600 * 4 = 720000(byte) = 0.686(mb)
getByteCount()可以獲取圖片占用內(nèi)存字節(jié)大小。
為什么計(jì)算的公式是圖片最終顯示出來的寬 * 圖片最終顯示出來的高,而不是,圖片的寬和圖片的高呢?
主要是這樣的,Android為了適配不同分辨率的機(jī)型,對(duì)放到不同drawable下的圖片,在創(chuàng)建Bitmap的過程中,進(jìn)行了縮放判斷,如果需要縮放的話,那么最終創(chuàng)建出來的圖片寬和高都進(jìn)行了修改。
原圖大小為165221,圖片放到drawable-hdpi目錄下,手機(jī)分辨率為7201280:
計(jì)算出圖片是否需要縮放
density,如果不設(shè)置opts.inDensity的話,該值默認(rèn)為160。如果圖片放到drawable-hdpi目錄下,該值為240,
targetDensity,如果不設(shè)置opts.inTargetDensity的話,該值默認(rèn)為DisplayMetrics的densityDpi,注意該值是由手機(jī)自身設(shè)置的。比如720 X 1280分辨率的手機(jī),該值為320;1080 X 1920分辨率的手機(jī),該值為480
scale = (float) targetDensity / density; 圖片放到drawable-hdpi目錄下,手機(jī)分辨率為720*1280;scale = 320 / 240 = 1.333
scale = (float) targetDensity / density = 320 / 240 = 1.333
scaledWidth = int(scaledWidth * scale + 0.5f) = 165 * 1.333 + 0.5 = 220
scaledHeight = int(scaledHeight * scale + 0.5f) = 221 * 1.333 + 0.5 = 295
圖片占用內(nèi)存 = 220 * 295 * 4 = 259600
4.其他參數(shù)
public Bitmap inBitmap; //是否重用該Bitmap,注意使用條件,Bitmap的大小必須等于inBitmap,inMutable為true
public boolean inMutable; //設(shè)置Bitmap是否可以更改
public boolean inJustDecodeBounds; // true時(shí),decode不會(huì)創(chuàng)建Bitmap對(duì)象,但是可以獲取圖片的寬高
public int inSampleSize; // 壓縮比例,比如=4,代表寬高壓縮成原來的1/4,注意該值必須>=1
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; //Bitmap.Config,默認(rèn)為ARGB_8888
public boolean inPremultiplied; //默認(rèn)為true,一般不需要修改,如果想要修改圖片原始編碼數(shù)據(jù),那么需要修改
public boolean inDither; //是否抖動(dòng),默認(rèn)為false
public int inDensity; //Bitmap的像素密度
public int inTargetDensity; //Bitmap最終的像素密度(注意,inDensity,inTargetDensity影響圖片的縮放度)
public int inScreenDensity; //當(dāng)前屏幕的像素密度
public boolean inScaled; //是否支持縮放,默認(rèn)為true,當(dāng)設(shè)置了這個(gè),Bitmap將會(huì)以inTargetDensity的值進(jìn)行縮放
public boolean inPurgeable; //當(dāng)存儲(chǔ)Pixel的內(nèi)存空間在系統(tǒng)內(nèi)存不足時(shí)是否可以被回收
public boolean inInputShareable; //inPurgeable為true情況下才生效,是否可以共享一個(gè)InputStream
public boolean inPreferQualityOverSpeed; //為true則優(yōu)先保證Bitmap質(zhì)量其次是解碼速度
public int outWidth; //Bitmap最終的寬
public int outHeight; //Bitmap最終的高
public String outMimeType; //
public byte[] inTempStorage; //解碼時(shí)的臨時(shí)空間,建議16*1024
5.優(yōu)化方法
大圖加載。兩種方法:1.通過BitmapRegionDecoder。進(jìn)行局部顯示。2.通過壓縮圖片。這里我們只介紹壓縮,局部顯示請(qǐng)參考
1.質(zhì)量壓縮
- 降低圖片的質(zhì)量,但是像素沒有減少
- 原理:通過算法摳掉(同化)了圖片中的一些某個(gè)些點(diǎn)附近相近的像素,達(dá)到降低質(zhì)量介紹文件大小的目的。減小了圖片質(zhì)量。
- 注意:它其實(shí)只能實(shí)現(xiàn)對(duì)file的影響,對(duì)加載這個(gè)圖片出來的bitmap內(nèi)存是無法節(jié)省的,還是那么大。
- 使用場(chǎng)景: 將圖片壓縮后保存到本地,或者將圖片上傳到服務(wù)器。根據(jù)實(shí)際需求來
private File imageFile;
private File sdFile;
public void qualitCompress(View v){
sdFile = Environment.getExternalStorageDirectory();
imageFile = new File(sdFile, "1.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
//壓縮圖片
compressImageToFile(bitmap, new File(sdFile,"qualityCompress.jpeg"));
}
//質(zhì)量壓縮
public static void compressImageToFile(Bitmap bmp,File file){
//0~100
int quality = 50;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, quality , baos );
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
2.尺寸壓縮
- 縮放像素減少圖片的大小
- 使用場(chǎng)景:緩存縮略圖的時(shí)候(頭像處理)
public void sizeCompress(View v){
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
compressBitmapToFileBySize(bitmap, new File(sdFile,"sizeCompress.jpeg"));
}
/**
* 2.尺寸壓縮
* 通過減少單位尺寸的像素值,正真意義上的降低像素
* 使用場(chǎng)景:緩存縮略圖的時(shí)候(頭像處理)
*
* @param bmp
* @param file
*/
public static void compressBitmapToFileBySize(Bitmap bmp,File file){
//壓縮尺寸倍數(shù),值越大,圖片的尺寸就越小
int ratio = 4;
Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, bmp.getHeight()/ratio, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
canvas.drawBitmap(bmp, null, rect , null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
3.采樣率
- 降低圖片像素來減小圖片的大小
/**
* 設(shè)置圖片的采樣率,降低圖片像素
* @param filePath
* @param file
*/
public static void compressBitmap(String filePath, File file){
// 數(shù)值越高,圖片像素越低,必須是2的倍數(shù)
int inSampleSize = 8;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
// options.inJustDecodeBounds = true;//為true的時(shí)候不會(huì)真正加載圖片,而是得到圖片的寬高信息。
//采樣率
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把壓縮后的數(shù)據(jù)存放到baos中
bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
try {
if(file.exists())
{
file.delete();
}
else {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
4.通過libjpeg方式進(jìn)行壓縮
為什么需要使用libjpeg的方式了
這和android使用的圖片處理引擎有關(guān)。pc和mac上使用的都是JPEG處理引擎。但是android為了減低內(nèi)存的開銷使用的是skia。
有什么區(qū)別了?區(qū)別在于編碼方式j(luò)peg使用的是哈弗曼編碼,android沒有使用。這也是同一張圖片android和ios顯示不同的原因。
-
所有我們可以把編碼的方式使用哈弗曼編碼進(jìn)行圖片的處理。
哈弗曼編碼參考此文章
我們圍繞這幾個(gè)問題進(jìn)行討論
- 使用到的jpeg庫(kù)怎么編譯?
A.庫(kù)文件的地址:https://github.com/bither/bither-android-lib
B. 可以直接下載進(jìn)行編譯。
C.也可以使用git clone最新版libjpeg-trubo的Android版分支
git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android
- 編譯
A. 克隆下來的文件夾名為libjpeg-turbo,所以我們?cè)谑褂肗DK編譯前需要將文件夾命名為JNI:
mv libjpeg-turbo jni
B. 將libjpeg-turbo文件夾下所有的文件復(fù)制到Android項(xiàng)目下的jni文件夾內(nèi)
使用NDK編譯時(shí),這里需要注意的是APP_ABI這個(gè)參數(shù),若需要在不同的平臺(tái)下運(yùn)行,則需要設(shè)置平臺(tái)參數(shù)如例所示,將編譯出兩個(gè)cpu平臺(tái)的so庫(kù),不同的平臺(tái)用逗號(hào)分隔:
ndk-build APP_ABI=armeabi-v7a,armeabi
C. 這時(shí)就可以看到在jni同目錄下會(huì)生成libs與objs兩個(gè)文件夾,生成的.so類庫(kù)就在libs文件夾內(nèi)。
- 怎么調(diào)用庫(kù)?
- 編譯成功后可以把需要*h拷貝到項(xiàng)目中
A.庫(kù)文件中android文件夾 是需要的h文件。

B.并把編譯的*so文件拷貝到對(duì)應(yīng)的文件夾中

C.build.gadle中添加
//so文件加載,需要添加下面代碼
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
D.配置NDK 和 JAVAH

F:\android-ndk-r9b\ndk-build.cmd
NDK_LIBS_OUT=$ModuleFileDir$\libs
H:\KJDJ\NativeImgCompress1\app\src\main\jni

- $JDKPath$/bin/javah
- -encoding UTF-8 -d ../jni -jni $FileClass$
- directory: $SourcepathEntry$..\java
E.編寫android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := libjpeg
LOCAL_SRC_FILES := libjpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := bitherjni
LOCAL_SRC_FILES := bitherlibjni.c
LOCAL_SHARED_LIBRARIES := libjpeg
LOCAL_LDLIBS := -ljnigraphics -llog
LOCAL_C_INCLUDES := $(LOCAL_PATH) \
$(LOCAL_PATH)/libjpeg-turbo \
$(LOCAL_PATH)/libjpeg-turbo/android
include $(BUILD_SHARED_LIBRARY)
- 程序中怎么使用?
A.編寫ndk代碼
/*
* Copyright 2014 http://Bither.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
#include "jpeglib.h"
#include "cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
#include "jversion.h" /* for version message */
#include "config.h"
#define LOG_TAG "jni"
#define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define true 1
#define false 0
typedef uint8_t BYTE;
char *error;
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
error=myerr->pub.jpeg_message_table[myerr->pub.msg_code];
LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
// LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
// LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
// LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE* data, int w, int h, int quality,
const char* outfilename, jboolean optimize) {
int nComponent = 3;
//jpeg的結(jié)構(gòu)體,保存的比如寬、高、位深、圖片格式等信息,相當(dāng)于java的類
struct jpeg_compress_struct jcs;
//當(dāng)讀完整個(gè)文件的時(shí)候就會(huì)回調(diào)my_error_exit這個(gè)退出方法。setjmp是一個(gè)系統(tǒng)級(jí)函數(shù),是一個(gè)回調(diào)。
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
if (setjmp(jem.setjmp_buffer)) {
return 0;
}
//初始化jsc結(jié)構(gòu)體
jpeg_create_compress(&jcs);
//打開輸出文件 wb:可寫byte
FILE* f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//設(shè)置結(jié)構(gòu)體的文件路徑
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;//設(shè)置寬高
jcs.image_height = h;
if (optimize) {
LOGI("optimize==ture");
} else {
LOGI("optimize==false");
}
//看源碼注釋,設(shè)置哈夫曼編碼:/* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
/* 顏色的組成 rgb,三個(gè) # of color components in input image */
jcs.input_components = nComponent;
//設(shè)置結(jié)構(gòu)體的顏色空間為rgb
if (nComponent == 1)
jcs.in_color_space = JCS_GRAYSCALE;
else
jcs.in_color_space = JCS_RGB;
//全部設(shè)置默認(rèn)參數(shù)/* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼表數(shù)據(jù)計(jì)算 品質(zhì)相差5-10倍
jcs.optimize_coding = optimize;
//設(shè)置質(zhì)量
jpeg_set_quality(&jcs, quality, true);
//開始?jí)嚎s,(是否寫入全部像素)
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
//一行的rgb數(shù)量
row_stride = jcs.image_width * nComponent;
//一行一行遍歷
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];
//此方法會(huì)將jcs.next_scanline加1
jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:寫入的行數(shù)
}
if (jcs.optimize_coding) {
LOGI("optimize==ture");
} else {
LOGI("optimize==false");
}
jpeg_finish_compress(&jcs);//結(jié)束
jpeg_destroy_compress(&jcs);//銷毀 回收內(nèi)存
fclose(f);//關(guān)閉文件
return 1;
}
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} rgb;
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
char* rtn = NULL;
jsize alen = (*env)->GetArrayLength(env, barr);
jbyte* ba = (*env)->GetByteArrayElements(env, barr, 0);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba, 0);
return rtn;
}
jbyteArray stoJstring(JNIEnv* env, const char* pat,int len) {
jbyteArray bytes = (*env)->NewByteArray(env, len);
(*env)->SetByteArrayRegion(env, bytes, 0, len, pat);
jsize alen = (*env)->GetArrayLength(env, bytes);
return bytes;
}
jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
jobject thiz, jobject bitmapcolor, int w, int h, int quality,
jbyteArray fileNameStr, jboolean optimize) {
AndroidBitmapInfo infocolor;
BYTE* pixelscolor;
int ret;
BYTE * data;
BYTE *tmpdata;
char * fileName = jstrinTostring(env, fileNameStr);
if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return (*env)->NewStringUTF(env, "0");;
}
if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
BYTE r, g, b;
data = NULL;
data = malloc(w * h * 3);
tmpdata = data;
int j = 0, i = 0;
int color;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
//解決掉alpha
//獲取二維數(shù)組的每一個(gè)像素信息(四個(gè)部分a/r/g/b)的首地址
color = *((int *)pixelscolor);//通過地址取值
//0~255:
// a = ((color & 0xFF000000) >> 24);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = color & 0x000000FF;
//改值!?。?---保存到data數(shù)據(jù)里面
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data = data + 3;
//一個(gè)像素包括argb四個(gè)值,每+4就是取下一個(gè)像素點(diǎn)
pixelscolor += 4;
}
}
AndroidBitmap_unlockPixels(env, bitmapcolor);
int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
free(tmpdata);
if(resultCode==0){
jstring result=(*env)->NewStringUTF(env, error);
error=NULL;
return result;
}
return (*env)->NewStringUTF(env, "1"); //success
}
void jstringTostring(JNIEnv* env, jstring jstr, char * output, int * de_len) {
*output = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode);
jsize alen = (*env)->GetArrayLength(env, barr);
*de_len = alen;
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
if (alen > 0) {
output = (char*) malloc(alen + 1);
memcpy(output, ba, alen);
output[alen] = 0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba, 0);
}
B. 編寫本地調(diào)用方法
package net.bither.util;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.media.ExifInterface;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* JNI圖片壓縮工具類
* @version V1.0.0
*/
public class NativeUtil {
private static int DEFAULT_QUALITY = 95;
/**
* @Description: JNI基本壓縮
* @param bit
* bitmap對(duì)象
* @param fileName
* 指定保存目錄名
* @param optimize
* 是否采用哈弗曼表數(shù)據(jù)計(jì)算 品質(zhì)相差5-10倍
*/
public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
}
/**
* @Description: 通過JNI圖片壓縮把Bitmap保存到指定目錄
* @param image
* bitmap對(duì)象
* @param filePath
* 要保存的指定目錄
*/
public static void compressBitmap(Bitmap image, String filePath) {
// 最大圖片大小 150KB
int maxSize = 150;
// 獲取尺寸壓縮倍數(shù)
int ratio = NativeUtil.getRatioSize(image.getWidth(),image.getHeight());
// 壓縮Bitmap到對(duì)應(yīng)尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio,Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image,null,rect,null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中
int options = 100;
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 循環(huán)判斷如果壓縮后圖片是否大于100kb,大于繼續(xù)壓縮
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都減少10
options -= 10;
// 這里壓縮options%,把壓縮后的數(shù)據(jù)存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// JNI保存圖片到SD卡 這個(gè)關(guān)鍵
NativeUtil.saveBitmap(result, options, filePath, true);
// 釋放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
}
/**
* @Description: 通過JNI圖片壓縮把Bitmap保存到指定目錄
* @param curFilePath
* 當(dāng)前圖片文件地址
* @param targetFilePath
* 要保存的圖片文件地址
*/
public static void compressBitmap(String curFilePath, String targetFilePath) {
// 最大圖片大小 150KB
int maxSize = 150;
//根據(jù)地址獲取bitmap
Bitmap result = getBitmapFromFile(curFilePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中
int quality = 100;
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// 循環(huán)判斷如果壓縮后圖片是否大于100kb,大于繼續(xù)壓縮
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都減少10
quality -= 10;
// 這里壓縮quality,把壓縮后的數(shù)據(jù)存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
}
// JNI保存圖片到SD卡 這個(gè)關(guān)鍵
NativeUtil.saveBitmap(result, quality, targetFilePath, true);
// 釋放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
}
/**
* 計(jì)算縮放比
* @param bitWidth 當(dāng)前圖片寬度
* @param bitHeight 當(dāng)前圖片高度
* @return int 縮放比
*/
public static int getRatioSize(int bitWidth, int bitHeight) {
// 圖片最大分辨率
int imageHeight = 1280;
int imageWidth = 960;
// 縮放比
int ratio = 1;
// 縮放比,由于是固定比例縮放,只用高或者寬其中一個(gè)數(shù)據(jù)進(jìn)行計(jì)算即可
if (bitWidth > bitHeight && bitWidth > imageWidth) {
// 如果圖片寬度比高度大,以寬度為基準(zhǔn)
ratio = bitWidth / imageWidth;
} else if (bitWidth < bitHeight && bitHeight > imageHeight) {
// 如果圖片高度比寬度大,以高度為基準(zhǔn)
ratio = bitHeight / imageHeight;
}
// 最小比率為1
if (ratio <= 0)
ratio = 1;
return ratio;
}
/**
* 通過文件路徑讀獲取Bitmap防止OOM以及解決圖片旋轉(zhuǎn)問題
* @param filePath
* @return
*/
public static Bitmap getBitmapFromFile(String filePath){
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只讀邊,不讀內(nèi)容
BitmapFactory.decodeFile(filePath, newOpts);
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 獲取尺寸壓縮倍數(shù)
newOpts.inSampleSize = NativeUtil.getRatioSize(w,h);
newOpts.inJustDecodeBounds = false;//讀取所有內(nèi)容
newOpts.inDither = false;
newOpts.inPurgeable=true;
newOpts.inInputShareable=true;
newOpts.inTempStorage = new byte[32 * 1024];
Bitmap bitmap = null;
File file = new File(filePath);
FileInputStream fs = null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if(fs!=null){
bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(),null,newOpts);
//旋轉(zhuǎn)圖片
int photoDegree = readPictureDegree(filePath);
if(photoDegree != 0){
Matrix matrix = new Matrix();
matrix.postRotate(photoDegree);
// 創(chuàng)建新的圖片
bitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
/**
*
* 讀取圖片屬性:旋轉(zhuǎn)的角度
* @param path 圖片絕對(duì)路徑
* @return degree旋轉(zhuǎn)的角度
*/
public static int readPictureDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
/**
* 調(diào)用native方法
* @Description:函數(shù)描述
* @param bit
* @param quality
* @param fileName
* @param optimize
*/
private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
}
/**
* 調(diào)用底層 bitherlibjni.c中的方法
* @Description:函數(shù)描述
* @param bit
* @param w
* @param h
* @param quality
* @param fileNameBytes
* @param optimize
* @return
*/
private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);
/**
* 加載lib下兩個(gè)so文件
*/
static {
System.loadLibrary("jpeg");
System.loadLibrary("bitherjni");
}
/**
* 1. 質(zhì)量壓縮
設(shè)置bitmap options屬性,降低圖片的質(zhì)量,像素不會(huì)減少
第一個(gè)參數(shù)為需要壓縮的bitmap圖片對(duì)象,第二個(gè)參數(shù)為壓縮后圖片保存的位置
設(shè)置options 屬性0-100,來實(shí)現(xiàn)壓縮
* @param bmp
* @param file
*/
public static void compressImageToFile(Bitmap bmp,File file) {
// 0-100 100為不壓縮
int options = 20;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把壓縮后的數(shù)據(jù)存放到baos中
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* 2. 尺寸壓縮
通過縮放圖片像素來減少圖片占用內(nèi)存大小
* @param bmp
* @param file
*/
public static void compressBitmapToFile(Bitmap bmp, File file){
// 尺寸壓縮倍數(shù),值越大,圖片尺寸越小
int ratio = 8;
// 壓縮Bitmap到對(duì)應(yīng)尺寸
Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
canvas.drawBitmap(bmp, null, rect, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把壓縮后的數(shù)據(jù)存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 3.設(shè)置圖片的采樣率,降低圖片像素
* @param filePath
* @param file
*/
public static void compressBitmap(String filePath, File file){
// 數(shù)值越高,圖片像素越低
int inSampleSize = 8;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
// options.inJustDecodeBounds = true;//為true的時(shí)候不會(huì)真正加載圖片,而是得到圖片的寬高信息。
//采樣率
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把壓縮后的數(shù)據(jù)存放到baos中
bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
try {
if(file.exists())
{
file.delete();
}
else {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
C. 調(diào)用方法
public void compressImage(String path) {
File saveFile = new File(getExternalCacheDir(), "終極壓縮.jpg");
Bitmap bitmap = getBitmapFromFile(path);
Log.e("===compressImage===", "====開始==壓縮==saveFile==" + saveFile.getAbsolutePath());
NativeUtil.compressBitmap(bitmap, saveFile.getAbsolutePath());
Log.e("===compressImage===", "====完成==壓縮==saveFile==" + saveFile.getAbsolutePath());
File saveFile1 = new File(getExternalCacheDir(), "質(zhì)量壓縮.jpg");
NativeUtil.compressImageToFile(bitmap,saveFile1);
File saveFile2 = new File(getExternalCacheDir(), "尺寸壓縮.jpg");
NativeUtil.compressBitmapToFile(bitmap,saveFile2);
File saveFile3 = new File(getExternalCacheDir(), "采樣率壓縮.jpg");
File f = new File(path);
if(f.exists()){
NativeUtil.compressBitmap(f.getAbsolutePath(),saveFile3);
}else{
Log.e("===compressImage===", "采樣率壓縮找不到這個(gè)代碼里面寫死的圖片哦~~~~");
}
}