2011년 5월 18일 수요일

안드로이드 (Android) Bitmap 구현, 관리 하기

모든 프로그램에서 이미지 관리의 기본은 비트맵이다. 안드로이드에서도 마찬가지로

이미지 관리와 표현을 위해서는 비트맵을 익히는게 가장 기본이다. 그 비트맵 관련

내용들을 소개한다.

안드로이드에서 비트맵 관련 클래스는 android.graphics.Bitmap 이다. 그래픽 관련

클래스들은 android.graphics 패키지에 있으며 여기에 포함된 것이다.

그리고 객체 Factory 관리를 위한 BitmapFactory 클래스가 있다. BitmapFactory

여러가지 이미지 포맷을 decode 해서 bitmap 으로 변환하는 함수들로 되어있는데

그 이름들은 decodeXXX 로 되어있어서 쉽게 원하는 기능의 함수를 찾을수 있을

것이다.

(1) BitmapFactory 에서 주로 사용하고 있는 함수와 옵션에 대한 설명


BitmapFactory.decodeByteArray() : Camera.PictureCallBack 으로부터 받은 Jpeg 사진

데이터를 가지고 Bitmap 으로 만들어 줄 때 많이 사용한다.

Camera.PictureCallback 에서 들어오는 데이터가 byte[] 배열로 들어오기 때문이다.

BitmapFactory.decodeFile() : 로컬에 존재하는 파일을 그대로 읽어올 때 쓴다. 파일경로를

파라미터로 넘겨주면 FileInputStream 을 만들어서 decodeStream 을 한다.

1Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”);

BitmapFactory.decodeResource() : Resource 폴더에 저장된 그림파일을 Bitmap 으로

만들어 리턴해준다

1Bitmap orgImage =
2 BitmapFactory.decodeResource(getResources(), R.drawable.test02);

BitmapFactory.decodeStream() : InputStream 으로부터 Bitmap 을 만들어 준다.

BitmapFactory.Options : BitmapFactory 가 사용하는 옵션클래스이다. Options 객체를 생성하고

설정하고자 하는 옵션을 넣은후 BitmapFactory 의 함수 실행시 파라미터로 넘기면된다.

inSampleSize : decode 시 얼마나 줄일지 설정하는 옵션인데 1보다 작을때는 1이 된다.

1보다 큰값일 때 1/N 만큼 이미지를 줄여서 decoding 하게 된다. 보통 2의 배수로 설정한다.

1BitmapFactory.Options options = new BitmapFactory.Options();
2options.inSampleSize = 4;
3Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”, options);

(2) Bitmap 과 BitmapFactory 을 사용한 여러가지 예제


BitmapFactory 로 이미지를 읽어온뒤 Bitmap.createScaledBitmap() 사용해서 크기를 재조정

할수 있다. 하지만 예를 들어 크기를 일정하게 2, 4 배등으로 줄일거면 굳이 createScaledBitmap

을 사용하지 않고 위에서 설명한 BitmapFactory.Options inSampleSize 를 사용하면 된다.

아래는 SD 카드에서 이미지를 불러와 Bitmap 을 원하는 크기 만큼 줄인 예제이다.

1Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”);
2Bitmap resize = Bitmap.createScaledBitmap(orgImage, 300, 400, true);

다음은 BitmapFactory.Options 사용해서 이미지를 4배로 줄인것인데 createScaledBitmap

사용해서 용량을 줄인 이미지에 다시 입력한 크기만큼 가로, 세로 크기를 줄인 것이 된다

1BitmapFactory.Options options = new BitmapFactory.Options();
2options.inSampleSize = 4;
3Bitmap orgImage = BitmapFactory.decodeFile(“/sdcard/test.jpg”, options);
4Bitmap resize = Bitmap.createScaledBitmap(orgImage, 300, 400, true);

google_protectAndRun("ads_core.google_render_ad", google_handleError, google_render_ad);

[OutOfMemoryError??]

보통 이미지 파일을 읽어서 Resizing을 해야 할 때가 있는데,
그럴때는 BitmapFactory로 읽어서 Bitmap.createScaledBitmap() 메소드를 사용하여 줄이면

간단하게 처리 할 수 있습니다.

그런데 BitmapFactory를 사용할 때 주의해야 할 점이 있습니다.
아래의 예를 한번 보시죠.

Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg");
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

이미지 파일로부터 Bitmap을 만든 다음에

다시 dstWidth, dstHeight 만큼 줄여서 resized 라는 Bitmap을 만들어 냈습니다.
보통이라면 저렇게 하는게 맞습니다.

읽어서, 줄인다.

그런데 만약 이미지 파일의 크기가 아주 크다면 어떻게 될까요?
지금 Dev Phone에서 카메라로 촬영하면
기본적으로 2048 x 1536 크기의 Jpeg 이미지가 촬영된 데이터로 넘어옵니다.
이것을 decode 하려면 3MB 정도의 메모리가 필요 할 텐데,

과연 어떤 모바일 디바이스에서 얼마나 처리 할 수 있을까요?

실제로 촬영된 Jpeg 이미지를 여러번 decoding 하다보면

아래와 같은 황당한 메세지를 발견 할 수 있습니다.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget

네... OutOfMemory 입니다.
더 이상 무슨 말이 필요 하겠습니까...
메모리가 딸려서 처리를 제대로 못합니다.

이것이 실제로 decoding 후 메모리 해제가 제대로 되지 않아서 그런 것인지,
하더라도 어디서 Leak이 발생 하는지에 대한 정확한 원인은 알 수 없습니다.
이것은 엔지니어들이 해결해야 할 문제 겠죠...

하지만 메모리 에러를 피할 수 있는 방법이 있습니다.


[BitmapFactory.Options.inSampleSize]

BitmapFactory.decodeXXX 시리즈는 똑같은 메소드가 두 개씩 오버로딩 되어 있습니다.
같은 이름이지만 Signature가 다른 메소드의 차이점은
BitmapFactory.Options를 파라메터로 받느냐 안받느냐의 차이죠.

BitmapFactory.Options를 사용하게 되면 decode 할 때 여러가지 옵션을 줄 수 있습니다.


여러가지 많지만 저희가 지금 사용할 것은 inSampleSize 옵션 입니다.

inSampleSize 옵션은,
애초에 decode를 할 때 얼마만큼 줄여서 decoding을 할 지 정하는 옵션 입니다.

inSampleSize 옵션은 1보다 작은 값일때는 무조건 1로 세팅이 되며,
1보다 큰 값, N일때는 1/N 만큼 이미지를 줄여서 decoding 하게 됩니다.
즉 inSampleSize가 4라면 1/4 만큼 이미지를 줄여서 decoding 해서 Bitmap으로 만들게 되는 것이죠.

2의 지수만큼 비례할 때 가장 빠르다고 합니다.
2, 4, 8, 16... 정도 되겠죠?

그래서 만약 내가 줄이고자 하는 이미지가 1/4보다는 작고 1/8보다는 클 때,
inSampleSize 옵션에 4를 주어서 decoding 한 다음에,

Bitmap.createScaledBitmap() 메소드를 사용하여 한번 더 줄이면 됩니다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

당연한 이야기 이겠지만,
내가 원하고자 하는 사이즈가 딱 1/4 크기라면

Bitmap.createScaledBitmap() 메소드를 쓸 필요가 없지요.

inSampleSize 옵션을 잘 활용하면 메모리 부족 현상을 대략적으로 해소 할 수 있습니다.
참고로 제가 저 옵션을 사용한 뒤로는 메모리 에러를 본적이 한~번도 없답니다.


[Appendix]

inSampleSize 옵션을 사용하면

SkScaledBitmapSampler Object (Library Level) 를 생성 하게 되는데,
Object를 만들때 정해진 SampleSize 만큼 축소하여 width와 height를 정한 뒤에 만들게 됩니다.

그러니까 애초에 축소된 사이즈로 이미지를 decoding 하는 것이죠.

댓글 없음: