Android NDK編譯libjpeg-turbo壓縮圖片

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;
    //自定義的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.運行

經過測試,原5M的圖片,native壓縮比系統壓縮耗時要長,圖片效果差不多一致,文件大小一樣,難道高版本手機內部也使用了哈夫曼算法壓縮?感覺有點坑啊,看不到優勢了,算了先就這樣吧,貼一份源碼。

github源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章