圖片壓縮技術(shù)

對(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)行不同的操作。

  1. inDestiy : 密度,指每平方英寸中的像素?cái)?shù),在DisplayMetrics類中屬性density的值為dpi/160
  2. inScreenDensity : 單位密度下可存在的點(diǎn)
  3. 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ì)算

  1. 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)

  2. getByteCount()可以獲取圖片占用內(nèi)存字節(jié)大小。

  3. 為什么計(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)行討論

  1. 使用到的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
  1. 編譯
    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)。

  1. 怎么調(diào)用庫(kù)?
  • 編譯成功后可以把需要*h拷貝到項(xiàng)目中
    A.庫(kù)文件中android文件夾 是需要的h文件。
h文件

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

LIB文件

C.build.gadle中添加

//so文件加載,需要添加下面代碼
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}

D.配置NDK 和 JAVAH

ndk配置

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

JAVAH配置
  • $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)
  1. 程序中怎么使用?
    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è)代碼里面寫死的圖片哦~~~~");
    }
}

代碼下載

最后編輯于
?著作權(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)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,036評(píng)論 25 709
  • 先發(fā)一張昨天去看我雷哥演唱會(huì)的皂片然后再說正文哈哈。 簡(jiǎn)介 由于工作原因,boss下達(dá)的任務(wù)就大概說了對(duì)圖片進(jìn)行壓...
    我叫王菜鳥閱讀 5,449評(píng)論 2 16
  • 2021期待與你一起共事,點(diǎn)擊查看崗位[http://www.itdecent.cn/p/6f4d67fa406...
    閑庭閱讀 17,066評(píng)論 0 75
  • Android基礎(chǔ)及相關(guān)機(jī)制 Android Context 上下文 你必須知道的一切 Android中子線程真的...
    楷桐閱讀 2,116評(píng)論 1 30
  • 早晨是一日之始,本該心情愉悅快樂,但我早晨起來永遠(yuǎn)處與那種焦急之狀。搞的我早晨確實(shí)心情不好。 咋日重復(fù)式的模式又開...
    秦聰1閱讀 269評(píng)論 0 0

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