Android仿微信高效压缩图片(libjpeg)
用过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)的更多相关文章
- Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等
仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...
- Android 仿微信小视频录制
Android 仿微信小视频录制 WechatShortVideo和WechatShortVideo文章
- Android 仿微信朋友圈添加图片
github地址(欢迎下载Demo) https://github.com/zhouxu88/WXCircleAddPic 老习惯,先上图,着急用的朋友,直接带走Demo,先拿来用吧,毕竟老板催的紧, ...
- Android 仿微信朋友圈发动态功能(相册图片多选)
代码分享 代码名称: 仿微信朋友圈发动态功能(相册图片多选) 代码描述: 仿微信朋友圈发动态功能(相册图片多选) 代码托管地址: http://www.apkbus.com/android-15276 ...
- Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果
版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/1873 ...
- android仿微信红包动画、Kotlin综合应用、Xposed模块、炫酷下拉视觉、UC浏览器滑动动画等源码
Android精选源码 仿微信打开红包旋转动画 使用Kotlin编写的Android应用,内容你想象不到 Android手机上的免Root Android系统日志Viewer 一个能让微信 Mater ...
- Android仿微信拍摄短视频
近期做项目需要添加上传短视频功能,功能设置为类似于微信,点击开始拍摄,设置最长拍摄时间,经过研究最终实现了这个功能,下面就和大家分享一下,希望对你有帮助. 1.视频录制自定义控件: /** * 视频播 ...
- Android仿微信界面
效果图 原理介绍 1.先绘制一个颜色(例如:粉红) 2.设置Mode=DST_IN 3.绘制我们这个可爱的小机器人 回答我,显示什么,是不是显示交集,交集是什么?交集是我们的小机器人的非透明区域,也就 ...
- Android 仿微信朋友圈发表图片拖拽和删除功能
朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...
随机推荐
- Ubuntu12.04安装 vsftpd
Ubuntu12.04 FTP 的配置 ubuntu安装ftp服务器 1: 安装vsftpd ~$ sudo apt-get install vsftpd 2: 配置vsftpd 2.1 修改vs ...
- [CentOS7] iconv编程转换
声明:本文主要总结自:鸟哥的Linux私房菜-第九章.vim 程式編輯器,如有侵权,请通知博主 (-- 源自鸟哥的私房菜) 首先用Notepad++新建个文件来做这个实验,在Windows平台下新建个 ...
- CodeForces 137C【贪心+优先队列】
这种区间的贪心好像都出"烂"了? 不过还是想写一下... 先按照区间左端点排序一下,然后搞个优先队列维护当前最小的右端点. #include <bits/stdc++.h&g ...
- go培训课程都学什么?xorm框架学习系列(二):xorm结构体映射规则和表操作
上节内容我们学习了基本的xorm框架的知识和基础配置的相关信息.本节课内容我们继续学习相关的知识和相关操作. 名称映射规则 名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射. ...
- SQL Server 2012安装——.net framework 3.5离线安装
前言 电脑用着一直很不舒服,所以就决定对电脑重新配置一番,在装数据库这里,可谓是屡装屡败.自己感觉太麻烦了,于是每次数据库装失败后,就重装系统,然后配置上网文档,这样一来,弄得自己挺恶心,这次很明显成 ...
- 编写自定义GenericServlet
用途: 编写自定义GenericServlet类后,子类只需继承这个类,就可以直接使用ServletConfig的config对象和ServletConfig接口的方法,而不需要init()方法获取c ...
- Linux调优(网络)
定义socket接受缓冲大小 net.core.rmem_default = N #接受 net.core.rmem_max = N net.core.wmem_default = N #发送 net ...
- AT2657 Mole and Abandoned Mine
传送门 好神的状压dp啊 首先考虑一个性质,删掉之后的图一定是个联通图 并且每个点最多只与保留下来的那条路径上的一个点有边相连 然后设状态:\(f[s][t]\)代表当前联通块的点的状态为\(s\)和 ...
- 再看thinkphp5分页类使用
之前使用tp5的分页paginate类时只用到了第一个参数,也就是每页显示多少行 今天又仔细看了下手册和paginate类,发现paginate可传入的参数有很多,可以满足更多需求 比如可以指定分页的 ...
- 关于unique去重
嗯.... unique这个东西也是一个冷门知识..... 但是在有时候它还是比较好用的东西... 下面就在详细代码中看unique是如何实际应用的....它主要是用于数组去重 #include< ...