用过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方法

/**
* 调用底层 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文件

 /**
* 加载lib下两个so文件
*/
static {
System.loadLibrary("jpegbither");
System.loadLibrary("bitherjni");
}

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

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

4.编写bitherjni.cpp

#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, );
}
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 ;
}
//初始化jsc结构体
jpeg_create_compress(&jcs);
//打开输出文件 wb:可写byte
FILE* f = fopen(outfilename, "wb");
if (f == NULL) {
return ;
}
//设置结构体的文件路径
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 = ;
/* 颜色的组成 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[];
int row_stride;
//一行的rgb数量
row_stride = jcs.image_width * nComponent;
//一行一行遍历
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[] = &data[jcs.next_scanline * row_stride];
//此方法会将jcs.next_scanline加1
jpeg_write_scanlines(&jcs, row_pointer, );//row_pointer就是一行的首地址,1:写入的行数
}
jpeg_finish_compress(&jcs);//结束
jpeg_destroy_compress(&jcs);//销毁 回收内存
fclose(f);//关闭文件
return ;
}
/**
* byte数组转C的字符串
*/
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
char* rtn = NULL;
jsize alen = env->GetArrayLength( barr);
jbyte* ba = env->GetByteArrayElements( barr, );
if (alen > ) {
rtn = (char*) malloc(alen + );
memcpy(rtn, ba, alen);
rtn[alen] = ;
}
env->ReleaseByteArrayElements( barr, ba, );
return rtn;
}
jstring Java_net_bither_util_BitmapCompressUtils_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*);//每一个像素都有三个信息RGB
BYTE *tmpdata;
tmpdata = data;//临时保存data的首地址
int i=,j=;
int color;
for (i = ; i < h; ++i) {
for (j = ; j < w; ++j) {
//解决掉alpha
//获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
color = *((int *)pixelscolor);//通过地址取值
//0~255:
// a = ((color & 0xFF000000) >> 24);
r = ((color & 0x00FF0000) >> );
g = ((color & 0x0000FF00) >> );
b = ((color & 0x000000FF));
//改值!!!----保存到data数据里面
*data = b;
*(data+) = g;
*(data+) = r;
data = data + ;
//一个像素包括argb四个值,每+4就是取下一个像素点
pixelscolor += ;
}
}
//处理bitmap图形信息方法2 解锁
AndroidBitmap_unlockPixels(env,bitmapcolor);
char* fileName = jstrinTostring(env,fileNameStr);
//调用libjpeg核心方法实现压缩
int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
if(resultCode ==){
jstring result = env->NewStringUTF("-1");
return result;
}
return env->NewStringUTF("");
}

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

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

质量压缩

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

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

尺寸压缩

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

 /**尺寸压缩
* @param bitmap 要压缩的图片
* @param ratio 压缩比例,值越大,图片的尺寸就越小
* @param file 压缩的图片保存地址
*/
public static void sizeCompressBitmap(Bitmap bitmap,int ratio,File file){
if (ratio<=){
return;
}
Bitmap result=Bitmap.createBitmap(bitmap.getWidth()/ratio,bitmap.getHeight()/ratio, Bitmap.Config.ARGB_8888);
Canvas canvas =new Canvas();
Rect rect=new Rect(,,bitmap.getWidth()/ratio,bitmap.getHeight()/ratio);
canvas.drawBitmap(bitmap,null,rect,null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, ,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 pixeCompressBitmap(String filePath, File file){
//采样率,数值越高,图片像素越低
int inSampleSize=;
BitmapFactory.Options osts=new BitmapFactory.Options();
osts.inSampleSize=inSampleSize;
//inJustDecodeBounds设为True时,不会真正加载图片,而是得到图片的宽高信息。
osts.inJustDecodeBounds=false;
Bitmap bitmap= BitmapFactory.decodeFile(filePath,osts);
ByteArrayOutputStream stream =new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG,,stream);
try {
if (file.exists()){
file.delete();
}else{
file.createNewFile();
}
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(stream.toByteArray());
fileOutputStream.flush();
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

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

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. hadoop主节点(NameNode)备份策略以、恢复方法、操作步骤

    一.dits和fsimage      首先要提到两个文件edits和fsimage,下面来说说他们是做什么的. 集群中的名称节点(NameNode)会把文件系统的变化以追加保存到日志文件edits中 ...

  2. 8、linux-数字计算

    bash内置了对整数四则运算的支持,但是并不支持浮点运算 bc命令是一种支持任意精度的交互执行的计算器语言,而bc命令可以很方便的进行浮点运算,当然整数运算也不再话下 在bc工作环境下,可以使用以下计 ...

  3. scrollView用法

    在这里记下UIScrollView的用法,一来防止自己忘记,而来再通过这个回顾一下,发现一些新细节. UIScrollView的主要问题在布局上,我现在只用到了内容大小固定额也就是不是tableVie ...

  4. C# 写 LeetCode easy #26 Remove Duplicates from Sorted Array

    26.Remove Duplicates from Sorted Array Given a sorted array nums, remove the duplicates in-place suc ...

  5. PureUI(扩展版本)

    喜欢一个UI(pure,官网)不怎么更新(可能官方认为不需要更新).我自己做了扩展和修正,整个库下载地址:http://files.cnblogs.com/files/RainbowInTheSky/ ...

  6. NPOI office操作

    写excel FileStream file = new FileStream(@"test.xls",FileMode.Create); hssfworkbook.write(f ...

  7. mysql失效的几种情况

    1.如果查询条件中有or,即使查询的条件中带有索引也会失效,如果想使用or,又不想让索引失效,只能将or条件中的所有列都加上索引 2.like 查询一%开头用不上索引, 3.隐式转换会使索引失效 比如 ...

  8. return die exit 常用

    die()停止程序运行,输出内容exit是停止程序运行,不输出内容return是返回值die是遇到错误才停止exit是直接停止,并且不运行后续代码,exit()可以显示内容.return就是纯粹的返回 ...

  9. PCB生产企业自动化立体仓库/智能仓库库系统WMS/WCS解决方案

    PCB生产企业自动化立体仓库/智能仓库库系统WMS/WCS解决方案 自动化立体仓库智能仓储系统WMS/WCS重要性调查 调查1(物流成本占总生产成本比例数据)1979年英国的第一次调查表明,在从原材料 ...

  10. Jmeter-返回值乱码处理

    Jmeter安装目录/bin/jmeter.properties中sampleresult.default.encoding默认为ISO-8859-1,将参数修改为 sampleresult.defa ...