用过ios手机的同学应该很明显感觉到,ios拍照1M的图片要比安卓拍照排出来的5M的图片还要清晰。这是为什么呢?

这得了解android底层是如何对图片进行处理的.

当时谷歌开发Android的时候,考虑了大部分手机的配置并没有那么高,所以对图片处理是使用的Skia这个开源库。当然这个库的底层还是是用的jpeg对图片进行压缩处理。但是为了能够适配低端的手机(这里的低端是指以前的硬件配置不高的手机,CPU和内存在手机上都非常吃紧 性能差),由于哈夫曼算法非常吃CPU,被迫用了其他的算法。所以Skia在进行图片处理并没有去使用压缩图像过程中基于图像数据计算哈弗曼表(关于图片压缩中的哈弗曼表,请自行查阅相关资料),但是解码还是保留了哈夫曼算法。这就导致了图片处理后文件变大了。

我们使用微信发图片的时候,会发现发出去的图片明显比原图小很大,但是效果好像差不多,那又是为什么呢,经过了怎样的压缩呢,其实他们是用了哈夫曼算法进行图片压缩;接下来我们就用这算法进行图片压缩:

仿微信终级压缩
1.下载JPEG引擎使用的库—libjpeg库

libjpeg是一个被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现库.说它使用广泛,是因为它跨了很多平台。比如Linux平台、JDK、Android和其他库如tess-two等等。libjpeg库下载地址;

2.编译android中libjpeg库使用库

编译android中libjpeg库使用库,这里我暂时先不做详细的请解,后期我会做一个ndk开发专题进行详细请解,编译后生成的libjpegbither.so和.h头文件,看如下图:

3.导入libjpeg库libjpegbither.so及头文件

在项目文件夹新建jni文件夹,把刚才生成的libjpegbither.so及头文件放在此文件夹下;

4.新建BitmapCompressUtils
    • 写一个native方法,调用c方法

  1. /**
  2. * 调用底层 bitherlibjni.c中的方法
  3. *
  4. * @param bit
  5. * @param w
  6. * @param h
  7. * @param quality
  8. * @param fileNameBytes
  9. * @param optimize
  10. * @return
  11. * @Description:函数描述
  12. */
  13. public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
  14. boolean optimize);

引入库lib下二个so文件

  1. /**
  2. * 加载lib下两个so文件
  3. */
  4. static {
  5. System.loadLibrary("jpegbither");
  6. System.loadLibrary("bitherjni");
  7. }

写一个方法,调用native方法,便于java层调用

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

4.编写bitherjni.cpp

  1. #include "bitherlibjni.h"
  2. #include <string.h>
  3. #include <android/bitmap.h>
  4. #include <android/log.h>
  5. #include <stdio.h>
  6. #include <setjmp.h>
  7. #include <math.h>
  8. #include <stdint.h>
  9. #include <time.h>
  10. //统一编译方式
  11. extern "C" {
  12. #include "jpeg/jpeglib.h"
  13. #include "jpeg/cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
  14. #include "jpeg/jversion.h" /* for version message */
  15. #include "jpeg/android/config.h"
  16. }
  17. #define LOG_TAG "jni"
  18. #define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
  19. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
  20. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
  21. #define true 1
  22. #define false 0
  23. typedef uint8_t BYTE;
  24. char *error;
  25. struct my_error_mgr {
  26. struct jpeg_error_mgr pub;
  27. jmp_buf setjmp_buffer;
  28. };
  29. typedef struct my_error_mgr * my_error_ptr;
  30. METHODDEF(void)
  31. my_error_exit (j_common_ptr cinfo)
  32. {
  33. my_error_ptr myerr = (my_error_ptr) cinfo->err;
  34. (*cinfo->err->output_message) (cinfo);
  35. error=(char*)myerr->pub.jpeg_message_table[myerr- >pub.msg_code];
  36. LOGE("jpeg_message_table[%d]:%s", myerr- >pub.msg_code,myerr->pub.jpeg_message_table[myerr- >pub.msg_code]);
  37. // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
  38. // LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
  39. // LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
  40. longjmp(myerr->setjmp_buffer, );
  41. }
  42. int generateJPEG(BYTE* data, int w, int h, int quality,
  43. const char* outfilename, jboolean optimize) {
  44. //jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
  45. struct jpeg_compress_struct jcs;
  46. //当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
  47. struct my_error_mgr jem;
  48. jcs.err = jpeg_std_error(&jem.pub);
  49. jem.pub.error_exit = my_error_exit;
  50. if (setjmp(jem.setjmp_buffer)) {
  51. return ;
  52. }
  53. //初始化jsc结构体
  54. jpeg_create_compress(&jcs);
  55. //打开输出文件 wb:可写byte
  56. FILE* f = fopen(outfilename, "wb");
  57. if (f == NULL) {
  58. return ;
  59. }
  60. //设置结构体的文件路径
  61. jpeg_stdio_dest(&jcs, f);
  62. jcs.image_width = w;//设置宽高
  63. jcs.image_height = h;
  64. // if (optimize) {
  65. // LOGI("optimize==ture");
  66. // } else {
  67. // LOGI("optimize==false");
  68. // }
  69. //看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
  70. jcs.arith_code = false;
  71. int nComponent = ;
  72. /* 颜色的组成 rgb,三个 # of color components in input image */
  73. jcs.input_components = nComponent;
  74. //设置结构体的颜色空间为rgb
  75. jcs.in_color_space = JCS_RGB;
  76. // if (nComponent == 1)
  77. // jcs.in_color_space = JCS_GRAYSCALE;
  78. // else
  79. // jcs.in_color_space = JCS_RGB;
  80. //全部设置默认参数/* Default parameter setup for compression */
  81. jpeg_set_defaults(&jcs);
  82. //是否采用哈弗曼表数据计算 品质相差5-10倍
  83. jcs.optimize_coding = optimize;
  84. //设置质量
  85. jpeg_set_quality(&jcs, quality, true);
  86. //开始压缩,(是否写入全部像素)
  87. jpeg_start_compress(&jcs, TRUE);
  88. JSAMPROW row_pointer[];
  89. int row_stride;
  90. //一行的rgb数量
  91. row_stride = jcs.image_width * nComponent;
  92. //一行一行遍历
  93. while (jcs.next_scanline < jcs.image_height) {
  94. //得到一行的首地址
  95. row_pointer[] = &data[jcs.next_scanline * row_stride];
  96. //此方法会将jcs.next_scanline加1
  97. jpeg_write_scanlines(&jcs, row_pointer, );//row_pointer就是一行的首地址,1:写入的行数
  98. }
  99. jpeg_finish_compress(&jcs);//结束
  100. jpeg_destroy_compress(&jcs);//销毁 回收内存
  101. fclose(f);//关闭文件
  102. return ;
  103. }
  104. /**
  105. * byte数组转C的字符串
  106. */
  107. char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
  108. char* rtn = NULL;
  109. jsize alen = env->GetArrayLength( barr);
  110. jbyte* ba = env->GetByteArrayElements( barr, );
  111. if (alen > ) {
  112. rtn = (char*) malloc(alen + );
  113. memcpy(rtn, ba, alen);
  114. rtn[alen] = ;
  115. }
  116. env->ReleaseByteArrayElements( barr, ba, );
  117. return rtn;
  118. }
  119. jstring Java_net_bither_util_BitmapCompressUtils_compressBitmap(JNIEnv* env,
  120. jclass thiz, jobject bitmapcolor, int w, int h, int quality,
  121. jbyteArray fileNameStr, jboolean optimize) {
  122. BYTE *pixelscolor;
  123. //1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
  124. //处理bitmap图形信息方法1 锁定画布
  125. AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);
  126. //2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
  127. BYTE *data;
  128. BYTE r,g,b;
  129. data = (BYTE*)malloc(w*h*);//每一个像素都有三个信息RGB
  130. BYTE *tmpdata;
  131. tmpdata = data;//临时保存data的首地址
  132. int i=,j=;
  133. int color;
  134. for (i = ; i < h; ++i) {
  135. for (j = ; j < w; ++j) {
  136. //解决掉alpha
  137. //获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
  138. color = *((int *)pixelscolor);//通过地址取值
  139. //0~255:
  140. // a = ((color & 0xFF000000) >> 24);
  141. r = ((color & 0x00FF0000) >> );
  142. g = ((color & 0x0000FF00) >> );
  143. b = ((color & 0x000000FF));
  144. //改值!!!----保存到data数据里面
  145. *data = b;
  146. *(data+) = g;
  147. *(data+) = r;
  148. data = data + ;
  149. //一个像素包括argb四个值,每+4就是取下一个像素点
  150. pixelscolor += ;
  151. }
  152. }
  153. //处理bitmap图形信息方法2 解锁
  154. AndroidBitmap_unlockPixels(env,bitmapcolor);
  155. char* fileName = jstrinTostring(env,fileNameStr);
  156. //调用libjpeg核心方法实现压缩
  157. int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
  158. if(resultCode ==){
  159. jstring result = env->NewStringUTF("-1");
  160. return result;
  161. }
  162. return env->NewStringUTF("");
  163. }

JavanetbitherutilBitmapCompressUtils_compressBitmap就是java调用的方法,通过此方法就可以进行图片压缩,c代码我就不再讲解,注释写得很清楚了;

接下我再讲一下另外几个方案进行图片压缩;

质量压缩

质量压缩,这个只是降低了图片的质量,但是像素是不会减小的

  1. /** 质量压缩,
  2. * @param bitmap 要压缩的图片
  3. * @param file //压缩的图片保存地址
  4. * Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some
  5. * formats, like PNG which is lossless, will ignore the quality setting
  6. * quality (0-100) 100是不压缩,值越小,压缩得越厉害
  7. */
  8. public static void qualityCompressBitmap(Bitmap bitmap,File file){
  9. //字节数组输出流
  10. ByteArrayOutputStream stream =new ByteArrayOutputStream();
  11. int quality=;
  12. //图片压缩后把数据放在stream中
  13. bitmap.compress(Bitmap.CompressFormat.JPEG,quality, stream);
  14. try {
  15. FileOutputStream fileOutputStream=new FileOutputStream(file);
  16. //不断把stream的数据写文件输出流中去
  17. fileOutputStream.write(stream.toByteArray());
  18. fileOutputStream.flush();
  19. fileOutputStream.close();
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }

尺寸压缩

尺寸压缩,通过缩放图片的像素,减小图片占用内存大小,这个比如用于缩略图,今日头条文章上显示的小图就是这样实现的;

  1. /**尺寸压缩
  2. * @param bitmap 要压缩的图片
  3. * @param ratio 压缩比例,值越大,图片的尺寸就越小
  4. * @param file 压缩的图片保存地址
  5. */
  6. public static void sizeCompressBitmap(Bitmap bitmap,int ratio,File file){
  7. if (ratio<=){
  8. return;
  9. }
  10. Bitmap result=Bitmap.createBitmap(bitmap.getWidth()/ratio,bitmap.getHeight()/ratio, Bitmap.Config.ARGB_8888);
  11. Canvas canvas =new Canvas();
  12. Rect rect=new Rect(,,bitmap.getWidth()/ratio,bitmap.getHeight()/ratio);
  13. canvas.drawBitmap(bitmap,null,rect,null);
  14. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  15. // 把压缩后的数据存放到baos中
  16. result.compress(Bitmap.CompressFormat.JPEG, ,baos);
  17. try {
  18. FileOutputStream fos = new FileOutputStream(file);
  19. fos.write(baos.toByteArray());
  20. fos.flush();
  21. fos.close();
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. }

采样率压缩

  1. /** 采样率压缩
  2. * @param filePath 压缩图
  3. * @param file 压缩的图片保存地址
  4. */
  5. public static void pixeCompressBitmap(String filePath, File file){
  6. //采样率,数值越高,图片像素越低
  7. int inSampleSize=;
  8. BitmapFactory.Options osts=new BitmapFactory.Options();
  9. osts.inSampleSize=inSampleSize;
  10. //inJustDecodeBounds设为True时,不会真正加载图片,而是得到图片的宽高信息。
  11. osts.inJustDecodeBounds=false;
  12. Bitmap bitmap= BitmapFactory.decodeFile(filePath,osts);
  13. ByteArrayOutputStream stream =new ByteArrayOutputStream();
  14. bitmap.compress(Bitmap.CompressFormat.JPEG,,stream);
  15. try {
  16. if (file.exists()){
  17. file.delete();
  18. }else{
  19. file.createNewFile();
  20. }
  21. FileOutputStream fileOutputStream=new FileOutputStream(file);
  22. fileOutputStream.write(stream.toByteArray());
  23. fileOutputStream.flush();
  24. fileOutputStream.close();
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. }

总结:以上几种方案对图片进行压缩,各有优缺点,根据实际场景来选择方案来进行图片压缩.

Android仿微信高效压缩图片(libjpeg)的更多相关文章

  1. Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等

    仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...

  2. Android 仿微信小视频录制

    Android 仿微信小视频录制 WechatShortVideo和WechatShortVideo文章

  3. Android 仿微信朋友圈添加图片

    github地址(欢迎下载Demo) https://github.com/zhouxu88/WXCircleAddPic 老习惯,先上图,着急用的朋友,直接带走Demo,先拿来用吧,毕竟老板催的紧, ...

  4. Android 仿微信朋友圈发动态功能(相册图片多选)

    代码分享 代码名称: 仿微信朋友圈发动态功能(相册图片多选) 代码描述: 仿微信朋友圈发动态功能(相册图片多选) 代码托管地址: http://www.apkbus.com/android-15276 ...

  5. Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果

    版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/1873 ...

  6. android仿微信红包动画、Kotlin综合应用、Xposed模块、炫酷下拉视觉、UC浏览器滑动动画等源码

    Android精选源码 仿微信打开红包旋转动画 使用Kotlin编写的Android应用,内容你想象不到 Android手机上的免Root Android系统日志Viewer 一个能让微信 Mater ...

  7. Android仿微信拍摄短视频

    近期做项目需要添加上传短视频功能,功能设置为类似于微信,点击开始拍摄,设置最长拍摄时间,经过研究最终实现了这个功能,下面就和大家分享一下,希望对你有帮助. 1.视频录制自定义控件: /** * 视频播 ...

  8. Android仿微信界面

    效果图 原理介绍 1.先绘制一个颜色(例如:粉红) 2.设置Mode=DST_IN 3.绘制我们这个可爱的小机器人 回答我,显示什么,是不是显示交集,交集是什么?交集是我们的小机器人的非透明区域,也就 ...

  9. Android 仿微信朋友圈发表图片拖拽和删除功能

    朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...

随机推荐

  1. pig 入门教程(1)

    出处:http://www.codelast.com/ 本文可以让刚接触pig的人对一些基础概念有个初步的了解. 本文大概是互联网上第一篇公开发表的且涵盖大量实际例子的Apache Pig中文教程(由 ...

  2. Servlet的一些细节

    由于客户端是通过URL地址访问web服务器的中的资源的,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet& ...

  3. 在Android中使用FlatBuffers(下篇)

    本文来自网易云社区. FlatBuffers编码数组 编码数组的过程如下: 先执行 startVector(),这个方法会记录数组的长度,处理元素的对齐,准备足够的空间,并设置nested,用于指示记 ...

  4. Boost Python学习笔记(三)

    你将学到什么 在C++中调用Python代码时的传参问题 基础类型 继续使用前面的项目,但是先修改下Python脚本(zoo.py),添加Add和Str函数,分别针对整数.浮点数和字符串参数的测试 d ...

  5. 【leetcode 239. 滑动窗口最大值】解题报告

    思路:滑动窗口的思想,只要是求连续子序列或者子串问题,都可用滑动窗口的思想 方法一: vector<int> maxSlidingWindow(vector<int>& ...

  6. [CentOS7] minimal安装后 出现 没有ifconfig 无法ping 无法yum could not retrieve mirrorlist http://mirrorlist.centos.org/

    刚以minimal方式安装完CentOS,打算看下ip,结果ifconfig没找到(后来得知可以用ip addr查看本机ip) 于是yum grouplist, 结果出现could not retri ...

  7. SCUT - 337 - 岩殿居蟹 - 线段树 - 树状数组

    https://scut.online/p/337 这个东西是个阶梯状的.那么可以考虑存两棵树,一棵树是阶梯的,另一棵树的平的,随便一减就是需要的阶梯. 优化之后貌似速度比树状数组还惊人. #incl ...

  8. python之02数据类型学习-作业练习

    题目: 购物车程序 salary = 5000 1. iphone6s 5800 2. mac book 9000 3. coffee 32 4. python book 80 5. bicyle 1 ...

  9. Solr 6.7学习笔记(02)-- 配置文件 managed-schema (schema.xml)(1)

    刚学Solr(版本6.7.0),新建一个core时,提示要求schema.xml文件,我找了半天也没在源码包中找到名为schema.xml的文件.这个版本其实用的是managed-schema文件,没 ...

  10. hdu6092(dp)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6092 题意: 输入格式为, 对于每组测试样例第一行输入两个数 n, m, 接下来一行输入B数组, 有 ...