十、Android性能优化之图片优化

本文详细介绍 Android 平台上的图片压缩与优化方法,包括质量压缩、尺寸压缩和采样率压缩等多种方式,并深入探讨了如何通过 JNI 实现对图片的高效压缩。

####一. 图片存在的几种形式: File 流的形式 Bitmap的形式---内存

####二、Bitmap的优化

图片压缩 BitmapFactory函数

BitmapFactory.Options:参数: inDensity:bitmap的像素密度 inTargetDensity:bitmap最终的像素密度

####压缩的几种方式 #####1.质量压缩 AB CD 压缩后 AA AA

注意:它其实只能实现对file的影响,对加载这个图片出来的bitmap内存是无法节省的,还是那么大。 * 因为bitmap在内存中的大小是按照像素计算的,也就是width*height,对于质量压缩,并不会改变图片的真实的像素(像素大小不会变)

#####2.尺寸压缩 bitmap内存是节省的

#####3.采样率压缩 如果我们想要加载一张大图到内存中,如果不进行压缩的话,那么很显然就会出现OOM的崩溃,

譬如我们加载一张5440*3000的大图到手机上面,如果不进行压缩处理的话,那么就会出现OOM。 错误日志如下

java.lang.OutOfMemoryErrorandroid.graphics.BitmapFactory.nativeDe
codeStream(Native 
Method)android.graphics.BitmapFactory.decodeStreamInternal(Bitmap
Factory.java:703)android.graphics.BitmapFactory.decodeStream(Bitm
apFactory.java:679)android.graphics.BitmapFactory.decodeFile(Bitma
pFactory.java:446)android.graphics.BitmapFactory.decodeFile(Bitmap
Factory.java:480)com.example.ly.bitmapdemo.MainActivity.onCreate(M
ainActivity.java:21)

复制代码

####三、基于该引擎来做一定的开发----自己实现压缩编码。 #####1.IOS拍照1M的图片要比安卓拍照排出来的5M的图片还要清晰? 都是在同一个环境下,保存的都是JPEG。

因为iOS采用了霍夫曼编码,优化文件大小。

#####2.Android图像处理引擎skia 95年 JPEG处理引擎,用于最初的在PC上面处理图片的引擎。

05年 skia开源的引擎, 开发了一套基于JPEG处理引擎的第二次开发。便于浏览器的使用。

07年, 安卓上面用的什么引擎? skia引擎,阉割版。 谷歌拿了skia 思考了半天做了一个决定,去掉一个编码算法---哈夫曼算法。采用定长编码算法。 但是解码还是保留了哈夫曼算法。 导致了图片处理后文件变大了。 理由:当时由于CPU和内存在手机上都非常吃紧 性能差,由于哈夫曼算法非常吃CPU,被迫用了其他的算法。

我们的优化: 绕过安卓Bitmap API层,来自己编码实现----修复使用哈夫曼算法。

argb 一个像素点包涵四个信息:alpha,red,green,blue

a b c d e

abcde acdbe bacde ……

101010100011100 a:001 b:010 c:011 d:100 e:101 用3位来表示一个字符信息,属于定长编码的最优。

abcde 001 010 011 100 101 加权信息编码

a:80% b:10% c:10% d:0% e:0% 这种情况,编码就可以优化了 a:01 b:10 c:11 优化后的abc:01 10 11 优化前的abc:001 010 011

问题来了: 如何得到每一个字母出现的权重? ######哈夫曼编码:需要去扫描真个信息(图片信息--每一个像素包括ARGB),药大量计算,很吃CPU。

#####3.采用jni方式(c、c++) ######解决方案:现在的手机CPU和内存性能提高了许多,因此可以把Google阉割掉的哈夫曼编码重新加上去。

1.导入库文件libjpegbither.so 下载JPEG引擎使用的库---libjpeg库 http://www.ijg.org/

2.导入头文件 图像处理工具类NativeUtil .java

public class NativeUtil {
	 private static int DEFAULT_QUALITY = 95;

	    /**
	     * @param bit      bitmap对象
	     * @param fileName 指定保存目录名
	     * @param optimize 是否采用哈弗曼表数据计算 品质相差5-10倍
	     * @Description: JNI基本压缩
	     */
	    public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
	        saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
	    }

	    /**
	     * @param image    bitmap对象
	     * @param filePath 要保存的指定目录
	     * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
	     */
	    public static void compressBitmap(Bitmap image, String filePath) {
	        ByteArrayOutputStream baos = new ByteArrayOutputStream();
	        // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
	        int options = 20;
	        // JNI调用保存图片到SD卡 这个关键
	        NativeUtil.saveBitmap(image, options, filePath, true);
	    }

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

	    /**
	     * 调用native方法
	     *
	     * @param bit
	     * @param quality
	     * @param fileName
	     * @param optimize
	     * @Description:函数描述
	     */
	    public static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
	        compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
	    }

	    /**
	     * 调用底层 bitherlibjni.c中的方法
	     *
	     * @param bit
	     * @param w
	     * @param h
	     * @param quality
	     * @param fileNameBytes
	     * @param optimize
	     * @return
	     * @Description:函数描述
	     */
	    public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
	                                                boolean optimize);

	    /**
	     * 加载lib下两个so文件
	     */
	    static {
	        System.loadLibrary("jpegbither");
	        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();
	        }
	    }


	    /**
	     * 设置图片的采样率,降低图片像素
	     * @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();
	        }
	    }
}

复制代码

根据NativeUtil .java中的compressBitmap接口

	    /**
	     * 调用底层 bitherlibjni.c中的方法
	     *
	     * @param bit
	     * @param w
	     * @param h
	     * @param quality
	     * @param fileNameBytes
	     * @param optimize
	     * @return
	     * @Description:函数描述
	     */
	    public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
	                                                boolean optimize);
复制代码

生成bitherlibjni.h头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_haocai_app_nativeimgcompress_utils_NativeUtil */

#ifndef _Included_com_haocai_app_nativeimgcompress_utils_NativeUtil
#define _Included_com_haocai_app_nativeimgcompress_utils_NativeUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_haocai_app_nativeimgcompress_utils_NativeUtil
 * Method:    compressBitmap
 * Signature: (Landroid/graphics/Bitmap;III[BZ)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_haocai_app_nativeimgcompress_utils_NativeUtil_compressBitmap
  (JNIEnv *, jclass, jobject, jint, jint, jint, jbyteArray, jboolean);

#ifdef __cplusplus
}
#endif
#endif
复制代码

#####怎样生成bitherlibjni.h头文件请参考超级简单的Android Studio jni 实现(无需命令行)

3.写mk文件 Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    :=jpegbither
LOCAL_SRC_FILES :=libjpegbither.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    :=bitherjni
LOCAL_SRC_FILES :=bitherlibjni.cpp
LOCAL_SHARED_LIBRARIES :=jpegbither
LOCAL_LDLIBS := -ljnigraphics -llog  
include $(BUILD_SHARED_LIBRARY)
复制代码

Applicatoin.mk

APP_ABI := armeabi-v7a armeabi 	#表示 编译目标 ABI(应用二进制接口)
APP_PLATFORM := android-14
复制代码

4.生成.so库调用

/*
 * 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 "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

//统一编译方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h"		/* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h"		/* for version message */
#include "jpeg/android/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=(char*)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) {

	//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;
	int nComponent = 3;
	/* 颜色的组成 rgb,三个 # of color components in input image */
	jcs.input_components = nComponent;
	//设置结构体的颜色空间为rgb
	jcs.in_color_space = JCS_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:写入的行数
	}
	jpeg_finish_compress(&jcs);//结束
	jpeg_destroy_compress(&jcs);//销毁 回收内存
	fclose(f);//关闭文件

	return 1;
}

/**
 * byte数组转C的字符串
 */
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
	char* rtn = NULL;
	jsize alen = env->GetArrayLength( barr);
	jbyte* ba = env->GetByteArrayElements( barr, 0);
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1);
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	env->ReleaseByteArrayElements( barr, ba, 0);
	return rtn;
}

jstring Java_com_haocai_app_nativeimgcompress_utils_NativeUtil_compressBitmap(JNIEnv* env,
		jclass thiz, jobject bitmapcolor, int w, int h, int quality,
		jbyteArray fileNameStr, jboolean optimize) {
	BYTE *pixelscolor;
	//1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
	//处理bitmap图形信息方法1 锁定画布
	AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);

	//2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
	BYTE *data;
	BYTE r,g,b;
	data = (BYTE*)malloc(w*h*3);//每一个像素都有三个信息RGB
	BYTE *tmpdata;
	tmpdata = data;//临时保存data的首地址
	int i=0,j=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;
		}
	}
	//处理bitmap图形信息方法2 解锁
	AndroidBitmap_unlockPixels(env,bitmapcolor);
	char* fileName = jstrinTostring(env,fileNameStr);
	//调用libjpeg核心方法实现压缩
	int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
	if(resultCode ==0){
		jstring result = env->NewStringUTF("-1");
		return result;
	}
	return env->NewStringUTF("1");
}
复制代码

1.将android的bitmap解码,并转换成RGB数据 一个图片信息---像素点(argb) alpha去掉 2.JPEG对象分配空间以及初始化 3.指定压缩数据源 4.获取文件信息 5.为压缩设置参数,比如图像大小、类型、颜色空间 boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ 6.开始压缩 jpeg_start_compress() 7.压缩结束 jpeg_finish_compress() 8.释放资源

5.调用

public class MainActivity extends Activity {
    public static final int REQUEST_PICK_IMAGE = 10011;
    public static final int REQUEST_KITKAT_PICK_IMAGE = 10012;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void pickFromGallery(View v) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"),
                    REQUEST_PICK_IMAGE);
        } else {
            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("image/*");
            startActivityForResult(intent, REQUEST_KITKAT_PICK_IMAGE);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {

                case REQUEST_PICK_IMAGE:
                    if (data != null) {
                        Uri uri = data.getData();
                        compressImage(uri);
                    } else {
                        Log.e("======", "========图片为空======");
                    }
                    break;
                case REQUEST_KITKAT_PICK_IMAGE:
                    if (data != null) {
                        Uri uri = ensureUriPermission(this, data);
                        compressImage(uri);
                    } else {
                        Log.e("======", "====-----==图片为空======");
                    }
                    break;
            }
        }
    }

    @SuppressWarnings("ResourceType")
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static Uri ensureUriPermission(Context context, Intent intent) {
        Uri uri = intent.getData();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            final int takeFlags = intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
            context.getContentResolver().takePersistableUriPermission(uri, takeFlags);
        }
        return uri;
    }


    public void compressImage(Uri uri) {
        Log.e("===compressImage===", "====开始====uri==" + uri.getPath());
        try {
            File saveFile = new File(getExternalCacheDir(), "终极压缩.jpg");
            Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);

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

            Log.e("===compressImage===", "====uri==" + uri.toString());

            File saveFile3 = new File(getExternalCacheDir(), "采样率压缩.jpg");

            File f = new File("/storage/sdcard0/DCIM/Camera/IMG_20161130_200251.jpg");
            if(f.exists()){
                NativeUtil.compressBitmap(f.getAbsolutePath(),saveFile3);
            }else{
                Log.e("===compressImage===", "采样率压缩找不到这个代码里面写死的图片哦~~~~");
            }


        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
复制代码

#####该项目源码下载

特别感谢: 动脑学院Ricky

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值