Tistory View

Android Develop/image

Android NDK Bitmap 메모리에 접근하기

God Dangchy What should I do? 2019. 8. 5. 18:16

Bitmap(android.graphics.Bitmap)의 픽셀단위로 처리하고 싶은 경우 getPixel/setPixel/setPixels/getPixels를 이용해야 한다. 하지만 이 처리는 java로 처리하기에는 속도가 너무 느리다. Android NDK에서는 이 처리를 위해 비트맵의 메모리에 직접 접근할 수 있는 방법을 제공하고 있다.

메모리에 직접접근이 가능하기 때문에, C로 미리 만들어진 그래픽라이브러리도 바로 사용이 가능한 장점이 있으며, 무엇보다도 빠른 속도로 처리할 수 있다.

 

 

사용법은 다음과 같다.

header파일은 <android/bitmap.h>를 include하고 library는 jnigraphics다. 링커에게 "-lJnigraphics"를 전달해 주면 된다.

 

다음의 코드는 CMakeLists.txt파일을 이용할 경우 target_link_libraries에 다음과 같이 넣으면 된다.

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        -ljnigraphics )

뭐.. 링크하지 않으면 undefined/unresolved function이 나오겠지만......뭐..

 

여기서 사용되는 함수들은 C-Type의 리턴값을 쓴다. 성공할 경우 0을 리턴하게되고 실패시 0이 아닌 값을 리턴하게 된다. <android/bitmap.h> 파일을 보면 성공값인 0은 ANDROID_BITMAP_RESULT_SUCCESS로 define되어 있으며, 실패는 모두 음수값으로 지정되어 있다. 이 것을 이용한 다른 소스들을 보면 실패를 검사하기 위해 다음과 같이 작성한 코드가 많이 있다.

if( AndroidBitmap_getInfo( env, bmp, &info ) < 0 ) {
	//실패 처리
}

하지만 필자는 다음과 같이 사용할 것을 권장한다. 뭐 특별한 이유는 없고 알아보기 쉬우니..

if( ANDROID_BITMAP_RESULT_SUCCESS != AndroidBitmap_getInfo( env, bmp, &info ) ) {
	//실패 처리
}

 

지원함수는 꼴랑 3개뿐이지만, 우리는 메모리에 직접 접근하는 것이 목적이니 이것으로 충분하다.

 

int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);

비트맵정보를 가지고 온다. Bitmap의 크기와 format을 알 수 있다.

typedef struct {
    /** The bitmap width in pixels. */
    uint32_t    width;
    /** The bitmap height in pixels. */
    uint32_t    height;
    /** The number of byte per row. */
    uint32_t    stride;
    /** The bitmap pixel format. See {@link AndroidBitmapFormat} */
    int32_t     format;
    /** Unused. */
    uint32_t    flags;      // 0 for now
} AndroidBitmapInfo;

 

지원하는 format은 다음과 같다.

enum AndroidBitmapFormat {
    /** No format. */
    ANDROID_BITMAP_FORMAT_NONE      = 0,
    /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
    /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
    ANDROID_BITMAP_FORMAT_RGB_565   = 4,
    /** Deprecated in API level 13.
    Because of the poor quality of this configuration,
    it is advised to use ARGB_8888 instead. **/
    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
    /** Alpha: 8 bits. */
    ANDROID_BITMAP_FORMAT_A_8       = 8,
};

가끔 정상적인 Bitmap도 ANDROID_BITMAP_FORMAT_NONE이 나올 때가 있는 데... 언젠지 기억이 안난다.

(이럴 경우, Java에서 Bitmap을 다시 만들어서 처리했던 기억이 있다. )

 

픽셀데이터의 pointer를 가지고 오는 함수

int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

 

 

다 사용 했으면 다시 java에서 쓸 수 있게 lock을 풀어 줘야 한다.

int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

 

따라서 보통 사용할 때는 다음의 구조를 갖는다.

 

java code

class YourClass {


	.
    .
    .
    .
	private native boolean func( Bitmap bmp );

}

 

jni code

#include <android/bitmap.h>




extern "C" JNIEXPORT jboolean JNICALL
Java_com_package_name_YourClass_func( JNIEnv *env, jobject _this, jobject bmp )
{
    assert( bmp );

    int rcc;
    jboolean rc = JNI_FALSE;

	AndroidBitmapInfo info;

    uint8_t* pBmp = NULL;

    try {
    	rcc = AndroidBitmap_getInfo( env, bmp, &info );
        if( rcc != ANDROID_BITMAP_RESULT_SUCCESS ) {
        	throw "get Bitmap Info failure";
        }

		if( info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 ) {
        	throw "only ARGB888 format support";
        }

        rcc = AndroidBitmap_lockPixels( env, bmp, (void**)&pBmp );
        if( rcc != ANDROID_BITMAP_RESULT_SUCCESS ) {
        	throw "lockPixels failure";
        }
    
    	for( int y = 0 ; y < info.height ; y++ ) {
        	uint8_t* px = pBmp + y * info.stride;
            for( int x = 0 ; x < info.width ; x++ ) {
            	
                px[0] = uint8_t( ( (float)x / info.width  ) * 255.0f );  // R
                px[1] = uint8_t( ( (float)y / info.height ) * 255.0f );  // G
                px[2] = 0x00;  // B
                px[3] = 0xff;  // A
                
                px += 4;
            }
        }
                
        rc = JNI_TRUE;
    }
    catch( const char* e ) {

    }

	// lock이 제대로 처리된 경우 unlock해준다.
    if( pBmp ) {
    	AndroidBitmap_unlockPixels( env, bmp );
    }

    return rc;
}

픽셀의 순서는 LITTLE ENDIAN기준으로 RGBA순서를 같는다. BIG ENDIAN의 경우는 기기가 없어서 테스트는 못 해봤다.

ENDIAN에 신경쓰지 않으려면 int의 pointer로 처리하면 된다. [0xABGR]로 처리하면 된다.(이 것도 확인 못했다)

 

 

 

위의 코드로 만들어진 비트맵

 

 

 

 

 

Replies
Reply Write