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 配置(篇幅有限这里不做过多的描述)
添加环境变量
将配置的环境变量添加到系统环境变量中。把 %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 文件夹
4. 新建一个 Android 项目
新建一个 Android 项目,并勾选 c++support。
如果环境配置好的话,AS 会自动生成一个包含 NDK 的项目,里面实现了 hello world。目录结构如下图:
新建一个类,JpegUtils,声明 native 方法
- 1 public class JpegUtils {
- 2 static {
- 3 System.loadLibrary("native-lib");
- 4
- }
- 5 6 public static native boolean compressBitmap(Bitmap bitmap, int width, int height, String fileName, int quality);
- 7
- }
在新建的方法上直接生成 c++ 方法。
把刚才 jpeg 库的头文件导入到 cpp\include 目录下。我只保留了 android 下面的头文件和其他的. h 以及. c 文件,其实这里面有无用的,但是具体不清楚,所以直接导入了。
jpeg 压缩的步骤
1、将 Android 的 bitmap 解码并转换为 RGB 数据
2、为 JPEG 对象分配空间并初始化
3、指定压缩数据源
4、获取文件信息
5、为压缩设定参数,包括图像大小,颜色空间
6、开始压缩
7、压缩完毕
8、释放资源
在 native-lib 文件中进行代码编写
- 1 extern "C"
- 2 JNIEXPORT jboolean JNICALL
- 3 Java_com_nick_compress_JpegUtils_compressBitmap(JNIEnv *env, jclass type, jobject bitmap,
- 4 jint width, jint height, jstring fileName,
- 5 jint quality) {
- 6
- 7 AndroidBitmapInfo infoColor;
- 8 BYTE *pixelColor;
- 9 BYTE *data;
- 10 BYTE *tempData;
- 11 const char *filename = env->GetStringUTFChars(fileName, 0);
- 12
- 13 if ((AndroidBitmap_getInfo(env, bitmap, &infoColor)) < 0) {
- 14 LOGE("解析错误");
- 15 return false;
- 16 }
- 17
- 18 if ((AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelColor)) < 0) {
- 19 LOGE("加载失败");
- 20 return false;
- 21 }
- 22
- 23 BYTE r, g, b;
- 24 int color;
- 25 data = (BYTE *) malloc(width * height * 3);
- 26 tempData = data;
- 27 for (int i = 0; i < height; i++) {
- 28 for (int j = 0; j < width; j++) {
- 29 color = *((int *) pixelColor);
- 30 r = ((color & 0x00FF0000) >>
- 31 16);//与操作获得rgb,参考java Color定义alpha color >>> 24 red (color >> 16) & 0xFF
- 32 g = ((color & 0x0000FF00) >> 8);
- 33 b = color & 0X000000FF;
- 34
- 35 *data = b;
- 36 *(data + 1) = g;
- 37 *(data + 2) = r;
- 38 data += 3;
- 39 pixelColor += 4;
- 40 }
- 41 }
- 42
- 43 AndroidBitmap_unlockPixels(env, bitmap);
- 44 int resultCode = generateJPEG(tempData, width, height, quality, filename, true);
- 45
- 46 free(tempData);
- 47 if (resultCode == 0) {
- 48 return false;
- 49 }
- 50
- 51 return true;
- 52 }
- 53
- 54 extern "C"
- 55 //图片压缩方法
- 56 int generateJPEG(BYTE *data, int w, int h, int quality,
- 57 const char *outfilename, jboolean optimize) {
- 58 int nComponent = 3;
- 59
- 60 struct jpeg_compress_struct jcs;
- 61
- 62 struct jpeg_error_mgr jem;
- 63
- 64 jcs.err = jpeg_std_error(&jem);
- 65
- 66 //为JPEG对象分配空间并初始化
- 67 jpeg_create_compress(&jcs);
- 68 //获取文件信息
- 69 FILE *f = fopen(outfilename, "wb");
- 70 if (f == NULL) {
- 71 return 0;
- 72 }
- 73 //指定压缩数据源
- 74 jpeg_stdio_dest(&jcs, f);
- 75 jcs.image_width = w;//image_width->JDIMENSION->typedef unsigned int
- 76 jcs.image_height = h;
- 77
- 78 jcs.arith_code = false;
- 79 //input_components为1代表灰度图,在等于3时代表彩色位图图像
- 80 jcs.input_components = nComponent;
- 81 if (nComponent == 1)
- 82 //in_color_space为JCS_GRAYSCALE表示灰度图,在等于JCS_RGB时代表彩色位图图像
- 83 jcs.in_color_space = JCS_GRAYSCALE;
- 84 else
- 85 jcs.in_color_space = JCS_RGB;
- 86
- 87 jpeg_set_defaults(&jcs);
- 88 //optimize_coding为TRUE,将会使得压缩图像过程中基于图像数据计算哈弗曼表,由于这个计算会显著消耗空间和时间,默认值被设置为FALSE。
- 89 jcs.optimize_coding = optimize;
- 90 //为压缩设定参数,包括图像大小,颜色空间
- 91 jpeg_set_quality(&jcs, quality, true);
- 92 //开始压缩
- 93 jpeg_start_compress(&jcs, TRUE);
- 94
- 95 JSAMPROW row_pointer[1];//JSAMPROW就是一个字符型指针 定义一个变量就等价于=========unsigned char *temp
- 96 int row_stride;
- 97 row_stride = jcs.image_width * nComponent;
- 98 while (jcs.next_scanline < jcs.image_height) {
- 99 row_pointer[0] = &data[jcs.next_scanline * row_stride];
- 100 //写入数据 http://www.cnblogs.com/darkknightzh/p/4973828.html
- 101 jpeg_write_scanlines(&jcs, row_pointer, 1);
- 102 }
- 103
- 104 //压缩完毕
- 105 jpeg_finish_compress(&jcs);
- 106 //释放资源
- 107 jpeg_destroy_compress(&jcs);
- 108 fclose(f);
- 109
- 110 return 1;
- 111 }
这段代码比较多,但是这是很常用的 jpeg 库的使用,网上解释比较多,我这里也进行了较详细的注释,这里不过多的描述。
OK,代码的编写就到这里,点击运行。——崩撒卡拉卡,果然没能运行成功。显示好多 undifined reference,熟悉 NDK 的都知道我们需要在 mk 文件中去定义这些使用到的头文件,但是我们项目是使用的 Cmake 工具进行编译,所以需要在 CMakelist.txt 中去定义我们用到的库及头文件
CMakelist.txt
- 1 # Sets the minimum version of CMake required to build the native
- 2 # library. You should either keep the default value or only pass a
- 3 # value of 3.4.0 or lower.
- 4
- 5 cmake_minimum_required(VERSION 3.4.1)
- 6
- 7 # Creates and names a library, sets it as either STATIC
- 8 # or SHARED, and provides the relative paths to its source code.
- 9 # You can define multiple libraries, and CMake builds it for you.
- 10 # Gradle automatically packages shared libraries with your APK.
- 11
- 12 add_library( # Sets the name of the library.
- 13 native-lib
- 14
- 15 # Sets the library as a shared library.
- 16 SHARED
- 17
- 18 # Provides a relative path to your source file(s).
- 19 # Associated headers in the same location as their source
- 20 # file are automatically included.
- 21 src/main/cpp/native-lib.cpp )
- 22 #include 这个目录下所有的文件
- 23 include_directories(src/main/cpp/include)
- 24 #外部导入jpeg这个库
- 25 add_library(jpeg SHARED IMPORTED )
- 26 #这句话是jpeg对应的so文件,so文件是放到ibs这个文件夹中(相对与cpp这个文件的位置)
- 27 set_target_properties(jpeg PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libjpeg.so)
- 28
- 29 # Searches for a specified prebuilt library and stores the path as a
- 30 # variable. Because system libraries are included in the search path by
- 31 # default, you only need to specify the name of the public NDK library
- 32 # you want to add. CMake verifies that the library exists before
- 33 # completing its build.
- 34 find_library( # Sets the name of the path variable.
- 35 log-lib
- 36
- 37 # Specifies the name of the NDK library that
- 38 # you want CMake to locate.
- 39 log )
- 40
- 41 # Specifies libraries CMake should link to your target library. You
- 42 # can link multiple libraries, such as libraries you define in the
- 43 # build script, prebuilt third-party libraries, or system libraries.
- 44
- 45 target_link_libraries( # Specifies the target library.
- 46 native-lib
- 47
- 48 # Links the target library to the log library
- 49 # included in the NDK.
- 50 jpeg
- 51 #jnigraphics这个是android下面的bitmap.h对应的库
- 52 jnigraphics
- 53 ${log-lib})
用 AS 去开发 NDK 最难的地方并不是什么代码,而是这个 CMakelist 文件。妈蛋想想我网上找了 n 久,真的资料太少了。NND!
有了上面的代码就可以成功的运行了,我把代码放到了 Github(https://github.com/mcksuu/jpeg-android) 上,需要的可以下下来看看。
5. 最终效果
原图和详情:
压缩后的图片和详情:
来源: http://www.bubuko.com/infodetail-1959681.html