这里我使用是非cmake的方式来实现,对于生成的头文件和.so文件,大家可以看我的这篇文章https://www.jianshu.com/p/93b895e5c27c
我们使用的是libjpeg,官网是:https://github.com/libjpeg-turbo/libjpeg-turbo,下载后生成.so文件即可.
android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=libjpeg
LOCAL_SRC_FILES :=libjpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE :=compressimg
LOCAL_SRC_FILES :=compress_image.cpp
LOCAL_SHARED_LIBRARIES :=libjpeg
LOCAL_LDLIBS := -ljnigraphics -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := armeabi-v7a armeabi #表示 编译目标 ABI(应用二进制接口)
APP_PLATFORM := android-9
我们写个工具类名为ImageUtils
public class ImageUtils {
static {
System.loadLibrary("compressimg");
}
public static void compressImage(Bitmap bitmap, int quality, String fileName) {
compressBitmap(bitmap, quality, fileName);
}
public native static int compressBitmap(Bitmap bitmap, int quality, String fileName);
public native static int encryptedFile(String filePath,String cryptedPath);
public static Bitmap decodeFile(String path) {
int finalWidth = 800;
//先获取宽度
BitmapFactory.Options options = new BitmapFactory.Options();
//不加载图片内存只拿宽高
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int bitmapWidth = options.outWidth;
int inSampleSize = 1;
if (bitmapWidth > finalWidth) {
inSampleSize = bitmapWidth / finalWidth;
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
}
然后我们生成相应的.h文件,注意这里我们的.h文件使用javah -jni后会报错找不到android.graphics.Bitmap
这时候我们首先定位到debug
然后输入javah -classpath sdk下android.jar位置;. 类的全路径,比如我的android.jar路径是
javah -classpath C:\Users\Administrator\AppData\Local\Android\Sdk\platforms\android-25\android.jar;. com.peakmain.project.utils.ImageUtils
生成.so文件
找到该自己新建的jni目录下然后打开cmd,定位到这里,然后输入ndk-build,不要有空格。生成.so文件拷贝到main下新建的jniLibs目录下
接下来就是写代码,我们在提供好的compress_image.cpp中进行添加代码
#include "compress_image.h"
#include
#include
#include
#include
#include
#include
#include
#include
//统一编译方式
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/jconfig.h"
}
// log打印
#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;
// error 结构体
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]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE *data, int w, int h, int quality,
const char *outfilename, jboolean optimize) {
// 结构体相当于Java类
struct jpeg_compress_struct jcs;
//当读完整个文件的时候就会回调my_error_exit这个退出方法。
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
// setjmp是一个系统级函数,是一个回调。
if (setjmp(jem.setjmp_buffer)) {
return 0;
}
//初始化jsc结构体
jpeg_create_compress(&jcs);
//打开输出文件 wb 可写 rb 可读
FILE *f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//设置结构体的文件路径,以及宽高
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;
jcs.image_height = h;
// /* 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;
///* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼
jcs.optimize_coding = optimize;
//设置质量
jpeg_set_quality(&jcs, quality, true);
//开始压缩
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
row_stride = jcs.image_width * nComponent;
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];
jpeg_write_scanlines(&jcs, row_pointer, 1);
}
// 压缩结束
jpeg_finish_compress(&jcs);
// 销毁回收内存
jpeg_destroy_compress(&jcs);
//关闭文件
fclose(f);
return 1;
}
// 函数的实现 AS 1.5如果没代码提示 AS2.2 VS去写好 Unity3D
// java 什么思想 C就是什么思想
jint Java_com_peakmain_project_utils_ImageUtils_compressBitmap(JNIEnv *env,
jclass thiz, jobject bitmap,
int quality,
jstring fileNameStr) {
// 1. 解析RGB
// 1.1 获取bitmap信息 w,h,format Android的Native要有了解
AndroidBitmapInfo info;
// java你调用完方法往往返回的是对象,而C往往是参数
AndroidBitmap_getInfo(env, bitmap, &info);
// 从地址获取值
int bitmap_height = info.height;
int bitmap_width = info.width;
int bitmap_format = info.format;
if (bitmap_format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
// argb
return -1;
}
LOGE("bitmap_height = %d,bitmap_width = %d,", bitmap_height, bitmap_width);
// 1.2 把bitmap解析到数组中,数组中保存的是rgb -> YCbCr
// 1.2.1 锁定画布
BYTE *pixel_color;
AndroidBitmap_lockPixels(env, bitmap, (void **) &pixel_color);
// 1.2.2 解析数据,定义一些变量
BYTE *data;
BYTE r, g, b;
// 申请一块内存 = 宽*高*3
data = (BYTE *) malloc(bitmap_width * bitmap_height * 3);
// 数组指针指向的是数组首地址,因为这块内存要释放所以先保存一下
BYTE *tempData;
tempData = data;
// 一个一个像素解析保存到data
int i = 0;
int j = 0;
int color;
for (i = 0; i < bitmap_height; ++i) {
for (j = 0; j < bitmap_width; ++j) {
// 获取二位数组的每一个像素信息的首地址
color = *((int *) pixel_color);
// 把 rgb 取出来
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下就是取下一个像素点
pixel_color += 4;
}
}
// 1.2.3 解锁画布
AndroidBitmap_unlockPixels(env, bitmap);
// 1.2.4 还差一个参数,jstring -> char*
char *file_name = (char *) env->GetStringUTFChars(fileNameStr, NULL);
LOGE("file_name = %s", file_name);
// 2.调用第三方的提供好的方法 赋值
int result = generateJPEG(tempData, bitmap_width, bitmap_height, quality, file_name, true);
LOGE("result = %d", result);
// 3.一定要回收内存
free(tempData);
env->ReleaseStringUTFChars(fileNameStr, file_name);
// 释放bitmap,调用bitmap的recycle
// 3.2 获取对象的class
jclass obj_clazz = env -> GetObjectClass(bitmap);
// 3.3 通过class获取方法id
jmethodID method_id = env -> GetMethodID(obj_clazz,"recycle","()V");//()V代表void方法
// 3.4 调用方法释放Bitmap
env->CallVoidMethod(bitmap,method_id);
LOGE("result = %d", result);
// 4.返回结果
if (result == 0) {
return -1;
}
return 1;
}
// 加密的秘钥
char password[] = "Happy every day!";
// 加密文件
void crypt_file(char *normal_path, char *crypt_path) {
//打开文件
FILE *normal_fp = fopen(normal_path, "rb");
FILE *crypt_fp = fopen(crypt_path, "wb");
//一次读取一个字符
int ch;
int i = 0; //循环使用密码中的字母进行异或运算
int pwd_len = strlen(password); //密码的长度
while ((ch = fgetc(normal_fp)) != EOF) { //End of File
//写入(异或运算)
fputc(ch ^ password[i % pwd_len], crypt_fp);
i++;
}
// 关闭
fclose(crypt_fp);
fclose(normal_fp);
}
// 加密文件,jfile_path 源文件路径 jcrypt_path 加密后文件路径
jint JNICALL Java_com_peakmain_project_utils_ImageUtils_encryptedFile
(JNIEnv *env, jclass jclazz, jstring jfile_path, jstring jcrypt_path) {
char *normal_path = (char *)env->GetStringUTFChars(jfile_path, JNI_FALSE);
char *crypt_path =(char *) env->GetStringUTFChars( jcrypt_path, JNI_FALSE);
crypt_file(normal_path, crypt_path);
return 1;
}
使用
String path=Environment.getExternalStorageDirectory()+"/阅图/ez.jpg";
Log.e("TAG", "文件是否存在"+!TextUtils.isEmpty(path));
// BitmapFactory.decodeFile可能会内存溢出
//一般后台会规定宽高
//Bitmap bitmap = BitmapFactory.decodeFile(path);
Bitmap bitmap =ImageUtils.decodeFile(path);
Log.e("TAG","地址"+path);
ImageUtils.compressImage(bitmap,30, path);
ImageUtils.encryptedFile(path,path);