前言

Android 提供的 JPEG 压缩, 是由外部链接库中的 libjpeg 实现的, 但 Google 考虑到 Android 设备性能的瓶颈, 在 Skia 调用中的三方链接库 libjpeg 时, 多处进行了阉割处理, 这样带来的好处就是压缩的速度更快了, 但细节丢失严重, 压缩后甚至有偏绿的情况, 下面的代码便是 Android 执行 JPEG 压缩的关键

/*** SkImageDecoder_libjpeg.cpp*/
class SkJPEGImageEncoder : public SkImageEncoder {
protected:virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {......// 1. 初始化 libjpegjpeg_create_compress(&cinfo);// 设置一些参数cinfo.dest = &sk_wstream;cinfo.image_width = bm.width();cinfo.image_height = bm.height();cinfo.input_components = 3;// FIXME: Can we take advantage of other in_color_spaces in libjpeg-turbo?cinfo.in_color_space = JCS_RGB;// The gamma value is ignored by libjpeg-turbo.cinfo.input_gamma = 1;jpeg_set_defaults(&cinfo);// 这个标志用于控制是否使用优化的哈夫曼表cinfo.optimize_coding = TRUE;jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);// 2. 开始压缩jpeg_start_compress(&cinfo, TRUE);const int       width = bm.width();uint8_t*        oneRowP = oneRow.reset(width * 3);const SkPMColor* colors = bm.getColorTable() ? bm.getColorTable()->readColors() : nullptr;const void*      srcRow = bm.getPixels();while (cinfo.next_scanline < cinfo.image_height) {JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */writer(oneRowP, srcRow, width, colors);row_pointer[0] = oneRowP;(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);srcRow = (const void*)((const char*)srcRow + bm.rowBytes());}// 3. 结束压缩jpeg_finish_compress(&cinfo);// 4. 释放内存jpeg_destroy_compress(&cinfo);return true;}
};
复制代码

从上面的代码中, 我们定位到 cinfo.optimize_coding 这个参数

  • Android7.0 之后, 这个参数为 true

    • 在图片压缩的时候, 会根据图片去计算其对应的哈夫曼表, 图片质量更高, 但是图片占用的磁盘空间也相应更高
  • Android7.0 之前, 这个参数为 false
    • 使用默认的哈夫曼表, 不会去根据图片进行特定的计算, 经 Google 测试, 图片质量比使用哈夫曼低两倍左右

除此之外早期的 Android 版本, 同样考虑到性能问题, skia 引擎写了一个函数替代了原来 libjpeg 的转换函数, 好处是提高了编码速度, 坏处就是牺牲了每一个像素的精度

为了实现更快速更高质量的 JPEG 有损压缩, 因此笔者选择编译 libjpeg-turbo, 来处理项目中的图片压缩, 据官方介绍, 得益于它高度优化的哈夫曼算法, 它比 libjpeg 要快上 2-6 倍, 接下来我们来一步一步的将它集成到项目中

一. 准备工作

一) 操作系统

Ubuntu-18.04.1

二) 依赖安装

1. NDK

android-ndk-r16b-linux-x86_64.zip

2. CMake

CMake 3.12.1 的 Linux 版本

3. make

sudo apt-get install make
复制代码

4. libjpeg-turbo

从 Github 上下载最新的源码即可
github.com/libjpeg-tur…

注释版本号
  • 打开 libjpeg-turbo/sharedLibs/CMakeList.txt, 将设置版本号的位置注释, 否则在使用时, 可能会出现运行时缺少 so 库的问题

二. 编译

一) 脚本编写

Android 端脚本编写指南在 libjpeg-turbo 库中的 BUILDING.md 中有说明

Building libjpeg-turbo for Android
----------------------------------Building libjpeg-turbo for Android platforms requires v13b or later of the
[Android NDK](https://developer.android.com/tools/sdk/ndk).### ARMv7 (32-bit)The following is a general recipe script that can be modified for your specific
needs.# Set these variables to suit your needsNDK_PATH={full path to the NDK directory-- for example,/opt/android/android-ndk-r16b}TOOLCHAIN={"gcc" or "clang"-- "gcc" must be used with NDK r16b and earlier,and "clang" must be used with NDK r17c and later}ANDROID_VERSION={the minimum version of Android to support-- for example,"16", "19", etc.}cd {build_directory}cmake -G"Unix Makefiles" \-DANDROID_ABI=armeabi-v7a \-DANDROID_ARM_MODE=arm \-DANDROID_PLATFORM=android-${ANDROID_VERSION} \-DANDROID_TOOLCHAIN=${TOOLCHAIN} \-DCMAKE_ASM_FLAGS="--target=arm-linux-androideabi${ANDROID_VERSION}" \-DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \[additional CMake flags] {source_directory}make
......
复制代码

我们按照它的要求, 进行 shell 脚本的编写即可, 编写后的shell 脚本如下

#!/bin/sh

# lib-name
MY_LIBS_NAME=libjpeg-turbo
# 源码文件目录
MY_SOURCE_DIR=/home/sharry/Desktop/libjpeg-turbo-master
# 编译的过程中产生的中间件的存放目录,为了区分编译目录,源码目录,install目录
MY_BUILD_DIR=binary##  CMake 环境变量
export PATH=/home/sharry/Desktop/cmake-3.12.1-Linux-x86_64/bin:$PATHNDK_PATH=/home/sharry/Desktop/android-ndk-r16b
BUILD_PLATFORM=linux-x86_64
TOOLCHAIN_VERSION=4.9
ANDROID_VERSION=19ANDROID_ARMV5_CFLAGS="-march=armv5te"
ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"  # -mfpu=vfpv3-d16  -fexceptions -frtti
ANDROID_ARMV8_CFLAGS="-march=armv8-a"   # -mfloat-abi=softfp -mfpu=neon -fexceptions -frtti
ANDROID_X86_CFLAGS="-march=i386 -mtune=intel -mssse3 -mfpmath=sse -m32"
ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"# params($1:arch,$2:arch_abi,$3:host,$4:compiler,$5:cflags,$6:processor)
build_bin() {echo "-------------------star build $2-------------------------"ARCH=$1                # arm arm64 x86 x86_64ANDROID_ARCH_ABI=$2    # armeabi armeabi-v7a x86 mips# 最终编译的安装目录PREFIX=$(pwd)/dist/${MY_LIBS_NAME}/${ANDROID_ARCH_ABI}/HOST=$3COMPILER=$4PROCESSOR=$6SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ARCH}CFALGS="$5"TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}# build 中间件BUILD_DIR=./${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}export CFLAGS="$5 -Os -D__ANDROID_API__=${ANDROID_VERSION} --sysroot=${SYSROOT} \-isystem ${NDK_PATH}/sysroot/usr/include \-isystem ${NDK_PATH}/sysroot/usr/include/${HOST} "export LDFLAGS=-pieecho "path==>$PATH"echo "build_dir==>$BUILD_DIR"echo "ARCH==>$ARCH"echo "ANDROID_ARCH_ABI==>$ANDROID_ARCH_ABI"echo "HOST==>$HOST"echo "CFALGS==>$CFALGS"echo "COMPILER==>$COMPILER-gcc"echo "PROCESSOR==>$PROCESSOR"mkdir -p ${BUILD_DIR}   #创建当前arch_abi的编译目录,比如:binary/armeabi-v7acd ${BUILD_DIR}         #此处 进了当前arch_abi的2级编译目录# 运行时创建临时编译链文件toolchain.cmake
cat >toolchain.cmake << EOF
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR $6)
set(CMAKE_C_COMPILER ${TOOLCHAIN}/bin/${COMPILER}-gcc)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN}/${COMPILER})
EOFcmake -G"Unix Makefiles" \-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \-DCMAKE_POSITION_INDEPENDENT_CODE=1 \-DCMAKE_INSTALL_PREFIX=${PREFIX} \-DWITH_JPEG8=1 \${MY_SOURCE_DIR}make cleanmakemake install#从当前arch_abi编译目录跳出,对应上面的cd ${BUILD_DIR},以便function多次执行cd ../../echo "-------------------$2 build end-------------------------"
}# build armeabi
build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS" arm
复制代码

二) 执行编译脚本

sh build.sh
复制代码

编译执行之后, 便会输出头文件 和 armeabi 架构的 so 库

三. 集成

一) 添加

将我们上面编译好的 so 和头文件拷贝到我们的项目中

二) CMake 链接

在 CMake 中将我们的动态了添加进去

# 链接头文件
include_directories(${source_dir}/jniLibs/include)# libjpeg-turbo
add_library(libjpeg SHARED IMPORTED)
set_target_properties(libjpegPROPERTIESIMPORTED_LOCATION${source_dir}/jniLibs/armeabi/libjpeg.so
)# 将打包的 so 链接到项目中
target_link_libraries(......libjpeg......
)
复制代码

三) build.gradle

因为我们只编译了 armeabi 架构的 so, 因此我们需要再 gradle 中添加 filters

android {compileSdkVersion 28defaultConfig {minSdkVersion 16targetSdkVersion 28versionCode 1versionName "1.0"externalNativeBuild {ndk {abiFilters "armeabi" // 只生成 armeabi 的 CPU 架构的 .so}}}
}
复制代码

好的, 至此我们的集成就完成了, 接下来提供一些简单的用法

四. 代码的编写与测试

我们编译 libjpeg-turbo 的主要目的就是为了进行 JPEG 的高质量压缩, 关于 libjpeg-turbo 的使用, 这里就不赘述了, 其官方提供好的 sample 如下
raw.githubusercontent.com/libjpeg-tur…

简单的来说, 就是将 Bitmap 的颜色通道转为 BGR, 然后传给 libjpeg-turbo API 即可, 代码还是非常简单的

extern "C"
JNIEXPORT jint JNICALL
Java_com_sharry_libscompressor_Core_nativeCompress(JNIEnv *env, jclass type, jobject bitmap,jint quality, jstring destPath_) {// 1. 获取 bitmap 信息AndroidBitmapInfo info;AndroidBitmap_getInfo(env, bitmap, &info);int cols = info.width;int rows = info.height;int format = info.format;LOGI("Bitmap width is %d, height is %d", cols, rows);// 若不为 ARGB8888, 则不给予压缩if (format != ANDROID_BITMAP_FORMAT_RGBA_8888) {LOGE("Unsupported Bitmap channels, Please ensure channels is ARGB_8888.");return false;}// 2. 解析数据LOGI("Parse bitmap pixels");// 锁定画布uchar *pixels = NULL;AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels);if (pixels == NULL) {LOGE("Fetch Bitmap data failed.");return false;}// 创建存储数组uchar *data = (uchar *) malloc(static_cast<size_t>(cols * rows * 3));uchar *data_header_pointer = data;// 临时保存 data 的首地址, 用于后续释放内存uchar r, g, b;int row = 0, col = 0, pixel;for (row = 0; row < rows; ++row) {for (col = 0; col < cols; ++col) {// 2.1 获取像素值pixel = *((int *) pixels);// ...                                              // 忽略 A 通道值r = static_cast<uchar>((pixel & 0x00FF0000) >> 16); // 获取 R 通道值g = static_cast<uchar>((pixel & 0x0000FF00) >> 8);  // 获取 G 通道值b = static_cast<uchar>((pixel & 0x000000FF));       // 获取 B 通道值pixels += 4;// 2.2 为 Data 填充数据*(data++) = b;*(data++) = g;*(data++) = r;}}// 解锁画布AndroidBitmap_unlockPixels(env, bitmap);// 3. 使用 libjpeg 进行图片质量压缩LOGI("Lib jpeg turbo do compress");char *output_filename = (char *) (env)->GetStringUTFChars(destPath_, NULL);int result = LibJpegTurboUtils::write_JPEG_file(data_header_pointer, rows, cols,output_filename,quality);// 4. 释放资源LOGI("Release memory");free((void *) data_header_pointer);env->ReleaseStringUTFChars(destPath_, output_filename);return result;
}
复制代码

效果展示

I/Core: Request{inputSourceType = String, outputSourceType = Bitmap, quality = 70, destWidth = -1, destHeight = -1}
// 采样压缩之后
E/Core_native: ->> Bitmap width is 1512, height is 2016
E/Core_native: ->> Parse bitmap pixels
E/Core_native: ->> Lib jpeg turbo do compress
E/Core_native: ->> Release memory
I/Core: ->> output file is: /data/user/0/com.sharry.scompressor/cache/1555157510264.jpg
// 质量压缩之后
I/Core: ->> Output file length is 196kb
复制代码

可以看到 1512 x 2016 的图片, 在 quality 为 70 的情况下压缩之后, 为 196kb, 当然他的依旧是非常清晰的

总结

到这里我们的编译与集成就完成了, 整体的过程还是比较简单的, 其效果也非常的 nice, 而且不会受到 Android SDK 版本的困扰, 感兴趣的同学可以按照上述的方式试试看。

参考文献

blog.csdn.net/yuxiatongzh…

高性能图片压缩 —— libjpeg-turbo 的编译与集成相关推荐

  1. 一款现代、高效的 Android 图片压缩框架

    本项目主要基于 Android 自带的图片压缩 API 进行实现,提供了开源压缩方案 Luban 和 Compressor 的实现,解决了单一 Fie 类型数据源的问题,并在它们的基础之上进行了功能上 ...

  2. Android使用libjpeg实现图片压缩

    一.Android中使用的图片压缩库 Android和IOS 中图片处理使用了一个叫做skia的开源图形处理引擎.他位于android源码的/external/skia 目录.我们平时在java层使用 ...

  3. Android压缩图片和libjpeg库

    前言 Fjpeg使用 Fjpeg 注意 如何使用 如何压缩图片只改变在硬盘的存储大小 如何改变图片分辨率让其Bitmap对象可以加载到内存中 关于重载版本 开始学习之旅 补充知识的结论 修改图片分辨率 ...

  4. 使用libjpeg进行图片压缩

    简介 由于工作原因,boss下达的任务就大概说了对图片进行压缩寻找比较合理的方式,还举了一个项目中的坑,就是系统原生的Bitmap.compress设置质量参数为100生成图片会变大的坑.所以我打算用 ...

  5. Android LibJpeg图片压缩

    Android的图片压缩 Android的图片压缩的几种方式:质量压缩,尺寸压缩,采样率压缩,通过NDK调用libjpeg库进行压缩! 质量压缩 通过设置bitmap options属性,降低图片的质 ...

  6. 使用libjpeg进行图片压缩(哈夫曼算法,无损压缩)

    Huffman算法也是一种无损压缩算法,但与LZW压缩算法不同,Huffman需要得到每种字符出现概率的先验知识.通过计算字符序列中每种字符出现的频率,为每种字符进行唯一的编码设计,使得频率高的字符占 ...

  7. 【Android 内存优化】Android 原生 API 图片压缩原理 ( Bitmap_compress 方法解析 | Skia 二维图形库 | libjpeg 函数库 | libpng 函数库 )

    文章目录 一. 图片质量压缩方法 二. Skia 二维图形库 三. libjpeg.libpng 函数库引入 在博客 [Android 内存优化]图片文件压缩 ( Android 原生 API 提供的 ...

  8. 从Xcode编译时自带的“图片压缩”说起

     在Xcode的设置中,默认设置"Compress PNG Files"为True,从字面的理解,在最终编译打包时,Xcode将对项目中的PNG图片进行"压缩" ...

  9. Centos7 编译安装 图片压缩 MozJPEG

    官方教程 https://github.com/mozilla/mozjpeg/blob/master/BUILDING.txt 源码地址 https://github.com/mozilla/moz ...

最新文章

  1. 面试常碰到++p/p--问题到底结果是什么?
  2. php autoload机制学习
  3. 模仿国外某小哥,做的一个字符串转动态linq表达式 及 部分扩展
  4. 修改hosts文件不需要重启的方法
  5. 对比学习(Contrastive Learning)相关进展梳理
  6. boost::multi_array模块实现在矩阵上测试切片
  7. 公司台湾主站的url重写
  8. 报表自动化就是连接数据库?错,它打开了数据仓库的大门
  9. Java基础复习笔记系列 七 IO操作
  10. 《Python爬虫开发与项目实战》——第1章 回顾Python编程 1.1 安装Python
  11. PHP读取表格都是精度,php 小数精度问题
  12. Android MTK 预制应用遇到的问题
  13. 曙光服务器怎么进入bios_怎么进入bios,教您怎么进入bios
  14. android 蓝牙自动连接,蓝牙自动连接实现
  15. Make the most of your 20s
  16. 2022 中国开发者影响力盛典暨 CSDN 企业生态汇在京举行
  17. 关于appium踩坑 selenium.common.exceptions.WebDriverException: Message: An unknown server-side error(已解决)
  18. 小学计算机教研组总结,小学信息技术教研组工作总结范文
  19. pygame小游戏框架
  20. ChIP专题 | 如何进行ChIP-qPCR富集验证

热门文章

  1. 干货 | 响应式设计在携程火车票的应用
  2. 表格数据转换为json格式 python
  3. 学人工智能好处有哪些?有什么就业优势?
  4. phpstorm配制断点调试教程
  5. 树莓派 系统语言设置成中文 失败,没反应?
  6. express+socket简易聊天室
  7. Fiddler抓包远程调试篇
  8. 咸鱼Maya笔记—NURBS挤出成型法
  9. MongooseError: Operation `users.insertOne()` buffering timed out after 10000ms
  10. 指定TreeNode排序