性能优化12_Android高清显示图片:哈夫曼算法

本文深入探讨Android图片处理引擎Skia的优化策略,特别是在哈夫曼编码的替代方案中,介绍了如何利用定长编码提高性能。文章详细讲解了哈夫曼编码的引入原因,以及在图片信息处理中的应用。此外,还提供了使用libjpeg库进行JPEG图片压缩的具体步骤,包括C层图片处理逻辑的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android性能优化汇总

一 原理

同样的图片,在ios手机上显示的效果要比安卓手机上效果要好?

  • 图片处理引擎用的是pc上的图片处理引擎skia
  • 去掉一个编码算法—哈夫曼算法。采用定长编码算法
    **原因:**当时由于CPU和内存在手机上都非常吃紧 性能差,由于哈夫曼算法非常吃CPU,被迫用了其他的算法。
    需求:,随着安卓设备硬件水平的提升,可以在性能的安卓设备上使用一些比较耗费性能的算法,使得图片显示的效果更佳
    **实现方式:**绕过安卓Bitmap API层,来自己编码实现----修复使用哈夫曼算法

二 哈夫曼编码引入原因

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

  1. 假设有五个基本字符:
    a b c d e
  2. 要表示下面一段内容
    abcde acdbe bacde ……
  3. 用3位来表示一个字符信息,属于定长编码的最优。
    a:001
    b:010
    c:011
    d:100
    e:101
  4. 最终结果:
    101010100011100

如果使用:加权信息编码
a:80%
b:10%
c:10%
d:0%
e:0%
则对应的
a:01
b:10
c:11
优化后的abc:01 10 11
优化前的abc:001 010 011

最终结果:
abcde
001 010 011 100 101
这种情况下,编码就可以优化了

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

三 引入下载JPEG引擎使用的库—libjpeg库

http://www.ijg.org/

基于该引擎来做一定的开发----自己实现编码。
略过:NDK环境变量,自行百度

  1. 导入库文件libjpegbither.so 和头文件
    在这里插入图片描述
  2. 定义native方法

JniUtil:

 public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
                                               boolean optimize);
  1. 编译生成.h头文件
    命令行:javac编译JniUtils.java生产.class文件
C:\rj\workspace\optimization\app\src\main\java\com\bpj\optimization\optimization> javac -classpath C:\rj\sdk\platforms\android-29\android.jar JniUtil.java

注意:

  • JniUtils不能用中文注释,可编译头再添加
  • 命令行位置:JniUtils.java的位置
  • 如果JniUtils有import 记得带上jar包 -classpath +jar包路径

生成.h头文件

javah -classpath +(jar包) -jni + (包名+类名)
  1. 创建.cpp文件
    JniUtil.cpp

  2. 写代码
    C++: XX.cpp
    C: XX.c

四 c层实现图片处理具体逻辑

  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. 释放资源
    代码:
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_bpj_optimization_optimization_JniUtil_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");
}

五 Demo

Lsn12Activity

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值