Android 调用jepg库进行图片压缩,保持图片不失真

时间:2022-06-14 17:00:45

1. 浅谈为什么Android和iOS图片质量差距那么大?

首先来说,作为一个安卓狗,机器当然用的是安卓的手机。现在的安卓手机大多数都会以高清拍照,动不动就几千万柔光相机来吸引各种买家。买来后,拍照发现,哇塞——一张图片好几M呢,但是还是不如iOS的感觉,iOS的图片也就1M左右吧。为什么会有这么大的差距呢?这要从安卓的设计初衷来说起,当时谷歌开发Android的时候,考虑了大部分手机的配置并没有那么高,所以对图片处理是使用的Skia这个库。当然这个库的底层还是是用的jpeg对图片进行压缩处理。但是为了能够适配低端的手机(这里的低端是指以前的硬件配置不高的手机),所以Skia在进行图片处理并没有去使用压缩图像过程中基于图像数据计算哈弗曼表(关于图片压缩中的哈弗曼表,请自行查阅相关资料),可以参考[这里](http://www.cnblogs.com/MaxIE/p/3951294.html)。这里面详细解释为何Google没有使用高性能的压缩,简单来说就是考虑了当时的手机硬件,将一个压缩参数optimize_coding设置为了false,使得硬件较低的手机能够很好的处理图片。

2. NDK环境以及Cmake配置(篇幅有限这里不做过多的描述)

添加环境变量

Android 调用jepg库进行图片压缩,保持图片不失真

将配置的环境变量添加到系统环境变量中。把%NDK_HOME%;添加到Path中。

3. jpeg库的下载及编译.so文件

下载libjpeg库源码,git clone地址

git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android  

将clone下来的源码目录改为jni(即源目录libjpeg-turbo改为jni),通过ndk命令进行编译(需要配好ndk环境变量,命令行进入修改好的jni目录输入命令即可):

ndk-build APP_ABI=armeabi-v7a,armeabi  

在当前目录下生成libs和obj文件夹

Android 调用jepg库进行图片压缩,保持图片不失真

4. 新建一个Android项目

新建一个Android项目,并勾选c++support

Android 调用jepg库进行图片压缩,保持图片不失真

如果环境配置好的话,AS会自动生成一个包含NDK的项目,里面实现了hello world。目录结构如下图:

Android 调用jepg库进行图片压缩,保持图片不失真

新建一个类,JpegUtils,声明native方法

 public class JpegUtils {
static {
System.loadLibrary("native-lib");
} public static native boolean compressBitmap(Bitmap bitmap, int width, int height, String fileName, int quality);
}

在新建的方法上直接生成c++方法。

把刚才jpeg库的头文件导入到cpp\include目录下。我只保留了android下面的头文件和其他的.h以及.c文件,其实这里面有无用的,但是具体不清楚,所以直接导入了。

Android 调用jepg库进行图片压缩,保持图片不失真

jpeg压缩的步骤
1、将Android的bitmap解码并转换为RGB数据
2、为JPEG对象分配空间并初始化
3、指定压缩数据源
4、获取文件信息
5、为压缩设定参数,包括图像大小,颜色空间
6、开始压缩
7、压缩完毕
8、释放资源

在native-lib文件中进行代码编写

 extern "C"
JNIEXPORT jboolean JNICALL
Java_com_nick_compress_JpegUtils_compressBitmap(JNIEnv *env, jclass type, jobject bitmap,
jint width, jint height, jstring fileName,
jint quality) { AndroidBitmapInfo infoColor;
BYTE *pixelColor;
BYTE *data;
BYTE *tempData;
const char *filename = env->GetStringUTFChars(fileName, 0); if ((AndroidBitmap_getInfo(env, bitmap, &infoColor)) < 0) {
LOGE("解析错误");
return false;
} if ((AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelColor)) < 0) {
LOGE("加载失败");
return false;
} BYTE r, g, b;
int color;
data = (BYTE *) malloc(width * height * 3);
tempData = data;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
color = *((int *) pixelColor);
r = ((color & 0x00FF0000) >>
16);//与操作获得rgb,参考java Color定义alpha color >>> 24 red (color >> 16) & 0xFF
g = ((color & 0x0000FF00) >> 8);
b = color & 0X000000FF; *data = b;
*(data + 1) = g;
*(data + 2) = r;
data += 3;
pixelColor += 4;
}
} AndroidBitmap_unlockPixels(env, bitmap);
int resultCode = generateJPEG(tempData, width, height, quality, filename, true); free(tempData);
if (resultCode == 0) {
return false;
} return true;
} extern "C"
//图片压缩方法
int generateJPEG(BYTE *data, int w, int h, int quality,
const char *outfilename, jboolean optimize) {
int nComponent = 3; struct jpeg_compress_struct jcs; struct jpeg_error_mgr jem; jcs.err = jpeg_std_error(&jem); //为JPEG对象分配空间并初始化
jpeg_create_compress(&jcs);
//获取文件信息
FILE *f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//指定压缩数据源
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;//image_width->JDIMENSION->typedef unsigned int
jcs.image_height = h; jcs.arith_code = false;
//input_components为1代表灰度图,在等于3时代表彩色位图图像
jcs.input_components = nComponent;
if (nComponent == 1)
//in_color_space为JCS_GRAYSCALE表示灰度图,在等于JCS_RGB时代表彩色位图图像
jcs.in_color_space = JCS_GRAYSCALE;
else
jcs.in_color_space = JCS_RGB; jpeg_set_defaults(&jcs);
//optimize_coding为TRUE,将会使得压缩图像过程中基于图像数据计算哈弗曼表,由于这个计算会显著消耗空间和时间,默认值被设置为FALSE。
jcs.optimize_coding = optimize;
//为压缩设定参数,包括图像大小,颜色空间
jpeg_set_quality(&jcs, quality, true);
//开始压缩
jpeg_start_compress(&jcs, TRUE); JSAMPROW row_pointer[1];//JSAMPROW就是一个字符型指针 定义一个变量就等价于=========unsigned char *temp
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];
//写入数据 http://www.cnblogs.com/darkknightzh/p/4973828.html
jpeg_write_scanlines(&jcs, row_pointer, 1);
} //压缩完毕
jpeg_finish_compress(&jcs);
//释放资源
jpeg_destroy_compress(&jcs);
fclose(f); return 1;
}

这段代码比较多,但是这是很常用的jpeg库的使用,网上解释比较多,我这里也进行了较详细的注释,这里不过多的描述。
OK,代码的编写就到这里,点击运行。——崩撒卡拉卡,果然没能运行成功。显示好多undifined reference,熟悉NDK的都知道我们需要在mk文件中去定义这些使用到的头文件,但是我们项目是使用的Cmake工具进行编译,所以需要在CMakelist.txt中去定义我们用到的库及头文件
CMakelist.txt

 # Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library.
native-lib # Sets the library as a shared library.
SHARED # Provides a relative path to your source file(s).
# Associated headers in the same location as their source
# file are automatically included.
src/main/cpp/native-lib.cpp )
#include 这个目录下所有的文件
include_directories(src/main/cpp/include)
#外部导入jpeg这个库
add_library(jpeg SHARED IMPORTED )
#这句话是jpeg对应的so文件,so文件是放到ibs这个文件夹中(相对与cpp这个文件的位置)
set_target_properties(jpeg PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libjpeg.so) # Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib # Specifies the name of the NDK library that
# you want CMake to locate.
log ) # Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library.
native-lib # Links the target library to the log library
# included in the NDK.
jpeg
#jnigraphics这个是android下面的bitmap.h对应的库
jnigraphics
${log-lib})

用AS去开发NDK最难的地方并不是什么代码,而是这个CMakelist文件。妈蛋想想我网上找了n久,真的资料太少了。NND!
有了上面的代码就可以成功的运行了,我把代码放到了Github(https://github.com/mcksuu/jpeg-android)上,需要的可以下下来看看。

5.最终效果

原图和详情:

Android 调用jepg库进行图片压缩,保持图片不失真Android 调用jepg库进行图片压缩,保持图片不失真

压缩后的图片和详情:

Android 调用jepg库进行图片压缩,保持图片不失真Android 调用jepg库进行图片压缩,保持图片不失真