图片压缩技术

对于一个应用或多或少的都会使用到图片,如果图片过大就会很吃内存,造成oom的。使用在使用图片的时候我们可以使用图片压缩的方式对图片进行处理。使图片即合适又不占用大量的内存。我们从以下几个方面介绍。

1.图片的存在方式

  • 文件的方式
  • 流的方式
  • bitmap---存在内存中
  • 字符串的方式(base64便于加密)

2.android中压缩的api

android系统中提供了Bitmapfactory类对图片进行压缩。
对与bitmapfactory对图片进行编码有如下方式:
-BitmapFactory.decodeResource(...) // resource

  • BitmapFactory.decodeByteArray() // 字节数组
  • BitmapFactory.decodeFile() // 文件
  • BitmapFactory.decodeStream() // 流
  • BitmapFactory.decodeFileDescriptor() // FileDescriptor

这些函数都有最终调用的是decodeStream这个函数。然后调用本地方法,即c/C++进行编码(nativeDecodeStream)

在这些函数中都可以传入一个参数,BitmapFactory.Options对编码进行不同的操作。

  1. inDestiy : 密度,指每平方英寸中的像素数,在DisplayMetrics类中属性density的值为dpi/160
  2. inScreenDensity : 单位密度下可存在的点
  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 描述 占用内存(字节)
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占用内存计算

  1. Bitmap占用内存计算 = 图片最终显示出来的宽 * 图片最终显示出来的高 * 图片品质(Bitmap.Config的值)

    SDcard中A图片的分辨率为300 X 600,使用ARGB_8888的品质加载,那么这张图片占用的内存 = 300 * 600 * 4 = 720000(byte) = 0.686(mb)

  2. getByteCount()可以获取图片占用内存字节大小。

  3. 为什么计算的公式是图片最终显示出来的宽 * 图片最终显示出来的高,而不是,图片的宽和图片的高呢?

主要是这样的,Android为了适配不同分辨率的机型,对放到不同drawable下的图片,在创建Bitmap的过程中,进行了缩放判断,如果需要缩放的话,那么最终创建出来的图片宽和高都进行了修改。

原图大小为165221,图片放到drawable-hdpi目录下,手机分辨率为7201280:

  • 计算出图片是否需要缩放

  • density,如果不设置opts.inDensity的话,该值默认为160。如果图片放到drawable-hdpi目录下,该值为240,

  • targetDensity,如果不设置opts.inTargetDensity的话,该值默认为DisplayMetrics的densityDpi,注意该值是由手机自身设置的。比如720 X 1280分辨率的手机,该值为320;1080 X 1920分辨率的手机,该值为480

  • scale = (float) targetDensity / density; 图片放到drawable-hdpi目录下,手机分辨率为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

  • 图片占用内存 = 220 * 295 * 4 = 259600

4.其他参数

public Bitmap inBitmap;  //是否重用该Bitmap,注意使用条件,Bitmap的大小必须等于inBitmap,inMutable为true
public boolean inMutable;  //设置Bitmap是否可以更改
public boolean inJustDecodeBounds; // true时,decode不会创建Bitmap对象,但是可以获取图片的宽高
public int inSampleSize;  // 压缩比例,比如=4,代表宽高压缩成原来的1/4,注意该值必须>=1
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;  //Bitmap.Config,默认为ARGB_8888
public boolean inPremultiplied; //默认为true,一般不需要修改,如果想要修改图片原始编码数据,那么需要修改
public boolean inDither; //是否抖动,默认为false
public int inDensity; //Bitmap的像素密度
public int inTargetDensity; //Bitmap最终的像素密度(注意,inDensity,inTargetDensity影响图片的缩放度)
public int inScreenDensity; //当前屏幕的像素密度
public boolean inScaled; //是否支持缩放,默认为true,当设置了这个,Bitmap将会以inTargetDensity的值进行缩放
public boolean inPurgeable; //当存储Pixel的内存空间在系统内存不足时是否可以被回收
public boolean inInputShareable; //inPurgeable为true情况下才生效,是否可以共享一个InputStream
public boolean inPreferQualityOverSpeed; //为true则优先保证Bitmap质量其次是解码速度
public int outWidth; //Bitmap最终的宽
public int outHeight;  //Bitmap最终的高
public String outMimeType; //
public byte[] inTempStorage; //解码时的临时空间,建议16*1024

5.优化方法

大图加载。两种方法:1.通过BitmapRegionDecoder。进行局部显示。2.通过压缩图片。这里我们只介绍压缩,局部显示请参考

1.质量压缩

  • 降低图片的质量,但是像素没有减少
  • 原理:通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素,达到降低质量介绍文件大小的目的。减小了图片质量。
  • 注意:它其实只能实现对file的影响,对加载这个图片出来的bitmap内存是无法节省的,还是那么大。
  • 使用场景: 将图片压缩后保存到本地,或者将图片上传到服务器。根据实际需求来
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"));
    }

    //质量压缩
    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.尺寸压缩

  • 缩放像素减少图片的大小
  • 使用场景:缓存缩略图的时候(头像处理)
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.尺寸压缩
 * 通过减少单位尺寸的像素值,正真意义上的降低像素
 * 使用场景:缓存缩略图的时候(头像处理)
 *
 * @param bmp
 * @param file
 */
public static void compressBitmapToFileBySize(Bitmap bmp,File file){
    //压缩尺寸倍数,值越大,图片的尺寸就越小
    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.采样率

  • 降低图片像素来减小图片的大小
 /**
       * 设置图片的采样率,降低图片像素
       * @param filePath
       * @param file
       */
      public static void compressBitmap(String filePath, File file){
          // 数值越高,图片像素越低,必须是2的倍数
          int inSampleSize = 8;
          BitmapFactory.Options options = new BitmapFactory.Options();
          options.inJustDecodeBounds = false;
//          options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。
          //采样率
          options.inSampleSize = inSampleSize;
          Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          // 把压缩后的数据存放到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方式进行压缩

为什么需要使用libjpeg的方式了

  • 这和android使用的图片处理引擎有关。pc和mac上使用的都是JPEG处理引擎。但是android为了减低内存的开销使用的是skia。

  • 有什么区别了?区别在于编码方式jpeg使用的是哈弗曼编码,android没有使用。这也是同一张图片android和ios显示不同的原因。

  • 所有我们可以把编码的方式使用哈弗曼编码进行图片的处理。

    哈弗曼编码参考此文章

我们围绕这几个问题进行讨论

  1. 使用到的jpeg库怎么编译?
    A.库文件的地址:https://github.com/bither/bither-android-lib
    B. 可以直接下载进行编译。
    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,所以我们在使用NDK编译前需要将文件夹命名为JNI:
mv libjpeg-turbo jni

B. 将libjpeg-turbo文件夹下所有的文件复制到Android项目下的jni文件夹内
使用NDK编译时,这里需要注意的是APP_ABI这个参数,若需要在不同的平台下运行,则需要设置平台参数如例所示,将编译出两个cpu平台的so库,不同的平台用逗号分隔:

ndk-build APP_ABI=armeabi-v7a,armeabi

C. 这时就可以看到在jni同目录下会生成libs与objs两个文件夹,生成的.so类库就在libs文件夹内。

  1. 怎么调用库?
  • 编译成功后可以把需要*h拷贝到项目中
    A.库文件中android文件夹 是需要的h文件。
图片压缩技术_第1张图片
h文件

B.并把编译的*so文件拷贝到对应的文件夹中

图片压缩技术_第2张图片
LIB文件

C.build.gadle中添加

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

D.配置NDK 和 JAVAH

图片压缩技术_第3张图片
ndk配置

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

图片压缩技术_第4张图片
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
  struct jpeg_compress_struct jcs;

  //当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
  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结构体
  jpeg_create_compress(&jcs);
  //打开输出文件 wb:可写byte
  FILE* f = fopen(outfilename, "wb");
  if (f == NULL) {
    return 0;
  }
  //设置结构体的文件路径
  jpeg_stdio_dest(&jcs, f);
  jcs.image_width = w;//设置宽高
  jcs.image_height = h;
  if (optimize) {
    LOGI("optimize==ture");
  } else {
    LOGI("optimize==false");
  }

  //看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
  jcs.arith_code = false;
  /* 颜色的组成 rgb,三个 # of color components in input image */
  jcs.input_components = nComponent;
  //设置结构体的颜色空间为rgb
  if (nComponent == 1)
    jcs.in_color_space = JCS_GRAYSCALE;
  else
    jcs.in_color_space = JCS_RGB;

  //全部设置默认参数/* Default parameter setup for compression */
  jpeg_set_defaults(&jcs);
  //是否采用哈弗曼表数据计算 品质相差5-10倍
  jcs.optimize_coding = optimize;
  //设置质量
  jpeg_set_quality(&jcs, quality, true);
  //开始压缩,(是否写入全部像素)
  jpeg_start_compress(&jcs, TRUE);

  JSAMPROW row_pointer[1];
  int row_stride;
  //一行的rgb数量
  row_stride = jcs.image_width * nComponent;
  //一行一行遍历
  while (jcs.next_scanline < jcs.image_height) {
    //得到一行的首地址
    row_pointer[0] = &data[jcs.next_scanline * row_stride];

    //此方法会将jcs.next_scanline加1
    jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
  }

  if (jcs.optimize_coding) {
     LOGI("optimize==ture");
    } else {
     LOGI("optimize==false");
    }
  jpeg_finish_compress(&jcs);//结束
  jpeg_destroy_compress(&jcs);//销毁 回收内存
  fclose(f);//关闭文件

  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
     //获取二维数组的每一个像素信息(四个部分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数据里面
       *data = b;
     *(data + 1) = g;
     *(data + 2) = r;
     data = data + 3;
     //一个像素包括argb四个值,每+4就是取下一个像素点
       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. 编写本地调用方法

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对象
   * @param fileName
   *            指定保存目录名
   * @param optimize
   *            是否采用哈弗曼表数据计算 品质相差5-10倍
   */
  public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
    saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
  }

  /**
   * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
   * @param image
   *            bitmap对象
   * @param filePath
   *            要保存的指定目录
   */
  public static void compressBitmap(Bitmap image, String filePath) {
    // 最大图片大小 150KB
    int maxSize = 150;
    // 获取尺寸压缩倍数
    int ratio = NativeUtil.getRatioSize(image.getWidth(),image.getHeight());
    // 压缩Bitmap到对应尺寸
    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();
    // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
    int options = 100;
    result.compress(Bitmap.CompressFormat.JPEG, options, baos);
    // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
    while (baos.toByteArray().length / 1024 > maxSize) {
     // 重置baos即清空baos
     baos.reset();
     // 每次都减少10
     options -= 10;
     // 这里压缩options%,把压缩后的数据存放到baos中
       result.compress(Bitmap.CompressFormat.JPEG, options, baos);
    }
    // JNI保存图片到SD卡 这个关键
    NativeUtil.saveBitmap(result, options, filePath, true);
    // 释放Bitmap
    if (!result.isRecycled()) {
     result.recycle();
    }
  }

  /**
   * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
   * @param curFilePath
   *            当前图片文件地址
   * @param targetFilePath
   *            要保存的图片文件地址
   */
  public static void compressBitmap(String curFilePath, String targetFilePath) {
    // 最大图片大小 150KB
    int maxSize = 150;
    //根据地址获取bitmap
    Bitmap result = getBitmapFromFile(curFilePath);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
    int quality = 100;
    result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
    // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
    while (baos.toByteArray().length / 1024 > maxSize) {
     // 重置baos即清空baos
     baos.reset();
     // 每次都减少10
     quality -= 10;
     // 这里压缩quality,把压缩后的数据存放到baos中
       result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
    }
    // JNI保存图片到SD卡 这个关键
    NativeUtil.saveBitmap(result, quality, targetFilePath, true);
    // 释放Bitmap
    if (!result.isRecycled()) {
     result.recycle();
    }

  }

  /**
   * 计算缩放比
   * @param bitWidth 当前图片宽度
   * @param bitHeight 当前图片高度
   * @return int 缩放比
   */
  public static int getRatioSize(int bitWidth, int bitHeight) {
    // 图片最大分辨率
    int imageHeight = 1280;
    int imageWidth = 960;
    // 缩放比
    int ratio = 1;
    // 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
    if (bitWidth > bitHeight && bitWidth > imageWidth) {
     // 如果图片宽度比高度大,以宽度为基准
       ratio = bitWidth / imageWidth;
    } else if (bitWidth < bitHeight && bitHeight > imageHeight) {
     // 如果图片高度比宽度大,以高度为基准
       ratio = bitHeight / imageHeight;
    }
    // 最小比率为1
    if (ratio <= 0)
     ratio = 1;
    return ratio;
  }

  /**
   * 通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题
   * @param filePath
   * @return
   */
  public static Bitmap getBitmapFromFile(String filePath){
    BitmapFactory.Options newOpts = new BitmapFactory.Options();
    newOpts.inJustDecodeBounds = true;//只读边,不读内容  
    BitmapFactory.decodeFile(filePath, newOpts);
    int w = newOpts.outWidth;
    int h = newOpts.outHeight;
    // 获取尺寸压缩倍数
    newOpts.inSampleSize = NativeUtil.getRatioSize(w,h);
    newOpts.inJustDecodeBounds = false;//读取所有内容
    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);
       //旋转图片
         int photoDegree = readPictureDegree(filePath);
       if(photoDegree != 0){
         Matrix matrix = new Matrix();
         matrix.postRotate(photoDegree);
         // 创建新的图片
           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;
  }

  /**
   *
   * 读取图片属性:旋转的角度
   * @param path 图片绝对路径
   * @return degree旋转的角度
   */

  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;
  }

  /**
   * 调用native方法
   * @Description:函数描述
   * @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);
  }

  /**
   * 调用底层 bitherlibjni.c中的方法
   * @Description:函数描述
   * @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下两个so文件
   */
  static {
    System.loadLibrary("jpeg");
    System.loadLibrary("bitherjni");
  }


  /**
   * 1. 质量压缩
   设置bitmap options属性,降低图片的质量,像素不会减少
   第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
   设置options 属性0-100,来实现压缩
   * @param bmp
   * @param file
   */
  public static void compressImageToFile(Bitmap bmp,File file) {
    // 0-100 100为不压缩
    int options = 20;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把压缩后的数据存放到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. 尺寸压缩
   通过缩放图片像素来减少图片占用内存大小
   * @param bmp
   * @param file
   */

  public static void compressBitmapToFile(Bitmap bmp, File file){
    // 尺寸压缩倍数,值越大,图片尺寸越小
    int ratio = 8;
    // 压缩Bitmap到对应尺寸
    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();
    // 把压缩后的数据存放到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.设置图片的采样率,降低图片像素
   * @param filePath
   * @param file
   */
  public static void compressBitmap(String filePath, File file){
    // 数值越高,图片像素越低
    int inSampleSize = 8;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = false;
//          options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。
    //采样率
    options.inSampleSize = inSampleSize;
    Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把压缩后的数据存放到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. 调用方法

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(), "质量压缩.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===", "采样率压缩找不到这个代码里面写死的图片哦~~~~");
    }
}

代码下载

你可能感兴趣的:(图片压缩技术)