Android NDK之旅——圖片高斯模糊

  • 閱讀本文可能花費的時間
    15分鐘
  • 本文可能瞭解到的知識
    1. CMake基本使用
    2. Android NDK開發/使用
    3. JNI層操做Java對象java

  • 實現效果
    Android使用C/C++實現圖片的毛玻璃效果。android

  • 注:
    1. 本文研究對象爲Android JNI/NDK開發,非圖片算法,故不對毛玻璃算法作闡述。
    2. 本人能力有限,若有不妥請指出。

前言

十一假期幾天的思考,確立了本身的進階方向,打算了解下計算機視覺方面的技術,也就是opencv。在Android中集成opencv的話必然要掌握JNI/NDK的開發,因此寫了本文,一是向你們分享本身的學習經驗,二是鞏固本身的JNI/NDK開發和拋棄已久的C/C++方面的知識。算法

CMake

CMake是一款項目構建工具,經過編寫簡單明瞭的在CmakeLists.txt來生成makefile,簡單來講就是一個makefile生成器。數組

在Android Studio中安裝CMake很是簡單,打開Tools->Android->SDK Manager,選擇SDK Tools標籤頁,勾選CMake、LLDB、NDK,OK自動安裝便可。其中LLDB可使咱們在Android Studio中調試C/C++程序。NDK爲原生開發工具包,必不可少。
緩存

爲何要作JNI/NDK開發

衆所周知,Java/Android程序是運行在JVM/Dalvik VM中,因此Java程序遠沒有C/C++程序性能高,尤爲是在CPU密集型運算時,因此Java平臺提供了JNI(Java Native Interface),可經過JNI調用C/C++等編寫的so動態連接庫。
注:Google在Android L之後用ART完全代替了Dalvik VM,但ART本質上還是一個虛擬機,並支持全部Dalvik VM指令集。
Java API中幾乎全部與硬件相關的方法都是native的,好比I/O操做、網絡訪問、手機傳感器、串口讀寫等。
本文涉及的圖片處理是一種CPU密集型任務,在Android開發中使用native方法最爲合適。bash

如何使用CMake作JNI/NDK開發

1 新建工程


選中Include C++ Support,意爲引入C++支持。

2 配置C++支持


在Customize C++ Support界面默認便可,意爲CMake/C++11環境

3 認識CMakeLists.txt

工程建立完畢以後Android Studio會在app目錄下生成CMakeLists.txt文件。CMakeLists.txt是CMake的配置文件,用於代表版本、依賴、等信息,如下爲Android Studio生成的CMakeLists(過濾註釋)網絡

cmake_minimum_required(VERSION 3.4.1)

add_library(native-lib SHARED src/main/cpp/native-lib.cpp)

find_library(log-lib log)

target_link_libraries(native-lib ${log-lib})複製代碼
  • cmake_minimum_required(VERSION 3.4.1)
    CMake最小版本使用的是3.4.1。
  • add_library()
    配置so庫信息(爲當前當前腳本文件添加庫)
    • native-lib
      這個是聲明引用so庫的名稱,在項目中,若是須要使用這個so文件,引用的名稱就是這個。值得注意的是,實際上生成的so文件名稱是libnative-lib。
    • SHARED
      這個參數表示共享so庫文件,也就是在Run項目或者build項目時會在目錄intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so文件。
    • src/main/cpp/native-lib.cpp
      構建so庫的源文件。
  • find_library()
    查找一個庫文件
    • log-lib
      這個指定的是在NDK庫中每一個類型的庫會存放一個特定的位置,而log庫存放在log-lib中
    • log
      指定使用log庫
  • target_link_libraries()
    若是你本地的庫(native-lib)想要調用log庫的方法,那麼就須要配置這個屬性,意思是把NDK庫關聯到本地庫。
    • native-lib
      要被關聯的庫名稱
    • ${log-lib}
      要關聯的庫名稱,要用大括號包裹,前面還要有$符號去引用。

4 瞭解JNI的C/C++規範

數據類型

JNI的數據類型包含兩種,分別是基本類型和引用類型,它們和Java中的數據類型對應關係以下兩表所示。app

基本數據類型
JNI類型 Java類型 描述
jboolean boolean 無符號8位整型
jbyte byte 無符號8位整型
jchar char 無符號16位整型
jshort short 有符號16位整型
jint int 32位整型
jlong long 64位整型
jfloat float 32位浮點型
jdouble double 64位浮點型
void void 無類型
引用數據類型
JNI類型 Java類型 描述
jobject Object Object類型
jclass Class Class類型
jstring String String類型
jobjectArray Object[] 對象數組
jbooleanArray boolean[] boolean數組
jbyteArray byte[] byte數組
jcharArray char[] char數組
jshortArray short[] short數組
jintArray int[] int數組
jlongArray long[] long數組
jfloatArray float[] float數組
jdoubleArray double[] double數組
jthrowable Throwable Throwable

JNI的類型簽名

JNI的類型簽名標識了一個特定的Java類型,這個類型既能夠是類也能夠是方法,也能夠是數據類型。 ide

  • 類的簽名比較簡單,它採用 L+包名+類型+; 的形式,只須要將其中的.替換爲/便可。例如java.lang.String, 它的簽名爲Ljava/lang/String; ,注意末尾的;也是簽名一部分。
  • 基本數據類型的簽名採用一系列大寫字母來表示, 以下表所示
基本數據類型的簽名
Java類型 簽名 Java類型 簽名 Java類型 簽名
boolean Z byte B char C
short S int I long J
float F double D void V

JNI C/C++函數編寫

先來看看Android Studio爲咱們生成的示例 函數

JNIEXPORT jstring JNICALL
Java_com_glee_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}複製代碼
  • JNIEXPORT & JNICALL
    JNIEXPORT和JNICALL這兩個宏(被定義在jni.h)確保這個函數在本地庫外可見,而且編譯器會進行正確的調用轉換。
  • 函數規範
    在JNI中C/C++的函數名是有規範要求的,由如下幾部分串接而成
    • Java_前綴
    • 徹底限定的類名,並用下劃線「_」做爲分隔符
    • 第一參數JNIEnv* env
    • 第二個參數jobject或jclass
    • 其餘參數按類型映射
    • 返回參數按類型映射

JNI層操做Bitmap對象

原理

Android中JNI層處理Bitmap一般有兩種方法

  • 獲取到Bitmap中的byte數組並傳入native方法,JNI層處理獲得的byte數組後返回一個新的byte數組,Java層重建Bitmap對象。(不推薦)
  • Java層直接向JNI層傳入Bitmap的引用,JNI層獲得Bitmap對象的圖像數據的地址,直接修改Bitmap的byte數組。

閱讀了不少篇博客,不少開發者都會採用第一種方法,本人是極不推薦的。這種方法會在內存中重建一個byte數組,會形成內存的浪費,性能低下。
第二種方法是性能最優的,JNI層充分利用的C/C++指針的特性,直接獲取到Bitmap中byte數組在內存中的地址,經過指針直接修改圖像數據,因此用到了NDK中的android/bitmap.h。

android/bitmap.h

android/bitmap.h這個頭文件用於在JNI層操做Bitmap對象的,其包含於jnigraphics庫中,因此要在CMakeLists.txt中的target_link_libraries加入-ljnigraphics,以下

target_link_libraries(native-lib -ljnigraphics ${log-lib})複製代碼

三個經常使用函數

  • AndroidBitmap_getInfo() 從位圖句柄得到信息(寬度、高度、像素格式)
  • AndroidBitmap_lockPixels() 對像素緩存上鎖,即得到該緩存的指針。
  • AndroidBitmap_unlockPixels() 解鎖

JNI接口函數

請看註釋

JNIEXPORT void JNICALL
Java_com_glee_ndkroad1006_MainActivity_gaussBlur(JNIEnv *env, jobject /* this */, jobject bmp) {
    AndroidBitmapInfo info = {0};//初始化BitmapInfo結構體
    int *data=NULL;//初始化Bitmap圖像數據指針
    AndroidBitmap_getInfo(env, bmp, &info);
    AndroidBitmap_lockPixels(env, bmp, (void **) &data);//鎖定Bitmap,而且得到指針
    /**********高斯模糊算法做對int數組進行處理***********/
    //調用gaussBlur函數,把圖像數據指針、圖片長寬和模糊半徑傳入
    gaussBlur(data,info.width,info.height,80);
    /****************************************************/
    AndroidBitmap_unlockPixels(env,bmp);//解鎖
}複製代碼

這裏用到的gaussBlur函數代碼將在文章最後列出。
這裏用到的gaussBlur函數代碼將在文章最後列出。
這裏用到的gaussBlur函數代碼將在文章最後列出。

Java層代碼

請看註釋

public class MainActivity extends AppCompatActivity {

    static {
        //經過靜態代碼塊加載so庫
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化兩個ImageView
        ImageView iv1 = (ImageView) findViewById(R.id.img1);
        ImageView iv2 = (ImageView) findViewById(R.id.img2);
        //iv1設置圖片
        iv1.setImageResource(R.drawable.test);
        //生成bitmap對象
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        //調用native方法,傳入Bitmap對象,對Bitmap進行高斯迷糊處理
        gaussBlur(bitmap);
        //把Bitmap對象設置給iv2
        iv2.setImageBitmap(bitmap);
    }
    //native方法聲明
    public native void gaussBlur(Bitmap bitmap);
}複製代碼

運行效果

上方的ImageView是沒有進行高斯模糊處理的,下方的ImageView調用了JNI方法進行高斯模糊處理。

高斯模糊算法

void gaussBlur1(int* pix, int w, int h, int radius)
{
    float sigma = (float) (1.0 * radius / 2.57);
    float deno  = (float) (1.0 / (sigma * sqrt(2.0 * PI)));
    float nume  = (float) (-1.0 / (2.0 * sigma * sigma));
    float* gaussMatrix = (float*)malloc(sizeof(float)* (radius + radius + 1));
    float gaussSum = 0.0;
    for (int i = 0, x = -radius; x <= radius; ++x, ++i)
    {
        float g = (float) (deno * exp(1.0 * nume * x * x));
        gaussMatrix[i] = g;
        gaussSum += g;
    }
    int len = radius + radius + 1;
    for (int i = 0; i < len; ++i)
        gaussMatrix[i] /= gaussSum;
    int* rowData  = (int*)malloc(w * sizeof(int));
    int* listData = (int*)malloc(h * sizeof(int));
    for (int y = 0; y < h; ++y)
    {
        memcpy(rowData, pix + y * w, sizeof(int) * w);
        for (int x = 0; x < w; ++x)
        {
            float r = 0, g = 0, b = 0;
            gaussSum = 0;
            for (int i = -radius; i <= radius; ++i)
            {
                int k = x + i;
                if (0 <= k && k <= w)
                {
                    //獲得像素點的rgb值
                    int color = rowData[k];
                    int cr = (color & 0x00ff0000) >> 16;
                    int cg = (color & 0x0000ff00) >> 8;
                    int cb = (color & 0x000000ff);
                    r += cr * gaussMatrix[i + radius];
                    g += cg * gaussMatrix[i + radius];
                    b += cb * gaussMatrix[i + radius];
                    gaussSum += gaussMatrix[i + radius];
                }
            }
            int cr = (int)(r / gaussSum);
            int cg = (int)(g / gaussSum);
            int cb = (int)(b / gaussSum);
            pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000;
        }
    }
    for (int x = 0; x < w; ++x)
    {
        for (int y = 0; y < h; ++y)
            listData[y] = pix[y * w + x];
        for (int y = 0; y < h; ++y)
        {
            float r = 0, g = 0, b = 0;
            gaussSum = 0;
            for (int j = -radius; j <= radius; ++j)
            {
                int k = y + j;
                if (0 <= k && k <= h)
                {
                    int color = listData[k];
                    int cr = (color & 0x00ff0000) >> 16;
                    int cg = (color & 0x0000ff00) >> 8;
                    int cb = (color & 0x000000ff);
                    r += cr * gaussMatrix[j + radius];
                    g += cg * gaussMatrix[j + radius];
                    b += cb * gaussMatrix[j + radius];
                    gaussSum += gaussMatrix[j + radius];
                }
            }
            int cr = (int)(r / gaussSum);
            int cg = (int)(g / gaussSum);
            int cb = (int)(b / gaussSum);
            pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000;
        }
    }
    free(gaussMatrix);
    free(rowData);
    free(listData);
}複製代碼

相關文章
相關標籤/搜索