美文网首页Android JNI技术干货Android 图片
Android NDK编译libjpeg-turbo压缩图片

Android NDK编译libjpeg-turbo压缩图片

作者: 844b9a3a3a68 | 来源:发表于2019-03-23 13:50 被阅读59次

    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工程

    新建C工程 新建C工程 新建Module Module添加C支持

    2.编译libjpeg-turbo

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

    libjpeg-turbo官网

    libjpeg-turbo源码

    复制源码

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

    libjpeg-turbo动态库

    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;
        //自定义的error
        struct 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_8888
                if (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(libjpeg
            SHARED
            IMPORTED)
    
    #设置 库的属性   里面是名称 ,属性:引入地址把我们的真实地址填写进去
    set_target_properties(libjpeg
            PROPERTIES IMPORTED_LOCATION
            ${distribution_DIR}/x86/libjpeg.so)
    
    #添加lib,SHARED类型,是IMPORTED 引入的库
    add_library(libturbojpeg
            SHARED
            IMPORTED)
    
    #设置 库的属性   里面是名称 ,属性:引入地址把我们的真实地址填写进去
    set_target_properties(libturbojpeg
            PROPERTIES 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-lib
            libjpeg
            -ljnigraphics
            libturbojpeg
            # 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">
    
        <TextView
            android: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" />
    
        <TextView
            android: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;
    
        @Override
        protected 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.运行

    native压缩 系统压缩 图片路径

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

    github源码

    相关文章

      网友评论

        本文标题:Android NDK编译libjpeg-turbo压缩图片

        本文链接:https://www.haomeiwen.com/subject/cyajvqtx.html