动态库的编译
- 首先要安装ndk的环境,前面的文章里面有提到,这里就不再赘述了。
下载jpeg的源码链接,只需要将jni的部分上传到服务器上面,新建一个文件夹将jni拷贝进去,在jni文件夹中执行
ndk build
这时候就会生成动态库和一些必要的文件。
接下来就是编码的时间。
实现图片的压缩
1.图片的压缩这个项目我用的是eclipse编辑器,同样的需要下载ndk的工具包,这个网上的资源比较多,这里就不再多说了。新建项目后,在项目的根目录下面新建jni文件夹,将我们生成的动态库和头文件拷贝进去,然后与ndk联系起来,右键项目点击Android Tools -》Add Native Support。这样就可以进行NDK开发了。项目的结构如下。
2.接下来就是代码,这里就不在一一讲解了,都比较的简单。
NativeUtil 类
/*
* 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.
*/
package net.bither.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Log;
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();
}
}
}
头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class net_bither_util_Test */
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv*,
jclass, jobject , int , int , int ,
jbyteArray , jboolean );
#ifdef __cplusplus
}
#endif
/*
* 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_net_bither_util_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");
}
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)
Application.mk
APP_ABI := armeabi-v7a armeabi #表示 编译目标 ABI(应用二进制接口)
APP_PLATFORM := android-9
MainActivity
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();
}
}
}
当然最后不要忘记权限,6.0以上自己申请哦
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
接下来我们来看一下效果。
原图像大小是6.7兆
综合对比 libjpeg有着很大的优势。尺寸压缩虽然小,但是失真严重。质量压缩要比jpeg的压缩大很多。