Android开发中,我们经常要面对图片压缩,大部分人使用Android Bitmap进行压缩,还有一些使用libjpeg压缩,之前有用过libjpeg,压缩效果相当惊艳,在保证图片损失较小的同时,还极大的减小了图片体积,不过这次我们基于libjpeg-turbo做图片压缩,据官方说速度提升2-6倍。

libjpeg-turbo is a JPEG image codec that uses SIMD instructions (MMX, SSE2, AVX2, NEON, AltiVec) to accelerate baseline JPEG compression and decompression on x86, x86-64, ARM, and PowerPC systems, as well as progressive JPEG compression on x86 and x86-64 systems. On such systems, libjpeg-turbo is generally 2-6x as fast as libjpeg, all else being equal. On other types of systems, libjpeg-turbo can still outperform libjpeg by a significant amount, by virtue of its highly-optimized Huffman coding routines. In many cases, the performance of libjpeg-turbo rivals that of proprietary high-speed JPEG codecs.

开始

1.Android Studio新建C工程

2.编译libjpeg-turbo

下载源码,将源码copy到Module的cpp目录

libjpeg-turbo官网

libjpeg-turbo源码

重新Build项目,找到Module编译的apk,解压apk,得到libjpeg-turbo的so动态链接库。

3.使用libjpeg-turbo

新建native方法

package peak.chao.picturecompression;import android.graphics.Bitmap;public class CompressUtil {static {System.loadLibrary("native-lib");}public native static int compressBitmap(Bitmap bitmap, int quality, String destFile);
}

javah 生成头文件

将生成的头文件移动到cpp目录,并且将需要使用的依赖头文件一并引入

修改native-lib.cpp,实现压缩方法

//
// Created by peakchao on 2019/3/22.
//#include <jni.h>
#include <string>
#include "turbojpeg.h"
#include "jpeglib.h"
#include <android/bitmap.h>
#include <android/log.h>
#include <csetjmp>
#include <setjmp.h>
#include "peak_chao_picturecompression_CompressUtil.h"#define LOG_TAG  "C_TAG"
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
typedef u_int8_t BYTE;
struct my_error_mgr {struct jpeg_error_mgr pub;jmp_buf setjmp_buffer;
};typedef struct my_error_mgr *my_error_ptr;int generateJPEG(BYTE *data, int w, int h, jint quality, const char *location, jint quality1) {int nComponent = 3;struct jpeg_compress_struct jcs;//自定义的errorstruct my_error_mgr jem;jcs.err = jpeg_std_error(&jem.pub);if (setjmp(jem.setjmp_buffer)) {return 0;}//为JPEG对象分配空间并初始化jpeg_create_compress(&jcs);//获取文件信息FILE *f = fopen(location, "wb");if (f == NULL) {return 0;}//指定压缩数据源jpeg_stdio_dest(&jcs, f);jcs.image_width = w;jcs.image_height = h;jcs.arith_code = false;jcs.input_components = nComponent;jcs.in_color_space = JCS_RGB;jpeg_set_defaults(&jcs);jcs.optimize_coding = quality;//为压缩设定参数,包括图像大小,颜色空间jpeg_set_quality(&jcs, quality, true);//开始压缩jpeg_start_compress(&jcs, true);JSAMPROW row_point[1];int row_stride;row_stride = jcs.image_width * nComponent;while (jcs.next_scanline < jcs.image_height) {row_point[0] = &data[jcs.next_scanline * row_stride];jpeg_write_scanlines(&jcs, row_point, 1);}if (jcs.optimize_coding) {LOGD("使用了哈夫曼算法完成压缩");} else {LOGD("未使用哈夫曼算法");}//压缩完毕jpeg_finish_compress(&jcs);//释放资源jpeg_destroy_compress(&jcs);fclose(f);return 1;
}const char *jstringToString(JNIEnv *env, jstring jstr) {char *ret;const char *tempStr = env->GetStringUTFChars(jstr, NULL);jsize len = env->GetStringUTFLength(jstr);if (len > 0) {ret = (char *) malloc(len + 1);memcpy(ret, tempStr, len);ret[len] = 0;}env->ReleaseStringUTFChars(jstr, tempStr);return ret;
}extern "C"
JNIEXPORT jint JNICALL
Java_peak_chao_picturecompression_CompressUtil_compressBitmap(JNIEnv *env, jclass,jobject bitmap, jint optimize,jstring destFile_) {AndroidBitmapInfo androidBitmapInfo;BYTE *pixelsColor;int ret;BYTE *data;BYTE *tmpData;const char *dstFileName = jstringToString(env, destFile_);//解码Android Bitmap信息if ((ret = AndroidBitmap_getInfo(env, bitmap, &androidBitmapInfo)) < 0) {LOGD("AndroidBitmap_getInfo() failed error=%d", ret);return ret;}if ((ret = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&pixelsColor))) <0) {LOGD("AndroidBitmap_lockPixels() failed error=%d", ret);return ret;}LOGD("bitmap: width=%d,height=%d,size=%d , format=%d ",androidBitmapInfo.width, androidBitmapInfo.height,androidBitmapInfo.height * androidBitmapInfo.width,androidBitmapInfo.format);BYTE r, g, b;int color;int w, h, format;w = androidBitmapInfo.width;h = androidBitmapInfo.height;format = androidBitmapInfo.format;data = (BYTE *) malloc(androidBitmapInfo.width * androidBitmapInfo.height * 3);tmpData = data;// 将bitmap转换为rgb数据for (int i = 0; i < h; ++i) {for (int j = 0; j < w; ++j) {//只处理 RGBA_8888if (format == ANDROID_BITMAP_FORMAT_RGBA_8888) {color = (*(int *) (pixelsColor));// 这里取到的颜色对应的 A B G R  各占8位b = (color >> 16) & 0xFF;g = (color >> 8) & 0xFF;r = (color >> 0) & 0xFF;*data = r;*(data + 1) = g;*(data + 2) = b;data += 3;pixelsColor += 4;} else {return -2;}}}AndroidBitmap_unlockPixels(env, bitmap);//进行压缩ret = generateJPEG(tmpData, w, h, optimize, dstFileName, optimize);free((void *) dstFileName);free((void *) tmpData);return ret;
}

修改CMakeLists.txt进行编译配置


cmake_minimum_required(VERSION 3.4.1)
set(distribution_DIR ../../../../libs)
#添加lib,SHARED类型,是IMPORTED 引入的库
add_library(libjpegSHAREDIMPORTED)#设置 库的属性   里面是名称 ,属性:引入地址把我们的真实地址填写进去
set_target_properties(libjpegPROPERTIES IMPORTED_LOCATION${distribution_DIR}/x86/libjpeg.so)#添加lib,SHARED类型,是IMPORTED 引入的库
add_library(libturbojpegSHAREDIMPORTED)#设置 库的属性   里面是名称 ,属性:引入地址把我们的真实地址填写进去
set_target_properties(libturbojpegPROPERTIES IMPORTED_LOCATION${distribution_DIR}/x86/libturbojpeg.so)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).native-lib.cpp)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)target_link_libraries( # Specifies the target library.native-liblibjpeg-ljnigraphicslibturbojpeg# Links the target library to the log library# included in the NDK.${log-lib})

清单文件加入权限,目标sdk版本大于等于23需要动态权限申请,为了测试,我这里在设置中手动授权。

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

修改MainActivity和activity_main布局文件,做图片压缩测试。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/native_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="compressNative"android:text="本地压缩"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"android:textSize="30sp"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/system_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:onClick="compressSystem"android:text="系统压缩"android:textSize="30sp"app:layout_constraintLeft_toLeftOf="@id/native_tv"app:layout_constraintRight_toRightOf="@id/native_tv"app:layout_constraintTop_toBottomOf="@id/native_tv" /></android.support.constraint.ConstraintLayout>
package peak.chao.picturecompression;import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;public class MainActivity extends AppCompatActivity {private int qu = 40;private Bitmap bitmap;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);AssetManager manager = getResources().getAssets();InputStream open = null; //得到输出流try {open = manager.open("max_image.jpg");} catch (IOException e) {e.printStackTrace();}bitmap = BitmapFactory.decodeStream(open);}private void compressByDefault(Bitmap bitmap, int quality) {File file = new File(getSaveLocation() + "/compress2.png");if (file.exists()) {try {file.delete();file.createNewFile();} catch (IOException e) {e.printStackTrace();}}try {OutputStream stream = new FileOutputStream(file);bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);} catch (FileNotFoundException e) {e.printStackTrace();}}private String getSaveLocation() {return Environment.getExternalStorageDirectory().getAbsolutePath();}public void compressNative(View view) {String result = getSaveLocation() + "/compress.png";long time = System.currentTimeMillis();int i = CompressUtil.compressBitmap(bitmap, qu, result);Log.e("C_TAG", "Native" + (System.currentTimeMillis() - time));if (i == 1) {Toast.makeText(this, "压缩完成,耗时:" + (System.currentTimeMillis() - time) + "毫秒", Toast.LENGTH_LONG).show();} else {Toast.makeText(this, "压缩失败", Toast.LENGTH_LONG).show();}}public void compressSystem(View view) {long time = System.currentTimeMillis();compressByDefault(bitmap, qu);Log.e("C_TAG", "Java" + (System.currentTimeMillis() - time));Toast.makeText(this, "压缩完成,耗时:" + (System.currentTimeMillis() - time) + "毫秒", Toast.LENGTH_LONG).show();}
}

4.运行

经过测试,原5M的图片,native压缩比系统压缩耗时要长,图片效果差不多一致,文件大小一样,难道高版本手机内部也使用了哈夫曼算法压缩?感觉有点坑啊,看不到优势了,算了先就这样吧,贴一份源码。

github源码

Android NDK编译libjpeg-turbo压缩图片相关推荐

  1. 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )

    文章目录 一.使用 libjpeg-turbo 压缩图片流程 二.初始化 JPEG 压缩对象 三.打开文件 四.设置压缩参数 五.开始压缩 六.循环写入压缩数据 七.完成图片压缩及收尾 八.libjp ...

  2. Android NDK编译中在libs\armeabi中加入第三方so库文件的方法

    Android NDK编译中在libs\armeabi中加入第三方so库文件的方法 假设要加入库文件的名字为libffmpeg.so文件 1.要在project\jni目录下新建一目录prebuilt ...

  3. android.mk ndk编译选项优化,Android NDK 编译脚本分析 之一

    版权信息:本文为本人原创,欢迎转载,但请著明出处,并保留本版权信息. Android NDK编译脚本编写起来还是是比较简单条理的,然而它的语法和传统的linux GNU Make编译脚本的编写似乎有很 ...

  4. NDK编译php,Android NDK编译常见错误及解决方案

    Android NDK编译常见错误及解决方案 Error 1:$ ndk-build/cygdrive/c/andy/abc/obj/local/armeabi-v7a/objs/abc//hello ...

  5. Android NDK 编译PjSip 2.6 之 搭建PjSip apk开发环境 (三)

    Android NDK 编译PjSip 2.6 之 PjSip编译 (二)中已经把so 和java文件编译出来.可以开始搭建apk的开发环境. 我们导入PjSip的example apk程序,编译生成 ...

  6. android jni不适用ndk,Android NDK编译之undefined reference to 'JNI_CreateJavaVM'

    利用Android NDK编译动态库,在C文件中调用了两个JNI函数:JNI_GetDefaultJavaVMInitArgs和JNI_CreateJavaVM.编译的时候始终报以下错误: XXX: ...

  7. android中ndk编译错误,Android NDK编译常见错误及解决方案

    Android NDK编译常见错误及解决方案 Error 1:$ ndk-build/cygdrive/c/andy/abc/obj/local/armeabi-v7a/objs/abc//hello ...

  8. android 编译 sdl,使用android ndk编译SDL2示例错误r14

    我已经测试过在我的ubuntu 16.04机器上构建SDL2源代码(2.0.5)中的示例.使用android ndk编译SDL2示例错误r14 根据https://wiki.libsdl.org/An ...

  9. 在安卓项目中使用gifsicle编辑GIF动图-Android NDK 编译 gifsicle 为可执行文件记录

    一.前言 最近项目中有需要压缩GIF的需求,最开始时试图使用FFmpeg通过降低GIF的分辨率和帧率的来减少GIF文件体积,但实际测试下来,大多数情况下压缩效果并不理想,甚至会出现降低分辨率后导出的G ...

最新文章

  1. 在批处理模式下使用mysql_3.5 在批处理模式下使用mysql
  2. 【BZOJ 3747】 3747: [POI2015]Kinoman (线段树)
  3. C语言中不安全的函数
  4. Nacos注册中心介绍
  5. 第十篇: Timer 控件
  6. ef生成mysql字段注释_EFcore+MySql 数据迁移的时候,怎么给表结构加注释?
  7. opencv python3 找图片色块_如何使用OpenCV在Python中找到图像的平均颜色?
  8. Struts1 生成Action请求的几种方式分析
  9. 查看数值类型python_python怎么看数据类型
  10. 20175212 《Java程序设计》第2周学习总结
  11. 对 数组[i].index=i的理解
  12. 判断IP是否为搜索引擎蜘蛛或爬虫
  13. 计算机网络知识点脑图 王道 考研
  14. 数字电路与逻辑设计(复习)
  15. 【翻译】torch.device的使用举例
  16. 企业微信java开发demo_微信企业号demo
  17. 每天一个编程题·iOS开发算法提升计划(1)
  18. VO、DTO、BO、QO、DO 如何使用,在那一层使用,一张图告诉你;别再纠结命名规则啦,我来告诉你
  19. C# arcengine 属性快速浏览
  20. 我为大家整理了一波 Java 超全面试题

热门文章

  1. 华为快应用-怎么隐藏原生导航条
  2. 什么是蜜罐、蜜饵、蜜标、蜜网、蜜场?
  3. 【java】求平均值
  4. 计算机卸载一个程序正确操作,卸载一个程序我在电脑里安装了一个一个山西省计算机考试系统21 爱问知识人...
  5. Android底部弹出选择框PickerView的使用
  6. 深瑞IEC103协议
  7. 尚学堂怎么样?在这里究竟给我带来了哪些收获?
  8. Java实现简单的图书管理系统(讲解清晰,代码齐全,能正常运行)
  9. 使用timer_create时链接librt(lrt)的问题
  10. 基于阿里云IoT平台OTA进行APP确认升级的方案——业务架构类